domingo, 30 de março de 2008

[ASP.NET] Componentizando o upload de imagens na camada de negócios

Um problema usual em aplicações ASP.NET é o envio de imagens. Esta necessidade é plenamente suprida pelo componente nativo ASP.NET FileUpload. Tal "WebControl", apresenta um TextBox e um botão "Browse", que encapsula a manipulação da stream da imagem durante o PostBack.

A figura ao lado apresenta-se um aplicação "mock" para envio de imagens. Abaixo segue o código em C# para o delegate do evento OnClick() do botão “Enviar”.

protected void btnEnviar_Click(object sender, EventArgs e)
{
if (FileUpload1.HasFile)
{
// Recupera dados da imagem
string fileName = FileUpload1.FileName;
string filePath = Page.MapPath("~") + Path.DirectorySeparatorChar +
"images" + Path.DirectorySeparatorChar + fileName;
byte[] data = FileUpload1.FileBytes;
// Escreve a imagem em disco
FileStream writer = new FileStream(filePath, FileMode.Create);
writer.Write(data, 0, data.Length);
writer.Close();
// Apresenta a imagem salva na tela
lblFileName.Text = "Arquivo enviado: " + fileName;
Image1.ImageUrl = "~/images/" + fileName;
Image1.Visible = true;
}
else
{
lblFileName.Text = string.Empty;
Image1.Visible = false;
}
}


No entanto, pode-se criticar esta implementação por quebrar a separação em camadas, uma vez que a lógica de manipulação da imagem está inserida no modelo da camada de apresentação (o arquivo .aspx.cs representa o model MVC). Desta forma se propõe o serviço abaixo a ser encapsulado em um assembly (dll) a parte para fins de reuso.

interface IUploader

using System;
namespace net.jorgealbuquerque.Services.Uploader
{
public interface IUploader
{
string BaseDir { get; set; }
System.Drawing.Size ThurmbnailSize { get; set; }
bool CreateThumbnails { get; set; }
string GetImageDirectory();
void Upload(string fileName, System.IO.Stream data);
void Upload(string fileName, byte[] data);
void Delete(string fileName);
void CreateThumbnail(string fileName);
}
}


classe UploaderService
using System;
using System.IO;
namespace net.jorgealbuquerque.Services.Uploader
{
public class UploaderService: IUploader
{
private bool _CreateThumbnails = true;
public bool CreateThumbnails
{
get { return _CreateThumbnails; }
set { _CreateThumbnails = value; }
}
private Size _ThurmbnailSize = new Size(64, 64);
public Size ThurmbnailSize
{
get { return _ThurmbnailSize; }
set
{
Size size = value;
if (size == null)
throw new ArgumentNullException("O tamanho do thurmbnail não pode ser nulo.");
if (size.Height < 5 || size.Width < 5)
throw new ArgumentException("O tamanho do thurmbnail inválido!");
_ThurmbnailSize = size;
}
}
private string _baseDir = "images";
public string BaseDir
{
get { return _baseDir; }
set
{
string dir = value;
if (string.IsNullOrEmpty(dir))
throw new ArgumentException("Nome de diretório base inválido!");
dir = dir.Replace('/', ' ');
dir = dir.Replace('\\', ' ');
dir = dir.Trim();
_baseDir = dir;
}
}
public string GetImageDirectory()
{
return AppDomain.CurrentDomain.BaseDirectory +
_baseDir + Path.DirectorySeparatorChar;
}
protected void Validate(string fileName)
{
if (string.IsNullOrEmpty(fileName))
throw new ArgumentException("nome de arquivo inválido!");
string ext = Path.GetExtension(fileName).ToLower();
if (ext != ".jpg" && ext != ".gif" &&
ext != ".bmp" && ext != ".png" && ext != ".tif")
throw new ArgumentException("extensão de arquivo inválida!");
string dir = GetImageDirectory();
if (!Directory.Exists(dir))
Directory.CreateDirectory(dir);
}
public void Upload(string fileName, byte[] data)
{
Validate(fileName);
FileStream writer = new FileStream(GetImageDirectory() + fileName,
FileMode.Create);
writer.Write(data, 0, data.Length);
writer.Close();
if (_CreateThumbnails)
CreateThumbnail(fileName);
}
public void Upload(string fileName, Stream data)
{
byte[] buffer = new byte[data.Length];
data.Write(buffer, 0, (int)data.Length);
Upload(fileName, buffer);
buffer = null;
GC.SuppressFinalize(buffer);
if (_CreateThumbnails)
CreateThumbnail(fileName);
}
public void Delete(string fileName)
{
string filePath = GetImageDirectory() + fileName;
if (File.Exists(filePath))
File.Delete(filePath);
string thumbnailPath = GetImageDirectory() + "thumb_" + fileName;
if (File.Exists(thumbnailPath))
File.Delete(thumbnailPath);
}
public void CreateThumbnail(string fileName)
{
string thumbnailName = "thumb_" + fileName;
string filePath = GetImageDirectory() + fileName;
string thumbnailPath = GetImageDirectory() + thumbnailName;
System.Drawing.Image img = System.Drawing.Image.FromFile(filePath);
System.Drawing.Image thumbnail = img.GetThumbnailImage(
_ThurmbnailSize.Width,
_ThurmbnailSize.Height,
new System.Drawing.Image.GetThumbnailImageAbort(ThumbnailCallback),
IntPtr.Zero);
thumbnail.Save(thumbnailPath);
}
protected bool ThumbnailCallback()
{
return true;
}
}
}

Finalmente, sugere-se uma fábrica concreta que permita um ponto de junção para a evolução do componente:

classe UploaderServiceFactory

using System;
namespace net.jorgealbuquerque.Services.Uploader
{
public class UploaderServiceFactory
{
private UploaderServiceFactory() { }
private static UploaderServiceFactory _instance;
public static UploaderServiceFactory GetInstance()
{
if (_instance == null)
_instance = new UploaderServiceFactory();
return _instance;
}
public IUploader GetUploader()
{
return new UploaderService();
}
}
}



Desta forma, o delegate do evento OnClick() seria reescrito da forma abaixo. Observa-se a separação entre lógica de negócios e apresentação.

protected void btnEnviar_Click(object sender, EventArgs e)
{
if (FileUpload1.HasFile)
{
UploaderServiceFactory().GetInstance().GetUploader().
Upload(FileUpload1.FileName, FileUpload1.FileBytes);
lblFileName.Text = "Arquivo enviado: " + FileUpload1.FileName;
Image1.ImageUrl = "~/imagens/" + FileUpload1.FileName;
Image1.Visible = true;
}
else
{
lblFileName.Text = string.Empty;
Image1.Visible = false;
}
}

Note ainda que o componente pode ser igualmente empregado em contexto Windows Forms sem qualquer adaptação, ampliando seu potencial de reuso. De forma a tornar mais evidente a separação em camadas obtida pelo componente descrito, propõe-se a persistência do cadastro de imagens. Neste contexto, definiu-se a camada de persistência conforme a figura ao lado através de um componente DataSet "tipado", de acordo com o modelo proposto pela Microsoft. Por sua vez, o serviço da camada de negócios também segue as recomendações da empresa, sendo apresentado abaixo:

using System;
using System.IO;
using System.Collections.Generic;
using System.Web.UI.WebControls;
using System.ComponentModel;
using net.jorgealbuquerque.Sample.Dao;
using net.jorgealbuquerque.Sample.Dao.DataSet1TableAdapters;
using net.jorgealbuquerque.Services.Uploader;
namespace net.jorgealbuquerque.Sample.Services
{
[System.ComponentModel.DataObject]
public class ManterImagesService
{
private static ImageDAO _DAO = null;
protected ImageDAO DAO
{
get
{
if (_DAO == null)
_DAO = new ImageDAO();
return _DAO;
}
}
public void Upload(FileUpload fileUpload, string comentario)
{
UploaderServiceFactory.GetInstance().GetUploader().
Upload(fileUpload.FileName, fileUpload.FileBytes);
Insert(fileUpload.FileName, DateTime.Now, comentario);
}
public void Upload(byte[] imageStream, string nome, string comentario)
{
UploaderServiceFactory.GetInstance().GetUploader().
Upload(nome, imageStream);
Insert(nome, DateTime.Now, comentario);
}
public void Upload(Stream imageStream, string nome, string comentario)
{
UploaderServiceFactory.GetInstance().GetUploader().
Upload(nome, imageStream);
Insert(nome, DateTime.Now, comentario);
}
[System.ComponentModel.DataObjectMethod(DataObjectMethodType.Select, true)]
public DataSetLisier.ImageDataTable GetAll()
{
return DAO.GetAll();
}
[System.ComponentModel.DataObjectMethod(DataObjectMethodType.Select, false)]
public DataSetLisier.ImageDataTable GetByID(int ID)
{
if (ID < -1)
throw new ArgumentException("ID inválido!");
return DAO.GetByID(ID);
}
public DataSetLisier.ImageRow GetRowByID(int ID)
{
DataSetLisier.ImageDataTable tbl = GetByID(ID);
if (tbl.Count != 1)
throw new ArgumentException("Imagem não encontrada!");
return (DataSetLisier.ImageRow)tbl.Rows[0];
}
[System.ComponentModel.DataObjectMethod(DataObjectMethodType.Delete, true)]
public void Delete(int ID)
{
if (ID < 0)
throw new ArgumentException("ID inválido!");
DataSetLisier.ImageRow img = GetRowByID(ID);
if (img == null)
throw new ArgumentException("imagem não cadastrada!");
UploaderServiceFactory.GetInstance().GetUploader().Delete(img.nome);
int linhas = DAO.Delete(ID);
if (linhas != 1)
throw new OperationCanceledException("Erro excluindo imagem!");
}
protected void ValidaDados(string nome, DateTime data, string comentario)
{
if (string.IsNullOrEmpty(nome))
throw new ArgumentException("nome de arquivo inválido!");
}
[System.ComponentModel.DataObjectMethod(DataObjectMethodType.Insert, true)]
public void Insert(string nome, DateTime data, string comentario)
{
ValidaDados(nome, data, comentario);
int linhas = DAO.Insert(nome, data, comentario);
if (linhas != 1)
throw new OperationCanceledException("Erro inserindo imagem!");
}
[System.ComponentModel.DataObjectMethod(DataObjectMethodType.Insert, false)]
public void Insert(DataSetLisier.ImageRow row)
{
if (row == null)
throw new OperationCanceledException("Imagem não pode ser nula!");
Insert(row.nome, row.data, row.comentario);
}
[System.ComponentModel.DataObjectMethod(DataObjectMethodType.Update, true)]
public void Update(string nome, DateTime data, string comentario, int ID)
{
// Regra de negocio: nome e data da imagem
// nao podem ser alterados pela interface!
DataSetLisier.ImageRow row = GetRowByID(ID);
if (row == null)
throw new OperationCanceledException("Imagem não cadastrada!");
int linhas = DAO.Update(row.nome, row.data, comentario, ID);
if (linhas != 1)
throw new OperationCanceledException("Erro alterando imagem!");
}
[System.ComponentModel.DataObjectMethod(DataObjectMethodType.Update, false)]
public void Update(DataSetLisier.ImageRow row)
{
if (row == null)
throw new OperationCanceledException("A imagem não pode ser nula!");
ValidaDados(row.nome, row.data, row.comentario);
int linhas = DAO.Update(row);
if (linhas != 1)
throw new OperationCanceledException("Erro alterando imagem!");
}
}
}

Vale resaltar novamente que a classe de serviço descrita acima deve ser encapsulada em um assembly (dll) a parte, sendo plenamente compatível com aplicações Windows Forms, o que demonstra a separação em camadas. Desta, pode-se evoluir o mock inicial para a pequena aplicação abaixo.



Onde novo delegate do botão "Enviar" é apresentado abaixo, onde fica ainda mais evidente a separação em camadas entre as lógicas de apresentação, negócios e persistência:


protected void btnEnviar_Click(object sender, EventArgs e)
{
new ManterImagesService().Upload(FileUploader1, txtComentario.Text);
ImageGridView.DataBind();
}

sábado, 29 de março de 2008

[ADO.NET] Estendendo ADO.NET: Parte 1- Projeto dos provedores de persistência.

Alguns dos aspectos criticados por alguns colegas na API ADO.NET seriam: (a) a dependência sintática do SQL e (b) a arquitetura orientada a dados no modelo de referência sugerido pela Microsoft para a camada de persistência. Uma vez que a dependência sintática do SQL é plenamente suprimida pela API LINQ do framework 3.5, pensei em escrever alguns artigos neste escopo de adaptação dos componentes da ADO.NET. O modelo de referência citado obtem grande produtividade pela modelagem visual do componente DataSet “tipado”. No entanto, os objetos definidos no componente DataSet para representação dos objetos de transferência (DataTable) e serviços (TableDataAdapter) são selados (i.e. selead), não permitindo sua herança, ou mesmo composição de tipos por referência.

Desta forma, apresentações alternativas e relatórios podem eventualmente exigir a definição de Value Objects (VO) auxiliares para transporte vertical de dados à camada de apresentação. Tal estrutura condiciona fortemente as camadas superiores ao modelo de entidades, definindo um grande esforço de refatoração no caso da mudança das definições da base de dados, ou na extensão de relatórios.

Neste escopo, um primeiro ponto de interessante seria a possibilidade da referência por reflection/remoting aos service factories de persistência, de forma tal que extensões em assemblies distintos possam ser adaptados em tempo de execução. Tal feature teria alto valor agregado em sistemas de alta disponibilidade com baixa resistência a mudança.


A arquitetura proposta é apresentada na figura ao lado, onde o seu principal componente consiste da classe DaoProvider, projetada conforme o design pattern provider. Tal classe possui a responsabilidade de gerenciar os service factories concretos para os objetos de persistência.

Para tanto, tal classe possui uma composição de objetos DaoProviderSettings (estrutura que representa a configuração para um service factory concreto dos objetos de persistência), onde estes objetos podem ser acessados de forma aleatória, adicionados na lista de provedores ou interpretados a partir de uma string de conexão fornecida.


A tabela ao lado apresenta descrição da interface da classe DaoProviderSettings proposta, que consiste de uma extensão da classe System.Configurations.ConfigurationSection. A classe pai define a associação entre um identificador textual único (“Name”), a string de conexão (“ConnectionString”) e a classe do provedor de acesso ao banco de dados (“ProviderName”), responsável por implementar a interface de tratamento das queries SQL sobre a string de conexão. Na classe estendida, proposta neste estudo, são adicionadas a existência de uma classe proxy para o provider (“ProxyName”), que implementa de forma concreta um mapeamento objeto relacional. Tal proxy pode ainda ser definido no contexto da aplicação ou em um assembly externo (DLL), neste caso, sendo referenciado pelo atributo “ProxyAssemblyName”. Desta forma, a extensão provê uma interface consistente para atendimento do requisito de referência por reflection aoa service factories de persistência.

Os parâmetros dos provedores de persistência da plataforma proposta são definidos no contexto da aplicação pelos arquivos padrões de configuração das APIs da plataforma.NET. (arquivo web.config em contexto ASP.NET e app.config em contexto Windows Forms). A listagem abaixo ilustra a marcação do arquivo web.config de uma aplicação web implementando o modelo proposto. Observa-se a sintaxe padrão para a definição de strings de conexão sobre o nó . A string de conexão de nome “Prevalente (default)” não apresenta um atributo “providerName” conforme definido no padrão ADO.NET. Neste caso não é necessário, pois será empregado um proxy (provedor concreto de serviços de persistência) da plataforma proposta. As marcações relativas aos atributos “ProxyName” e “ProxyAssemblyName” são definidos sobre o atributo “ConnectionString”. A marcação “Default” indica qual o provedor corrente.

<connectionstrings>
<add connectionstring="ProxyName=net.jorgealbuquerque.pedweb.Dao.ConcreateFactories.XMLDaoFactoryProxy; Data Source=data.xml; ProxyAssemblyName=prevalentDao.dll; Default; " name="Prevalente (default)" providername="">

<add connectionstring="Integrated Security=SSPI; Persist Security Info=False;Initial Catalog=pedweb; User ID=sa; password=; Data Source=.\SQLSERVER;" name="Microsoft SQL Server 2000" providername="System.Data.SqlClient">

</connectionstrings>


Desta forma, a classe DaoProvider, realiza a leitura dos arquivos de configuração das APIs da plataforma .NET, interpreta seus parâmetros e mantêm referências aos seus provedores, que podem ser livremente criados, alterados ou removidos em tempo de execução e instanciados de forma estática.


A Tabela ao lado apresenta uma descrição resumida da interface da classe DaoProvider. A interpretação dos arquivos de configuração o ocorre durante a chamada do método GetInstance(). O carregamento somente é definido se a própria classe não contiver uma instância desta (design pattern singleton). Neste contexto, caso não exista uma instância, o método GetInstance() chama o método privado FillFromConfigManager() que, por sua vez, delega a leitura dos arquivos de configuração a classe nativa da plataforma .NET System.Configuration.ConfigurationManager (propriedade ConnectionStrings). Uma vez obtidas as strings de conexão (definidas nos arquivos web.config ou app.config), a responsabilidade da interpretação das mesmas é delegada a classe DaoProviderSettingsParse (método Parse), retornando os objetos DaoProviderSettings, que são adicionados a lista de provedores existentes (método Add). A seqüência de mensagens descritas é representada em notação UML na figura abaixo.

Nos próximos posts pretendo implementar estas primeiras classes de serviço e a seguir projetar o serviço de prevalência propriamente dito...

domingo, 23 de março de 2008

[Pessoal] Comer Sozinho

Minha mulher encontrou essa pérola no google e me mostrou! Eu não me lembrava! Foi escrito as 3:46 am num site obscuro. Claro que foi antes de efetivamente ficarmos juntos... Testemunho de uma época em que eu não estava satisfeito com a minha vida, e antes de mais nada por não estar com ela! Te amo, minha loira!

Adoro comer só
Máxima liberdade do egoísmo
Comer o que quiser... Quanto quiser... Como quiser...
O cardápio é seu, assim como o tempo
Um mini-mundo ao seu controle
E, se você voltar mais de uma vez
Juro que param de olhar...

"Aceita o último pedaço?"
O outro sempre aceita
Mesmo quando você queria
A voz de sua mãe espreita

Colegas de trabalho são os piores
Vinte pessoas num mesa (Sempre uma churascaria)
Jung diz que aproxima o grupo
Max diz que mistura a conta

Ao invés, almoço sushi
Self-service no supermecado
Badejinha de isopor, tudo em 20 segundos
Emagrece e não enche o saco

Odeio lavar pratos
Sempre janto na tupperware
Abro a tampa,
ligo o microondas
e pego um garfo.
Sempre na frente da TV
com 2 litros coca-cola (recentemente coca-zero)
E sem copos

A pizza, então, é perfeita
Sempre sobram ao menos 4 pedaços
Que ficam muito melhores frios
Perfeitos para o café
Perfeitos na noite seguinte

Mas o destino é irreverente
Estou em vias de casar
E adoro cozinhar para ela
Assisto ao Oliver (o ingles) e ao Olivier (o frances)

Me divirto muito preparando,
comendo a dois
e até lavando...

sexta-feira, 7 de março de 2008

[Livro] Muito longe de casa


Na contra-capa deste livro tem uma crítica da Newsweek: "É necessário ler apenas 20 páginas de 'Muito Longe de Casa' para afirmar que, se fosse ficção, o jovem autor deveria ganhar um prêmio Nobel". Parece uma chamada exagerada. Mas não é!

Essa é a auto-biografia de Ishmael Beah, contanto sua infância e adolescência em Serra Leoa em meio a uma guerra civil. O menino perde a família, passa a fugir para sobreviver ao conflito até ser recrutado como soldado pelo exercíto. A linguagem é simples, direta e tocantemente honesta. O livro foge a todos os lugares comuns. Ishmael não descreve em detalhes as atrocidades que comete como soldado, apenas em flashes (evidentemente pela vergonha e por querer esquecer), preferindo se ater a fuga e ao seu processo de recuperação. Mesmo assim, os flashs e os relatos de sobrevivência dramática deixam claro o tipo de violência praticada no interior de Serra Leoa de 1991 a 2002. A tática geral consiste em recrutar crianças, e tomar suprimentos de pequenas aldeiras para sustentar continuo conflito entre exército oficial e rebeldes (RUF). O detalhe é que em vez de saquear o necessário, a orientação é sempre torturar e exterminar com crueldade a todos os habitantes da aldeia (aparentemente na lógica corrente, massacrar com crueldade por nada demonstra força, sendo uma postura desejável). Não é preciso imaginar o custo humano dessa política em um povo já miserável e sem recursos.

Um detalhe que me chamou atenção é a discrepância entre a longevidade das pessoas e das armas. Os meninos soldados são extremamente descartáveis, mas as armas são sempre capturadas e postas novamente em circulação, nunca sendo destruídas.

Coincidência ao não, assim que acabei o livro liguei a TV e estava começando naquele instante o Senhor da Guerra no telecine!!! Embora o filme nem de longe sonhe em arranhar a profundidade do livro, completa o cenário discutindo o lado de quem ganha com tudo isso (apresenta a vida de um traficante de armas para Serra Leoa nesta mesma guerra civil). Ver o filme logo após ler o livro foi muito forte, porque remetia constantemente aos episódios do último, tornando-os ainda mais sinistros. O conflito de Serra Leoa ainda inspirou o filme Diamante de Sangue, que também é um conto da carochinha perto do livro. Acho que o filme que melhor captura a atmosfera desse livro (e do problema) é o Hotel Ruanda, embora não trate da questão das crianças propriamente dita (e também seja mais ameno). Em comum, o filme e o livro ilustram bem a perversidade humana, e que na falta de interesse, ninguem interfere na matança (em verdade, o presidente eleito somente tomou posse em Serra Leoa com a ajuda do exércido do Congo, aprovado pela ONU, mas essa ajuda demorou 10 anos).

Muito provavelmente, as únicas publicações comparáveis que tenha entrado em contado foram as graphic novels Gen (talvez a melhor de todos os tempos) e Maus, respectivamente, auto-biografias de um sobrevivente de Hiroshima e do Holocausto. Todos são impedíveis e as pessoas deveriam ler esses livros em alguma ocasição.

Para se pensar! E como disse Ghandi: "a civilização é uma boa idéia".

Apenas no enigma do macaco eu não vi graça: percebi a solução imediatamente!! :)

quinta-feira, 6 de março de 2008

[Java] Desenvolvimento declarativo orientado a anotações sobre JSF/Spring/JPA

A arquitetura definida sobre a pilha JSF/Spring/JPA apresenta um altenativa interessante para gestão da configuração de aplicações web, permitindo produtividade pelo uso extensivo de programação declarativa, desacoplamento entre camadas e independencia da persistencia/visualização. Neste contexto, é proposto um projeto "mock", demonstrando a integração JSF/Spring/JPA fortemente orientada a "annotations". A proposta consiste em somente definir por xml os elementos basicos da infra-estrutura descrita tais como datasource, entityManagerFactory e transactionManager (utiliza-se MyFaces 1.2.2 com Facelets, Spring 2.5 e JPA sobre Hibernate).

A aplicação consiste de um "Xavecometro", destinado a cadasto de "xavecos" (contatos do sexo oposto). Neste contexto, existem duas categorias de "xavecos": "tchutchucas" e "bagangas". (As colegas nao se ofendam! É uma grande piada!)

Classe Xaveco

package net.jorgealbuquerque.tutorial.xavecometro.entidades;

//imports not displayed

@Entity
public class Xaveco implements Serializable{

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

@Basic
private String nome;

@Basic
private Integer telefone;

@Enumerated
private Categoria categoria;

public Xaveco() {}

public Xaveco(String nome, Integer telefone) {
this.nome = nome;
this.telefone = telefone;
}

public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}

public Integer getTelefone() {
return telefone;
}
public void setTelefone(Integer telefone) {
this.telefone = telefone;
}

public String getNome() {
return nome;
}
public void setNome(String nome) {
this.nome = nome;
}

public Categoria getCategoria() {
return categoria;
}
public void setCategoria(Categoria categoria) {
this.categoria = categoria;
}
}


Para a DAO, é definida uma interface generica para os operadores CRUD (IDAO)

Interface IDAO

package net.jorgealbuquerque.tutorial.xavecometro.dao;

//imports not displayed

public interface IDAO {

T getById(ID id);
void persist(T entity);
void update(T entity);
void delete(T entity);
List getAll();
}


Seguindo o contrato IDAO, apresenta-se a implementação com JPA. Note que como a entityManager é instanciada. Ao inves de exterder a JPATemplate com a anotação @PersistenceContext, a entityManager é resolvida pelo Spring.

Classe AbstractJPADAO

package net.jorgealbuquerque.tutorial.xavecometro.dao;

//imports are not displayed

public abstract class AbstractJPADAO implements IDAO {

private Class persistentClass;

protected EntityManager entityManager;

@SuppressWarnings("unchecked")
public AbstractJPADAO() {
this.persistentClass = (Class) ((ParameterizedType) getClass().
getGenericSuperclass()).getActualTypeArguments()[0];
}

@PersistenceContext
public void setEntityManager(EntityManager entityManager) {
this.entityManager = entityManager;
}

public Class getPersistentClass() {
return persistentClass;
}

public T getById(ID id) {
return entityManager.find(persistentClass, id);
}

public void persist(T entity) {
entityManager.persist(entity);
}

public void update(T entity) {
entityManager.merge(entity);
}

public void delete(T entity) {
entityManager.remove(entity);
}

@SuppressWarnings("unchecked")
public List loadAll() {
return entityManager.createQuery("Select t from " +
persistentClass.getSimpleName() + " t").getResultList();
}
}


A interface IXavecoDAO apresenta as especializacoes (operações especificas no database) sobre a IDAO

Interface IXavecoDAO

package net.jorgealbuquerque.tutorial.xavecometro.dao;

//imports not displayed

public interface IXavecoDAO extends IDAO{

public List getByNome(String nome);
}



Finalmente, a classe XavecoDAO recebe a anotação @Repository, desta forma o Spring pode gerenciar seu comportamento, também permitidindo ExceptionTranslation (PersistenceAnnotationBeanPostProcessor deve ser configurado no applicationContext.xml).

classe XavecoDAO

package net.jorgealbuquerque.tutorial.xavecometro.dao;

//imports not displayed

@Repository
public class XavecoDAO extends AbstractJPADAO implements IXavecoDAO{

@SuppressWarnings("unchecked")
public List getByNome(String nome) {
Query query = entityManager.createQuery("Select m from Xaveco m where m.nome = ?1");
query.setParameter(1, nome);
return query.getResultList();
}

}


A camada de negócios apresente a ServiceBean (XavecoService) e sua interface (IXavecoService). Note a anotação @Transactional, desta forma as transações tambem são configuradas por anotações (É necessária a marcação em applicationContext.xml)

Interface IXavecoService

package net.jorgealbuquerque.tutorial.xavecometro.service;

//imports not displayed

public interface IXavecoService {

@Transactional
public void createNew(Xaveco xaveco);
public List getAll();
public List getByNome(String nome);
}


Na classe Classe XavecoService, a anotação @Service transforma a classe em uma spring bean, e a classe XavecoDAO é injetada por referência utilizando @AutoWired

Classe XavecoService

package net.jorgealbuquerque.tutorial.xavecometro.service;

//imports not displayed

@Service
public class XavecoService implements IXavecoService{

private XavecoDAO xavecoDAO;

@Autowired
public XavecoService(XavecoDAO xavecoDAO) {
this.xavecoDAO = xavecoDAO;
}

public void createNew(Xaveco xaveco) {
xavecoDAO.persist(xaveco);
}

public List getAll() {
return xavecoDAO.getAll();
}

public List getByNome(String nome) {
return xavecoDAO.getByNome(nome);
}
}


Finalmente, Beans JSF e Facelets são utilizados para criacao de novos xavecos no sistema.

createXaveco.xhtml




A classe CreateXaveco tem a responsabildiade de manipular a página acima, sendo gerenciado pelo Spring. A anotação @Component refencia a spring bean com o nome "createXaveco". Nas views JSF, este nome é resolvido com expressões do tipo:
#{createXaveco.Xaveco.nome}. A anotação @Scope é utilizada para definir o request, separando escopos customizados.

Classe CreateXaveco

package net.jorgealbuquerque.tutorial.xavecometro.view;

//imports not displayed

@Component("createXaveco")
@Scope("request")
public class CreateXaveco implements Serializable{

private XavecoService xavecoService;
private Xaveco xaveco = new Xaveco();

@Autowired
public CreateXaveco(XavecoService xavecoService) {
this.xavecoService = xavecoService;
}

public Xaveco getXaveco() {
return xaveco;
}
public void setXaveco(Xaveco xaveco) {
this.xaveco = xaveco;
}

public String save() {
xavecoService.createNew(xaveco);
FacesMessage facesMessage = new FacesMessage(
FacesMessage.SEVERITY_INFO, "O Xaveco foi salvo com sucesso!",
"OK");
FacesContext.getCurrentInstance().addMessage(null, facesMessage);
xaveco = new Xaveco();
return null;
}
}


Arquivos XML:

Arquivo applicationContext.xml




Notas sobre o spring config;

  • habilita a busca anotações nos subpacotes de xavecometro.

  • habilita a gestao de transacoes orientada a annotations



Arquivo persistence.xml

net.jorgealbuquerque.tutorial.xavecometro.entidades



Arquivo faces-config.xml

com.sun.facelets.FaceletViewHandler
org.springframework.web.jsf.DelegatingVariableResolver


Arquivo web.xml

javax.faces.STATE_SAVING_METHOD
client

javax.faces.DEFAULT_SUFFIX
.xhtml

contextConfigLocation
classpath:applicationContext.xml

org.springframework.web.context.ContextLoaderListener
org.springframework.web.context.request.RequestContextListener

JPA Filter
org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter

JPA Filter
*.jsf

Faces Servlet
javax.faces.webapp.FacesServlet
1

Faces Servlet
*.jsf


Adicionalmente, foi incluida uma classe de teste de integraçào para a XavecoDAO (TDD).

Classe XavecoDAOTest

package net.jorgealbuquerque.tutorial.xavecometro.dao;

import java.util.List;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.TransactionConfiguration;
import org.springframework.transaction.annotation.Transactional;

import net.jorgealbuquerque.tutorial.xavecometro.entidades;

import static org.junit.Assert.*;

@RunWith(SpringJUnit4ClassRunner.class)
@Transactional
@TransactionConfiguration(transactionManager="txManager")
@ContextConfiguration(locations={"/applicationContext.xml"})
public class XavecoDAOTest{

private XavecoDAO xavecoDAO;

@Autowired
public void setRepository(XavecoDAO xavecoDAO) {
this.xavecoDAO = xavecoDAO;
}

@Test
public void shouldPersistNewXaveco() {
Xaveco xaveco = new Xaveco();
xaveco.setNome("Robertinha Fireball");
xaveco.setTelefone(new Integer(2342343));
xavecoDAO.persist(xaveco);
assertNotNull(xaveco.getId());
}

@Test
public void shouldFindBynome() {
Xaveco xaveco = new Xaveco();
xaveco.setNome("Renatinha Raio Laser");
xaveco.setTelefone(new Integer(7452354));
xavecoDAO.persist(xaveco);
assertNotNull(xaveco.getId());
List results = xavecoDAO.findBynome("Renatinha Raio Laser");
assertEquals(1, results.size());
assertEquals("Renatinha Raio Laser", results.get(0).getNome());
}

@Test
public void shouldReadAllXavecos() {
Xaveco xaveco1 = new Xaveco();
xaveco1.setNome("Pathy Tissunami");
xaveco1.setTelefone(new Integer(86452343));
Xaveco xaveco2 = new Xaveco();
xaveco2.setNome("Rafinha Quebra-Barraco");
xaveco2.setTelefone(new Integer(6734565));
xavecoDAO.persist(xaveco1);
assertNotNull(xaveco1.getId());

xavecoDAO.persist(xaveco2);
assertNotNull(xaveco2.getId());
List results = xavecoDAO.loadAll();
assertEquals(2, results.size());
}
}


Desta forma, pelo estudo proposto, concluí-se que a orientação a anotações para a pilha JSF/Spring/JPA permitinde, delegando a responsabilidade de ligação das camadas ao Spring.

terça-feira, 4 de março de 2008

[.NET] Videos ASP.NET 3.5

Como parte integrante do lançamento da IDE VS 2008, a API ASP.NET 3.5 integra nativamente Ajax e novos componentes de GUI e processamento de dados. Vale a pena conferir os video-tutoriais em http://asp.net/learn/3.5-videos/