JBean File Storage
Agosto 20, 2008
Após um bomtempo sem publicar devido a minha dedicação ao projeto “Easy Accept Web” (mas isso fica para outro post), anuncio um novo componente para auxílio a manipulação e associação de caminhos de arquivos/diretórios com POJOS. Muito útil quando sua aplicação trabalha em conjunto com framework ORM (Hibernate like), onde se deseja por exemplo associar uma entidade com um determinado diretório de arquivos (para upload/download por exemplo), seu nome por enquanto é :
JBean File Storage
O componente ainda apesar de implementado ainda não está disponibilizado publicamente pois ainda necessita de revisão, documentação e algumas possíveis melhorias embora esta versão esteja presente em 2 projetos já em produção.
A idéia chave foi associar um caminho a um tipo java, eu mentiria se dissesse que a idéia foi completamente minha pois me baseei numa solução proposta por Luciano Logrado ao trabalharmos juntos em um outro projeto, digamos que eu incrementei a idéia para suportar polimorfismo e com isto será possível:
- Associar um caminho a uma tipo (classe ou interface) e seus subtipos também estarão associados
- Usar uma Expression Language (semelhante a do JSP 2.0) mínima para usar caminhos dinâmicos
- Herdar um caminho dum outro POJO
- Manter na hierarquia de diretórios a hierarquia nas associações dos POJOS
Estes dois últimos são importantes caso por exemplo existam dois POJO’s com relacionamento de pai/filho e ao excluir o pai os filhos devem ser excluídos:
Cliente ->* Conta
Desta forma o caminho de arquivos do cliente poderia ser:
$/Cliente_99
/Conta_33
MeuExtrato1.txt
/Conta_34
Extrato2008.txt
Com uma operação de exclusão do diretório do cliente haverá propagação no sistema de arquivo para os subdiretórios, ou seja, as contas. Além de manter a organização dos arquivos numa hierarquia de pastas de fácil entendimento visual e manipulação.
Veja detalhes do uso:
Configuração
Consiste em definir num arquivo xml os caminhos (paths) e as formas de amazenamento de arquivos utilizada (Stores).
Arquivo: test-file-storage.xml
<jbfs-conf>
<stores>
<local-system-file-store
name="MySampleFileStorage"
rootDir="C:/temp" />
</stores>
<paths>
<path name="root" >/MyRootDir</path>
<path name="fatherDir" type="jbfs.test.FatherBean">
${root}/Father/${_bean.name}
</path>
<path name="childSubdir">
${fatherDir}/ChildSubdir
</path>
<path name="childFileName" type="jbfs.test.ChildBean" >
${_bean.father}/ChildDir/${_bean.childName}.txt
</path>
</paths>
</jbfs-conf>
Uso
Basta recuperar a instância do JBFS e utlizar os dois objetos principais:
- PathResolver (Resolvedor de Caminhos) -> Resolve qual caminho associado ao objeto ou um nome utilizando os paths configurados.
- FileStorage (O armazenamento de arquivos) -> Representa uma Store configurada e executa as operações sobre o sistema de arquivo escolhido.
- Podem existir várias implementações de FileStorage conforme o sistema de arquivo utilizado: FTP, sistema de arquivos remoto, etc. Por enquanto só existe a implementação de armazenamento em disco local: <local-system-file-store />
Segue abaixo um teste unitário que exemplifica o uso duma possível classe cliente do nosso componente:
package jbfs.test;
public class TestPathResolver {
protected JBeanFileStorage jbfs;
//CONFIGURANDO
@Before
public void setUp() {
jbfs = new JBeanFileStorage("test-storage.xml"); //File must be on classpath
}
//Resolvendo um diretório baseado em um nome
@Test
public void testResolveSimplePathFromName() {
String path = jbfs.getPathResolver().resolveName("root", null);
assertEquals("/MyRootDir", path);
}
//Resolvendo um diretório baseado em um Bean
@Test
public void testResolvePathFromBean() {
FatherBean bean = new FatherBean("fatherName");
String path= jbfs.getPathResolver().resolveBean(bean);
assertEquals("/MyRootDir/Father/fatherName", path);
}
@Test
public void testResolveComplexPathFromBean() {
ChildBean bean = new ChildBean("childFile", new FatherBean("fatherName"));
String filePath = jbfs.getPathResolver().resolveBean(bean);
assertEquals("/MyRootDir/Father/fatherName/ChildDir/childFile.txt", filePath);
}
//TESTANDO MANIPULAÇÃO DOS ARQUIVOS
@Test
public void testFileCRUD() {
ChildBean bean = new ChildBean("childFile", new FatherBean("fatherName"));
String filePath= jbfs.getPathResolver().resolveBean(bean);
FileStorage storage = jbfs.getStore("MySampleFileStorage");
try {
storage.createNewFile(filePath, "hellow".getBytes());
assertTrue(storage.exists(filePath));
assertEquals("hellow", new String(storage.getBytes(filePath)));
storage.delete(filePath);
assertFalse(storage.exists(filePath));
} catch (IOException e) {
e.printStackTrace();
fail("Test fail");
}
}
}
Integração com outros Componentes
Quando trabalhando com um framework ORM como Hibernate, pode ser interessante apagar os arquivos associados de uma entidade automaticamente quando for removida do Banco de Dados. Seguindo o exemplo Cliente -> * Conta, quando a transação que remove a entidade Cliente for confirmada os arquivos do cliente e suas contas serão excluídos. Para isto interceptador como o implementado abaixo pode ser utilizado em conjunto com o JBFS:
/**
* File Garbage Collector tries to remove file paths associated with deleted
* Hibernate Entities.
*
* @author Fabrício Silva
*
*/
public class FileGarbageCollector extends EmptyInterceptor {
private FileStorage repository;
private PathResolver pathResolver;
private JBeanFileStorage jbfs;
private static SessionFactory hibernateSessionFactory;
private Map trash = new HashMap();
public FileGarbageCollector (SessionFactory hibernateSessionFactory){
this.hibernateSessionFactory = hibernateSessionFactory;
}
@Override
public void onDelete(Object entity, Serializable id, Object[] state,
String[] propertyNames, Type[] types) {
if (jbfs.getConfiguration().hasPolimorficPath(entity)) {
putPathInGarbageForDeletion(pathResolver.resolveBean(entity));
}
}
@Override
public void afterTransactionCompletion(Transaction tx) {
performGarbageDeletion(tx);
}
private void performGarbageDeletion(Transaction tx) {
Garbage g = trash.remove(tx);
if (g != null && tx.wasCommitted() && !tx.wasRolledBack()) {
for (String path : g.paths) {
try {
repository.deleteQuietly(path);
} catch (Exception e) {
logger.debug(
"Error when file garbage collector try to delete path:"
+ path, e);
}
}
}
}
private void putPathInGarbageForDeletion(String path) {
Transaction tx = hibernateSessionFactory.getCurrentSession()
.getTransaction();
Garbage g = trash.get(tx);
if (g == null) {
g = new Garbage();
trash.put(tx, g);
}
g.addPath(path);
}
class Garbage {
protected List paths = new LinkedList();
void addPath(String path) {
paths.add(path);
}
List getPaths() {
return paths;
}
void setPaths(List paths) {
this.paths = paths;
}
}
//O código que inicializa as variáveis de instância foi omitido...
}
Por o JFBS ser um POJO ele pode facilmente se integrar com qualquer framework ou componente de sua aplicação, em especial no framework Spring sua configuração é tão simples como de qualquer outro bean:
<!-- File Storage --> <bean id="jbfs" class="easyaccept.util.file.jbfs.JBeanFileStorage" scope="singleton" > <property name="confLocation"> <value>file-storage.xml</value> </property> </bean>
Espero que tenham gostado, quando o componente estiver com o mínimo aceitável de documentação será publicado aqui. Futuramente outras implementações de armazenamento estarão disponíveis:
- FTP
- SVN
- CVS
- Remote System File
Abraço.
Bem interessante e bacana!
Você pesquisou algum outro framework antes de implementa-lo?
No mais, quando puder disponibilize o source e algum step-by-step bem básico mesmo, assim outros desenvolvedores poderão fazer alguns testes, até quem sabe corrigir problemas ou mesmo ajuda-lo no desenvolvimento.
Abraços e parabéns.
Opa Rafael! Desculpe a demora em responder seu comment, descobri quando estava terminando o JBFS que a apache possui o VFS
que se propõe a facilitar e abstratir acesso/manipulaçãode arquivos, que é equivalente a abstração “file store” proposta no JBFS. Eu só havia implementado uma store para arquivos locais, VFS da apache (na época em desenvolvimento) iria ter implementações até p/ sistemas remotos como FTP, etc.. por isso dei um stop no projeto enquanto eles não lançavam isto, hoje já está lançado e estável.
Também existe o RSE da plataforma eclipse que trabalha com abstração do acesso arquivos locais e remotos de forma transparente. Enfim hoje podemos continuar/refatorar o JBFS usando “coisas” já prontas para nossas “file stores”, (me tirando do sufoco =]), mas JBFS tem algo a mais, pois ele não trata só da abstração de manipular o armazenamento do arquivo, mas da associação com POJOS, possivelmente em conjunto com frameworks ORM de persistência. Daqui a alguns dias estarei liberando o componente na versão atual para comunidade, e se for o caso de haverem interessados no uso do componente, podemos colaborar para fechar uma versão beta.
Que projeto genial
Parabéns pelos trabalhos ( e inclui o resto da equipe).
ehehehe, justiça seja feita aos co-autores