Aew Pessoal.
Hoje vou começar a postar sobre o projeto que estou trabalhando para meus alunos de Desenvolvimento de Sistemas. Este projeto consiste em criar uma aplicação do início ao fim usando diversas tecnologias, conceitos de Engenharia de Software e boas práticas de Arquitetura de Software. Este projeto será separados em vários subsistemas, sendo composto por: Servidor (disponibiliza serviços), Mobile (android), Desktop (Swing - Window Builder) e Web (estou analisando a tecnologia que vamos usar). Para este trabalho será usado o banco de dados Northwind, sendo que será possível usar SQL Server ou o MySQL.
O código principal do projeto está disponível em: http://wpattern.codeplex.com/SourceControl/BrowseLatest (sample projects => java => wpattern-northwind-sample => wpattern-northwind-all).
Este projeto é muito grande e utiliza várias tecnologias. Com o decorrer dos posts irei adicionando os ferramentas que vamos trabalhar. Neste primeiro post irei mostrar como configurar o ambiente e explicar os conceitos básicos do que está sendo feito.
As tecnologias que vamos utilizar são:
- Northwind: Durante os trabalhos irei disponibilizar este banco de dados para o SQL Server e MySQL.
- Maven: Será usado para o controle de dependências e a automatização de alguns processos.
- Eclipse Indigo: Podem usar outras versões do Eclipse. Contudo, indico o uso do Indigo para possamos trabalhar sobre as mesmas versões, facilitando a solução de possíveis problemas. Os plugins devem ser instalados utilizando a opção "Install New Software..." ou "Eclipse Market Place...".
- Plugin M2E (Maven 2 Eclipse): Plugin do Maven para o eclipse.
- Hibernate: Facilita a configuração do acesso ao banco de dados usando Hibernate.
- Subclipse: Usado para submeter códigos ao repositório SVN.
- Window Builder: Ferramenta usada para criar aplicativos Desktop.
- Tortoise: Usado para gerenciar códigos do repositório SVN.
- JDK 1.6.
- Spring: Vou utilizar as funcionalidades de Injeção de Dependência e RMI do Spring.
- Log4J: Usado para o log de informações.
- Dozer: Realiza o parser de Beans para Entidades e Entidades para Beans.
- Commons Lang3: Facilita o processo de log de beans.
- JUnit: Testes unitários.
- Codeplex: Site que vamos usar para criar projetos e submeter códigos (Repositório SVN).
É preciso configurar as variáveis de ambiente MAVEN_HOME e JAVA_HOME para o Maven e Java, respectivamente. Apenas com essa configuração será possível executar algumas operações em nossos projetos.
O vídeo abaixo mostra a configuração do nosso ambiente.
No próximo post vou mostrar a criação os projetos com o Maven.
Até mais pessoal, abraços.

Olá pessoal.
Apesar de já ter muito tempo que não público artigos, passei este período trabalhando em uma aplicação em Java e outra em .NET para postar no blog e ajudar meus alunos. Hoje irei escrever um pouco de uma parte do sistema em Java que estou trabalhando. Futuramente irei postar mais informações no blog, incluindo vídeos, diagramas e os códigos.
O código que irei mostrar é uma melhoria do tutorial de Introdução ao Hibernate. O objetivo é tornar os serviços mais simples de serem implementados e realizar o parser entre Entidades e Beans e vice-versa. Para realizar o parser é utilizada a biblioteca Dozer. Essa biblioteca é muito poderosa, permitindo a configuração de como é realizado o parser. Além disso, ela trata de beans complexos e é fácil de ser utilizada. Para adicionar essa biblioteca utilizando o maven temos o dependency:
<dependency>
<groupId>net.sf.dozer</groupId>
<artifactId>dozer</artifactId>
<version>5.3.2</version>
</dependency>
Porém, com a adição dessa dependência ocorreram conflitos com a slf4j-log4j12. Sendo assim, para o meu cenário foi necessário remover as dependências da SLF4J. Sendo assim, temos
<dependency>
<groupId>net.sf.dozer</groupId>
<artifactId>dozer</artifactId>
<version>5.3.2</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
</exclusion>
</exclusions>
</dependency>
Utilizando a Dozer o parser de objetos mais simples é realizado especificando o objeto que possui os valores e o tipo de destino que será feita a conversão. Uma vez que foi realizado o parser é retornado o objeto do tipo de destino. O código a seguir mostra um exemplo do método de parser de beans para entidades.
private static final Mapper dozerMapper = new DozerBeanMapper();
public static <S extends BaseBean, D extends BaseEntity> D parserBeanToEntity(S sourceObject, Class<D> destinationType) {
return dozerMapper.map(sourceObject, destinationType);
}
Para realizar o parser em geral de entidades, beans e keys, temos:
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import org.dozer.DozerBeanMapper;
import org.dozer.Mapper;
import br.com.wpattern.northwind.utils.beans.BaseBean;
public class ParserDatabase {
private static final Mapper dozerMapper = new DozerBeanMapper();
public static <S extends BaseBean, D extends BaseEntity> D parserBeanToEntity(S sourceObject, Class<D> destinationType) {
return dozerMapper.map(sourceObject, destinationType);
}
public static <S extends BaseBean, D extends BaseEntity> List<D> parserListBeansToEntities(List<S> sourceObjects, Class<D> destinationType) {
List<D> destinationObjects = new ArrayList<D>();
for (Object obj : sourceObjects) {
destinationObjects.add(dozerMapper.map(obj, destinationType));
}
return destinationObjects;
}
public static <S extends BaseEntity, D extends BaseBean> D parserEntityToBean(S sourceObject, Class<D> destinationType) {
return dozerMapper.map(sourceObject, destinationType);
}
public static <S extends BaseEntity, D extends BaseBean> List<D> parserListEntitiesToBeans(List<S> sourceObjects, Class<D> destinationType) {
List<D> destinationObjects = new ArrayList<D>();
for (Object obj : sourceObjects) {
destinationObjects.add(dozerMapper.map(obj, destinationType));
}
return destinationObjects;
}
public static <IDB extends Serializable, IDE extends Serializable> IDE parserKeyBeanToKeyEntity(IDB sourceObject, Class<IDE> destinationType) {
return dozerMapper.map(sourceObject, destinationType);
}
public static <IDE extends Serializable, IDB extends Serializable> IDB parserKeyEntityToKeyBean(IDE sourceObject, Class<IDB> destinationType) {
return dozerMapper.map(sourceObject, destinationType);
}
}
Com a classe ParserDatabase conseguimos realizar o parser em geral dos objetos usados em um módulo de acesso ao banco de dados e serviços.
Além do ParserDatabase, implementei uma classe abstrata para tornar o desenvolvimento de serviços básicos mais simples. Nem sempre um serviço utilizara esses métodos de "CRUD", porém, essa classe deixa o trabalho de criação destes serviços muito simples. Inicialmente, temos a classe GenericService que possui alguns métodos otimizados quando o identificador de uma entidade é um tipo de dado básico (em geral é um Long). Está classe é mostrada a seguir.
import java.io.Serializable;
import java.util.List;
import br.com.wpattern.northwind.utils.beans.BaseBean;
import br.com.wpattern.northwind.utils.database.interfaces.IServiceBase;
public abstract class GenericService<T extends BaseBean, K extends BaseEntity, ID extends Serializable>
extends GenericServiceWithKey<T, K, ID, ID> implements IServiceBase<T, ID> {
/////////////////////////////////////////////////////////////////////////////////
// PUBLIC METHODS (IServiceBase)
/////////////////////////////////////////////////////////////////////////////////
@Override
public T findById(ID id) {
return parserEntity(this.getConcreteDao().findById(id));
}
@Override
public List<T> findAll() {
return parserEntity(this.getConcreteDao().findAll());
}
@Override
public ID insert(T bean) {
return getConcreteDao().save(parserBean(bean));
}
@Override
public void update(T bean) {
this.getConcreteDao().update(parserBean(bean));
}
@Override
public void delete(T bean) {
this.getConcreteDao().delete(parserBean(bean));
}
}
Na GenericService especificamos o tipo da entidade e o bean correspondente. Além disso, definimos o tipo da chave usada pela entidade. É interessante observar que T estende BaseBean e K estende BaseEntity. Com isso evitamos possíveis erros quando as classes de serviço herdarem este abstract. Abaixo apresento a classe GenericServiceWithKey que é herdada pela GenericService. A classe GenericServiceWithKey trabalha da mesma forma que GenericService. Contudo, GenericServiceWithKey trata de chaves compostas de entidades. A seguir mostro a implementação da classe GenericServiceWithKey.
import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import org.apache.log4j.Logger;
import br.com.wpattern.northwind.database.utils.interfaces.IGenericDao;
import br.com.wpattern.northwind.utils.beans.BaseBean;
import br.com.wpattern.northwind.utils.database.interfaces.IServiceBase;
public abstract class GenericServiceWithKey<T extends BaseBean, K extends BaseEntity, IDE extends Serializable, IDB extends Serializable> implements IServiceBase<T, IDB> {
private final Logger logger = Logger.getLogger(this.getClass());
private final Class<T> typeOfBean;
private final Class<K> typeOfEntity;
private final Class<IDE> typeOfKeyEntity;
private final Class<IDB> typeOfKeyBean;
/////////////////////////////////////////////////////////////////////////////////
// CONSTRUCTORS
/////////////////////////////////////////////////////////////////////////////////
@SuppressWarnings("unchecked")
public GenericServiceWithKey() {
try {
ParameterizedType parameterizedType = (ParameterizedType)getClass().getGenericSuperclass();
Type[] genericTypes = parameterizedType.getActualTypeArguments();
this.typeOfBean = (Class<T>)genericTypes[0];
this.typeOfEntity = (Class<K>)genericTypes[1];
this.typeOfKeyEntity = (Class<IDE>)genericTypes[2];
this.typeOfKeyBean = (Class<IDB>)genericTypes[(genericTypes.length == 4) ? 3 : 2];
} catch (RuntimeException e) {
this.logger.error(e.getMessage(), e);
// Re-throw the exception.
throw e;
}
if (this.logger.isInfoEnabled()) {
this.logger.info(String.format("Creating a service with the bean [%s], entity [%s], key bean [%s] and key entity [%s].",
getTypeOfBean(), getTypeOfEntity(), getTypeOfKeyBean(), getTypeOfKeyEntity()));
}
}
/////////////////////////////////////////////////////////////////////////////////
// ABSTRACT METHODS
/////////////////////////////////////////////////////////////////////////////////
protected abstract IGenericDao<K, IDE> getConcreteDao();
/////////////////////////////////////////////////////////////////////////////////
// PUBLIC METHODS (IServiceBase)
/////////////////////////////////////////////////////////////////////////////////
@Override
public T findById(IDB id) {
return parserEntity(this.getConcreteDao().findById(parserKeyBean(id)));
}
@Override
public List<T> findAll() {
return parserEntity(this.getConcreteDao().findAll());
}
@Override
public IDB insert(T bean) {
return parserKeyEntity(getConcreteDao().save(parserBean(bean)));
}
@Override
public void update(T bean) {
this.getConcreteDao().update(parserBean(bean));
}
@Override
public void delete(T bean) {
this.getConcreteDao().delete(parserBean(bean));
}
/////////////////////////////////////////////////////////////////////////////////
// PUBLIC METHODS (GET)
/////////////////////////////////////////////////////////////////////////////////
public final Class<T> getTypeOfBean() {
return this.typeOfBean;
}
public final Class<K> getTypeOfEntity() {
return this.typeOfEntity;
}
public final Class<IDE> getTypeOfKeyEntity() {
return this.typeOfKeyEntity;
}
public final Class<IDB> getTypeOfKeyBean() {
return this.typeOfKeyBean;
}
/////////////////////////////////////////////////////////////////////////////////
// PUBLIC METHODS
/////////////////////////////////////////////////////////////////////////////////
public final T parserEntity(K entity) {
return ParserDatabase.parserEntityToBean(entity, getTypeOfBean());
}
public final K parserBean(T bean) {
return ParserDatabase.parserBeanToEntity(bean, getTypeOfEntity());
}
public final IDB parserKeyEntity(IDE idEntity) {
return ParserDatabase.parserKeyEntityToKeyBean(idEntity, getTypeOfKeyBean());
}
public final IDE parserKeyBean(IDB idBean) {
return ParserDatabase.parserKeyBeanToKeyEntity(idBean, getTypeOfKeyEntity());
}
public final List<T> parserEntity(List<K> entities) {
return ParserDatabase.parserListEntitiesToBeans(entities, getTypeOfBean());
}
public final List<K> parserBean(List<T> beans) {
return ParserDatabase.parserListBeansToEntities(beans, getTypeOfEntity());
}
}
Entretanto, existe um problema na implementação da GenericServiceWithKey. Na chamada por reflexão da super classe não necessariamente teremos um classe GenericService ou GenericServiceWithKey. Sendo assim, é preciso que recursivamente seja(m) recuperada(s) a(s) super(s) classe(s), e assim sejam extraídos os tipos genéricos. Em um próximo post irei falar sobre a solução deste problema.
A partir dessas classes podemos criar os serviços. A seguir apresento um exemplo de serviço desenvolvida seguindo estes conceitos.
import javax.inject.Inject;
import javax.inject.Named;
import br.com.wpattern.northwind.database.entities.CustomerCustomerDemoEntity;
import br.com.wpattern.northwind.database.entities.keys.CustomerCustomerDemoKeyEntity;
import br.com.wpattern.northwind.database.interfaces.ICustomerCustomerDemoDao;
import br.com.wpattern.northwind.database.utils.GenericServiceWithKey;
import br.com.wpattern.northwind.database.utils.interfaces.IGenericDao;
import br.com.wpattern.northwind.utils.database.beans.CustomerCustomerDemoBean;
import br.com.wpattern.northwind.utils.database.interfaces.ICustomerCustomerDemoService;
import br.com.wpattern.northwind.utils.database.keys.CustomerCustomerDemoKeyBean;
@Named
public class CustomerCustomerDemoService extends GenericServiceWithKey<CustomerCustomerDemoBean, CustomerCustomerDemoEntity, CustomerCustomerDemoKeyBean, CustomerCustomerDemoKeyEntity>
implements ICustomerCustomerDemoService {
@Inject
private ICustomerCustomerDemoDao customerCustomerDemoDao;
@Override
protected IGenericDao<CustomerCustomerDemoEntity, CustomerCustomerDemoKeyEntity> getConcreteDao() {
return this.customerCustomerDemoDao;
}
}
Um melhoria que estou planejando de fazer é de recuperar no próprio abstract a instância do DAO. O código completo do projeto que estou trabalhando pode ser encontrada em wpattern.codeplex.com (sample projects => java => wpattern-northwind-sample => wpattern-northwind-all).
Nos próximos posts irei explicar em detalhes este sistema.
Até mais a todos.