Universidade Federal de Pernambuco – UFPE
Centro de Informática - CIn
Tópicos Avançados em
Engenharia de Software
Utilizando técnicas de
compilação condicional e AOM
Dupla:
Caio César Sabino Silva (ccss2@cin.ufpe.br)
Lais Sousa de Andrade (lsa@cin.ufpe.br)
Professor: Paulo Borba (phmb@cin.ufpe.br)
Sumário
1. Introdução
2. Resolvendo problemas encontrados no projeto
2.1. Problema 1: Imutabilidade da definição de Publicações
2.1.1. Situação anterior
2.1.2. Valores observados
2.1.3. Princípios utilizados
2.1.4. Refatorando o código
2.1.4.1. PropriedadePublicacao.java
2.1.4.2. Publicacao.java
2.1.4.3. TipoPropriedade.java
2.1.4.4. TipoPublicacao.java
2.1.4.5. publicacao-aom.xml
2.1.4.6. AOMHelper.java
2.1.5. Situação posterior
2.1.5.1 TreeMap
2.1.5.2. Polymetric View
2.1.5.3. Páginas do sistema
2.2. Problema 2: Imutabilidade no povoamento do banco de dados
2.2.1. Situação anterior
2.2.1.1. GerarBanco.java
2.2.1.2. TreeMap
2.2.2. Valores observados
2.2.3. Princípios utilizados
2.2.4. Refatorando o código
2.2.4.1. membros.xml
2.2.4.2. publicacoes.xml
2.2.4.3. noticias.xml
2.2.4.4. linhaspesquisa.xml
2.2.4.5. ReflectionXmlGerador.java
2.2.4.6. AOMXmlGenerator.java
2.2.4.7. GerarBanco.java
2.2.5. Situação posterior
3. Adicionando nova funcionalidade
3.1. Funcionalidade escolhida
3.2. Implementação da funcionalidade
3.2.1. Formato do arquivo BibTex
3.2.1.1. Exemplo de entrada num arquivo .bib
3.2.2. Acrescentando a funcionalidade
3.2.2.1. BibTexAspect.aj
3.2.2.2. AdicionarPublicacoesFromBibTexServlet.java
3.2.2.3. adicionarpublicacao.jsp
3.2.2.4. web.xml
4.1. Impactos das alterações
4.2. Adaptando o mecanismo de validação
4.2.1. Situação anterior
4.2.2. Otimizando a solução
4.2.2.1. Novo trecho do publicacao-aom.xml
4.2.2.2. IValidationRule
4.2.2.3. Exemplo de RuleObject - RangeLengthRule
4.2.2.4. AbstractBusinessEntity.validate.properties
4.2.2.5. AbstractBusinessEntity.validation.xml
4.2.2.6. XMLValidationUtil.java
4.2.2.7. Alteração no AOMHelper
1. Introdução
Nesta atividade, alguns problemas de modularidade e reuso de código tentarão ser
solucionados, utilizando para isso as técnicas vistas nas últimas aulas, destacando
principalmente compilação condicional e AOM.
2. Resolvendo problemas encontrados no projeto
Alguns problemas de modularidade e reuso de código foram encontrados e
solucionados na atividade anterior. Algumas soluções ainda podem ser melhoradas e alguns
problemas menores ainda restam no sistema.
Nas próximas subseções, um problema encontrado no código será identificado e
solucionado utilizando novas técnicas de refatoramento como compilação condicional, AOM ou
técnicas já vistas anteriormente.
2.1. Problema 1: Imutabilidade da definição de Publicações
No projeto, temos a classe Publicacao e alguns subtipos da mesma. Cada tipo de
publicação no projeto tem um conjunto fixo de atributos, o que torna tais classes rígidas, sem
capacidade de alterar sua definição. O maior problema com esta rigidez está no fato de o
tratamento de publicações específicas no sistema exige código de verificação e manipulação
específico para cada um de seus subtipos, tornando o código maior e mais complexo.
2.1.1. Situação anterior
Mostraremos o código com a definição de uma Publicacao do sistema.
public abstract class Publicacao extends AbstractBusinessEntity implements Comparable<Publicacao>{
public final static String CONFERENCIA = "Artigo em Conferência";
public final static String PERIODICO = "Artigo em Periódicos e Revistas"; public final static String POSGRADUACAO = "Pós-Graduação";
protected String tipo;
protected List<Membro> autores;
protected List<String> autoresNaoMembros;
protected String titulo;
protected String ano;
protected byte[] pdf;
// Getters and setters...
}
public class AdicionarPublicacaoServlet extends PublicacaoModelMapperServlet {
@Override
protected Publicacao getObject() { Publicacao ret = null;
String tipo = (String) this.getFormfields().get("tipo"); String nivel = (String) this.getFormfields().get("nivel");
if(tipo.equals("Artigo em Conferência")){ ret = new ArtigoConferencia(); }
if(tipo.equals("Artigo em Periódicos e Revistas")){ ret = new ArtigoPeriodico();
}
if(tipo.equals("Pós-Graduação")){ if(nivel.equals("Mestrado")){
ret = new PublicacaoPosGraduacao();
((PublicacaoPosGraduacao) ret).setNivel(Nivel.MESTRADO);
}
if(nivel.equals("Doutorado")){
ret = new PublicacaoPosGraduacao();
((PublicacaoPosGraduacao) ret).setNivel(Nivel.DOUTORADO);
}
}
return ret; }
... }
2.1.2. Valores observados
Analisando o código de Publicacao e seus subtipos, percebe-se que o mesmo é:
●
Rígido: cada classe de Publicacao tem um número fixo de atributos e toda instância
tem o mesmo esqueleto. Para a inserção de um novo tipo de Publicacao, é necessário
modificar todo código que trate dos subtipos da mesma, em vários lugares, deixando
esta modificação mais difícil.
●
Não tão flexível: embora haja uma superclasse aberta a extensão, em caso de
adição de um novo tipo de publicação, é preciso inserir uma nova classe no ambiente
de execução, o que não é tão prático e exige parada na execução do servidor e
re-compilação do código.
●
Específico:
sobre cada subtipo da classe Publicacao, o código que manipula cada
subclasse específica está bastante preso à definição da mesma.
●
Frágil: alterações na definição de uma publicação afeta vários lugares do sistema, que
podem ser facilmente quebrados com a modificação de um subtipo da mesma.
2.1.3. Princípios utilizados
a
minimização de repetições em vários lugares que tratam com cada tipo específico de
Publicacao.
2.1.4. Refatorando o código
Utilizando a técnica de AOM (Adaptive Object-Model), pode-se definir uma estrutura de
uma publicação que pode ser adaptável a um modelo definido num arquivo XML. A princípio,
pensou-se em fazer isso para todas as entidades do sistema, mas se verificou que o código se
tornaria excessivamente complexo (como o artigo de
Yoder and Johnson
alerta, há dificuldades
de implementação de AOM quando suas entidades precisam ser persistidas). Dessa forma o
que vamos fazer a seguir visa conciliar o custo de complexidade do refatoramento com os
benefícios que podem vir do uso da técnica de AOM.
A princípio, verificou-se atributos obrigatórios de uma publicação e verificou-se
que
“titulo” (usado inclusive como chave no BibTex),
“ano”,
“pdf”,
“autores” e
“tipo” são
fixos em publicação (na especificação dos requisitos, vemos que são obrigatórios em quaisquer
tipos de publicação). Além disso, eles são necessários na implementação do Dao feita como
atributos para ordenação, e por isso devem ser tratados como colunas da tabela do banco de
dados. Por isso decidiu-se tratá-los como atributos fixos da classe
Publicacao
.
Observando que os demais atributos podem ser diversos, dependendo do tipo de
Publicação, e utilizando o padrão Property para poder definir adaptar os modelos dos tipos de
publicação em relação a seus atributos, foi criada a classe
PropriedadePublicacao
:
2.1.4.1. PropriedadePublicacao.java
@Entity
public class PropriedadePublicacao {
@Id
@GeneratedValue private Long id;
@Basic
private String nome;
@Lob
private byte[] valor;
public PropriedadePublicacao(String nome, Serializable valor) { this.setNome(nome);
this.setValor(valor); }
public PropriedadePublicacao() {}
public Object getValor() { Object obj = null;
if(this.valor != null){
obj = SerializationUtils.deserialize(this.valor); }
return obj; }
this.valor = SerializationUtils.serialize(valor); }
// Getters e setters... }
Perceba que há um grande inconveniente que se refere a como o valor da propriedade é
representado no banco de dados. Utilizando objetos serializáveis, foram definidos métodos que
abstraem como o valor serializado é de fato armazenado no banco de dados (getValor e
setValor).
Após isso, os subtipos de Publicacao deixaram de existir e agora Publicacao é definida
dessa forma:
2.1.4.2. Publicacao.java
@Entity
@Table(uniqueConstraints = {@UniqueConstraint(columnNames={"titulo"})})
public class Publicacao extends AbstractBusinessEntity implements Comparable<Publicacao>{
private String titulo;
private String tipo;
private List<Membro> autores;
private List<String> autoresNaoMembros;
private String ano;
private byte[] pdf;
private List<PropriedadePublicacao> propriedades;
// ...
}
Além disso, foram criadas a classe
TipoPublicacao
, em conformidade ao padrão
TypeObject para permitir a definição de tipos de publicação que representa um tipo de
publicação (artigo de conferência, trabalho de pós-graduação, ...), guardando a lista de
propriedades possíveis que cada publicação desse tipo pode ou deve ter, e a classe
TipoPropriedade
, que guarda características de uma propriedade específica de um tipo de
Publicação, como qual o tipo de Java que representa um valor dessa propriedade, qual o valor
da propriedade de texto que ela deve buscar no arquivo de linguagens para mostrar o nome
desse campo.
2.1.4.3. TipoPropriedade.java
package br.ufpe.cin.rgms.publicacao.modelo;
public class TipoPropriedade {
private String tipo;
private boolean optional;
public TipoPropriedade(String tipo, String text, boolean optional){ this.setTipo(tipo);
this.setText(text);
this.setOptional(optional); }
2.1.4.4. TipoPublicacao.java
package br.ufpe.cin.rgms.publicacao.modelo;
public class TipoPublicacao {
private String type;
private String text;
private HashMap<String, TipoPropriedade> propertyTypes;
public TipoPublicacao(String type, String text) { this.setType(type);
this.setText(text);
this.setPropertyTypes(new HashMap<String, TipoPropriedade>()); }
public void addPropertyType(String propName, TipoPropriedade propType){ this.propertyTypes.put(propName, propType);
}
public TipoPropriedade getPropertyType(String propName){ return this.propertyTypes.get(propName);
}
// Getters e setters... }
Perceba que as relações em linhas horizontais não são implementadas de maneira
direta (Publicação guarda apenas uma String que é a identificação do tipo de publicação e não
há ligação direta entre PropriedadePublicacao e TipoPublicacao, ambos apenas possuem
mesmo atributo nome). Isso se deve ao fato de que apenas Publicacao e
PropriedadePublicacao são persistidas no banco de dados para efeito de simplicidade e
minimizar o custo do refatoramento (caso contrário, precisaria ser definido um DAO para essas
duas outras entidades).
Precisou-se após isso definir tipos de publicação que se adaptam a um modelo definido
num arquivo. Para evitar complicações de definir num banco de dados os meta-dados e
precisar estender os serviços do Dao atuais, as classes que representam um tipo de publicação
e um tipo de propriedade não serão persistidas e serão associadas a um arquivo de XML
chamado publicacao-aom.xml. O seu conteúdo segue essa estrutura:
2.1.4.5. publicacao-aom.xml
<?xml version="1.0" encoding="ISO-8859-1"?>
<aom xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="file:publicacao-aom.xsd">
<publicacao-types> <publicacao-type>
<text-option>Artigo em Conferência</text-option>
<optional-attributes> <attribute>
<name>pages</name>
<class>java.lang.String</class> <text>paginas</text>
</attribute> </optional-attributes> <required-attributes>
<attribute>
<name>booktitle</name>
<class>java.lang.String</class> <text>conferencia</text>
</attribute> </required-attributes> </publicacao-type>
... <publicacao-types> </aom>
Temos a classe AOMHelper, de acordo com os padrões Helper e Singleton, que lida
com a manipulação das definições de Publicacao existentes no XML e é usada pelo sistema
para saber quais tipos de publicação são suportados pela aplicação no momento atual e quais
atributos cada tipo utiliza. Ela utiliza outra classe auxiliar
XMLHelper também seguindo o
mesmo pattern.
2.1.4.6. AOMHelper.java
public class AOMHelper {
private static AOMHelper instance;
private Hashtable<String, TipoPublicacao> map = new Hashtable<String, TipoPublicacao>();
static{
AOMHelper.instance = new AOMHelper(); }
public static AOMHelper getInstance() { return instance;
}
public AOMHelper() { this.readAOM(); }
public TipoPublicacao getTipoPublicacao(String tipo){ return this.map.get(tipo);
}
public Set<String> getTipos(){ return this.map.keySet(); }
public void readAOM(){
synchronized (this.map) { try{
this.map = new Hashtable<String, TipoPublicacao>(); XMLHelper helper = XMLHelper.getInstance();
Document xml = helper.getXML(
Collection<Element> typeElements = helper.getElements( xml.getDocumentElement(), "publicacao-type" );
for(Element type : typeElements){
String entry = helper.getContent(
helper.getMatchingElement(type, "entry-type")); String option = helper.getContent(
helper.getMatchingElement(type, "text-option")); TipoPublicacao tipo = new TipoPublicacao(entry, option);
for(Element attribute :
helper.getElements(helper.getMatchingElement(type, "optional-attributes"), "attribute")){ this.loadAttributes(tipo, attribute, true); }
for(Element attribute :
helper.getElements(helper.getMatchingElement(type, "required-attributes"), "attribute")){ this.loadAttributes(tipo, attribute, false); }
this.map.put(entry, tipo); }
}catch(SAXException e){
throw new IllegalStateException(); } catch (IOException e) {
throw new IllegalStateException(); } catch (ParserConfigurationException e) { throw new IllegalStateException(); }
} }
private void loadAttributes(TipoPublicacao tipo, Element attribute, boolean optional) { XMLHelper helper = XMLHelper.getInstance();
String name = helper.getContent(helper.getMatchingElement(attribute, "name")); String clazz = helper.getContent(helper.getMatchingElement(attribute, "class")); String text = helper.getContent(helper.getMatchingElement(attribute, "text"));
tipo.addPropertyType(name, new TipoPropriedade(clazz, text, optional)); }
}
Após as alterações, houve um problema: o mecanismo de validação definido
anteriormente obtia os atributos das publicações por Reflection e agora não é mais dessa forma
para os atributos variáveis dos tipos de publicação. Agora temos listas de atributos obrigatórios
e opcionais no xml, mas isso é bastante restrito. O mecanismo de validação foi estendido como
pode ser visto na seção de otimização de soluções.
Após isso, foi necessário alterar os jsps para que as páginas de inserir/alterar
publicação se adequem à definição dinâmica de um tipo de publicação, bem como os Servlets
que fazem tais operações.
2.1.5. Situação posterior
2.1.5.1 TreeMap
2.1.5.2. Polymetric View
2.1.5.3. Páginas do sistema
Trechos de código nos jsps onde se verificava os tipos de publicação e colocava seus
atributos foram alterados no alterarpublicacao.jsp e adicionarpublicacao.jsp, uma vez que no no
corpo do jsp, pode-se iterar sobre os atributos de cada tipo de publicação mostrando seus
campos:
<p> <%
AOMHelper helper = AOMHelper.getInstance();
for(PropriedadePublicacao t : publicacao.getPropriedades()){
%>
<LABEL for="<%= t.getNome() %>" id="label<%= t.getNome() %>"> <% out.println(Properties.getProperty(
this.getServletContext(),
helper.getTipoPublicacao(publicacao.getTipo()). getPropertyType(t.getNome()).getText()) );
%>
</LABEL>
<INPUT onkeypress="return noenter();" type="text" name="<%= t.getNome() %>" size="80" value="<% out.print(t.getValor().toString()); %>">
<%
}
%> </p>
Página de cadastro após alterações: uma complicação foi que podia haver campos de mesmo nomes entre tipos de publicação diferentes. Deve ser apresentado ao usuário um único campo e não múltiplas vezes. Isso foi tratado como
pode ser visto na screenshot.
Teste executado na inserção após refatoramento
2.2. Problema 2: Imutabilidade no povoamento do banco de dados
Atualmente o povoamento do banco de dados é feito pela classe
GerarBanco,
mais
especificamente com um único método, o
popularBanco()
que sozinho inicializa os dados e
os insere na base de dados. Esta geração de dados está presa ao código, exigindo uma
modificação toda vez que se quer modificar uma informação que será inserida, ou inserir mais
dados no gerador. Além de deixar a geração de dados presa ao código, o mesmo não está bem
modularizado, de modo que a classe tem um método muito grande, com quase todo o código
da classe.
2.2.1. Situação anterior
Aqui está a classe antes do refatoramento:
2.2.1.1. GerarBanco.java
package br.ufpe.cin.rgms.util;
public class GerarBanco {
public static void main(String[] args) {
Configuration conf = new AnnotationConfiguration(); conf.configure();
SchemaExport se = new SchemaExport(conf); se.create(true, true);
popularBanco(); }
private static void popularBanco() { try {
File file = new File("WebContent/images/login-page-bg.jpg"); byte[] foto = new byte[(int) file.length()];
FileInputStream reader = new FileInputStream(file); reader.read(foto);
Estudante felype = new Estudante("felype.ferreira@gmail.com","Felype","Santiago", "Estudante","Centro de Inform?tica","UFPE","8134395054",
"www.cin.ufpe.br/~fsf2","Olinda","Brasil","Ativo",null,foto, "Paulo Henrique Monteiro Borba","");
Estudante michelle = new Estudante("michelle.cms@gmail.com","Michelle","Silva", "Estudante","Centro de Inform?tica","UFPE","8131345911","www.cin.ufpe.br/~mcms", "Recife","Brasil", "Ativo",null,foto,"Paulo Henrique Monteiro Borba","");
felype.setSenha("senha"); michelle.setSenha("senha");
Facade.getInstance().inserirMembro(felype); Facade.getInstance().inserirMembro(michelle); List<Membro> membros = new ArrayList<Membro>();
naoMembro.add("Michelle");
Publicacao p1 = new Publicacao(membros,naoMembro, "Uma abordadegem de Teste", "2010", null, "conference"); p1.addProperty(new PropriedadePublicacao("pages", "112-117"));
p1.addProperty(new PropriedadePublicacao("month", "Maio"));
p1.addProperty(new PropriedadePublicacao("booktitle", "Artigo em Conferência"));
Facade.getInstance().inserirPublicacao(p1);
List<String> not = new ArrayList<String>(); not.add("Unknown");
p1 = new Publicacao(membros,not, "Linha de Produto de Software", "2012", null, "phdthesis"); p1.addProperty(new PropriedadePublicacao("month", "Março"));
p1.addProperty(new PropriedadePublicacao("school", "Pós-Graduação")); Facade.getInstance().inserirPublicacao(p1);
List<String> financiadores = new ArrayList<String>(); financiadores.add("Financiador 1");
financiadores.add("Financiador 2"); financiadores.add("Financiador 3");
List<String> linksRelacionados = new ArrayList<String>(); linksRelacionados.add("Link relacionado 1");
linksRelacionados.add("Link relacionado 2"); linksRelacionados.add("Link relacionado 3");
List<Publicacao> publicacoes = Facade.getInstance().getPublicacoes();
NoticiaCadastrada noticia = new NoticiaCadastrada("Lalalala",michelle,
Noticia.CADASTRADA, "jjhdjhfj"); Facade.getInstance().inserirNoticia(noticia);
NoticiaTwitter twi = new NoticiaTwitter(felype, Noticia.TWITTER,
"http://twitter.com/felypesantiago"); Facade.getInstance().inserirNoticia(twi);
Facade.getInstance().inserirLinhaPesquisa(new LinhaPesquisa("T?tulo","Breve
descri??o","Descri??o detalhada",financiadores,linksRelacionados,membros,publicacoes)); } catch (RGMSException e) {
// TODO Auto-generated catch block e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block e.printStackTrace();
} } }
2.2.1.2. TreeMap
2.2.2. Valores observados
Analisando o código de GerarBanco, temos as seguintes características do mesmo é:
●
Não tão flexível: a adição de um novo valor de entidade que já é inserida pelo gerador
exige a inserção de mais código, sem oportunidade para reuso de nenhuma inserção
que é feita
●
Rígido: difícil de ser modificado para inserir outros tipos de dados ou para realizar a
operação sobre outros dados.
●
Específico: código fixo, específico para a inserção de cada valor definido no mesmo.
2.2.3. Princípios utilizados
Para permitir o reuso do código, foram aplicados os seguintes principios:
●
Generalização de código: tornando o código que insere entidades no banco de dados
mais genérico e permitindo o reuso do mesmo.
da especificidade do que está sendo inserido em listas ou no próprio banco, que não
permite o uso de laços.
●
Extração de pontos de extensão: que permitam a modificação dos dados que serão
inseridos no banco, adicionado ou removendo entidades, sem modificações no código.
●
Open Closed Principle:
fazendo que qualquer modificação nos dados a serem
inseridos, seja na quantidade ou no conteúdo do mesmo, não exija uma modificação no
código de geração do banco.
2.2.4. Refatorando o código
Inicialmente foi feita a separação de dois concerns desta classe: o primeiro, a geração
dos dados a serem inseridos no banco de dados, e o segundo a inserção dos mesmos no
banco. Para isso, visando manter os princípios definidos acima, foram criados alguns xmls, um
para cada entidade básica do sistema, que definem valores que serão inseridos no banco de
dados. Cada xml tem basicamente a seguinte formação:
<xml>
<entity_set class=”className”> <entity_name id=”<id>”>
<attribute1_name>value</attribute1_name> <attribute2_name>value</attribute2_name> ...
<attributeK_name ref=”<id>” /> ...
<attributeN_name>value</attributeN_name> </entity_name>
... </entity_set> </xml>
Seguindo este formato, os dados inseridos pela classe
GerarBanco
tornaram-se os
seguintes xmls:
2.2.4.1. membros.xml
<xml>
<entity_set class="br.ufpe.cin.rgms.membro.modelo.Estudante"> <entity_name id="felype">
<email>felype.ferreira@gmail.com</email> <nome>Felype</nome>
<sobrenome>Santiago</sobrenome> <tipo>Estudante</tipo>
<departamento>Centro de Informática</departamento> <universidade>UFPE</universidade>
<telefone>8134395054</telefone>
<website>www.cin.ufpe.br/~fsf2</website> <cidade>Olinda</cidade>
<pais>Brasil</pais> <situacao>Ativo</situacao>
<foto>WebContent/images/login-page-bg.jpg</foto> <orientador>Paulo Henrique Monteiro Borba</orientador> <coOrientador>x</coOrientador>
</entity_name> <entity_name id="michelle"> <email>michelle.cms@gmail.com</email> <nome>Michelle</nome> <sobrenome>Silva</sobrenome> <tipo>Estudante</tipo>
<departamento>Centro de Informática</departamento> <universidade>UFPE</universidade> <telefone>8131345911</telefone> <website>www.cin.ufpe.br/~mcms</website> <cidade>Recife</cidade> <pais>Brasil</pais> <situacao>Ativo</situacao> <foto>WebContent/images/login-page-bg.jpg</foto> <orientador>Paulo Henrique Monteiro Borba</orientador> <coOrientador>x</coOrientador> <senha>senha</senha> <publicacoes> </publicacoes> </entity_name> </entity_set> </xml>
2.2.4.2. publicacoes.xml
<xml> <entity_set class="br.ufpe.cin.rgms.publicacao.modelo.Publicacao"> <entity_name id="p1" type="conference"><autores> <autor ref="felype"/> </autores> <autoresNaoMembros> <autor>Unknown</autor> </autoresNaoMembros>
<titulo>Uma abordadegem de Teste</titulo> <ano>2010</ano>
<pages>112-117</pages> <month>Maio</month>
<booktitle>Conferência X</booktitle> </entity_name>
<entity_name id="p2" type="phdthesis"> <autores> <autor ref="felype"/> </autores> <autoresNaoMembros> <autor>Unknown</autor> </autoresNaoMembros>
<tipo>Notícia Cadastrada</tipo> <titulo>jjhdjhfj</titulo> </entity_name>
</entity_set>
<entity_set class="br.ufpe.cin.rgms.noticia.modelo.NoticiaTwitter"> <entity_name id="n2">
<link>http://twitter.com/felypesantiago</link> <autor ref="felype"></autor>
<tipo>Notícia Importada do Twitter</tipo> </entity_name>
</entity_set> </xml>
2.2.4.4. linhaspesquisa.xml
<xml>
<entity_set class="br.ufpe.cin.rgms.linhasdepesquisa.modelo.LinhaPesquisa"> <entity_name id="l1">
<titulo>Uma abordadegem de Teste</titulo> <breveDescricao>Breve descrição</breveDescricao>
<descricaoDetalhada>Descrição detalhada</descricaoDetalhada> <financiadores>
<financiador>Financiador 1</financiador> <financiador>Financiador 2</financiador> <financiador>Financiador 3</financiador> </financiadores>
<linksRelacionados>
<linkRelacionado>Link relacionado 1</linkRelacionado> <linkRelacionado>Link relacionado 2</linkRelacionado> <linkRelacionado>Link relacionado 3</linkRelacionado> </linksRelacionados>
<membros>
<membro ref="felype"></membro> <membro ref="michelle"></membro> </membros>
<publicacoes>
<membro ref="p1"></membro> <membro ref="p2"></membro> </publicacoes>
</entity_name> </entity_set> </xml>
Todos eles foram inseridos em uma pasta chamada
schema
,
que fica no pacote
util
do projeto. Agora que os dados são descritos em arquivos .xml separados do código, qualquer
modificação nos dados não exige mais que o projeto seja recompilado, mas apenas que o
banco de dados seja gerado novamente com o mesmo código gerador. Este código agora se
encarrega apenas de ler estes arquivos .xml e criar as entidades, inserindo-as no banco.
Para isso foram definidas duas classes auxiliares:
2.2.4.5. ReflectionXmlGerador.java
public class ReflectionXmlGerador {
private Document document;
private XMLHelper helper;
IOException, ParserConfigurationException{ helper = XMLHelper.getInstance();
this.document = helper.getXML(this.getClass().getResourceAsStream(xml)); }
public Collection<Object> inserir(Hashtable<String, Object> map) throws ClassNotFoundException, InstantiationException, IllegalAccessException{
Collection<Object> objects = new ArrayList<Object>();
for(Element entitySet :
helper.getElements(document.getDocumentElement(), "entity_set")){ objects.addAll(this.handleEntitySet(entitySet,map));
}
return objects; }
protected Collection<Object> handleEntitySet(Element entitySet, Hashtable<String, Object> map) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
Class clazz = Class.forName(entitySet.getAttribute("class")); Collection<Object> objects = new ArrayList<Object>();
for(Element entity : helper.getElements(entitySet, "entity_name")){ objects.add(this.handleEntity(entity, clazz, map));
}
return objects; }
protected Object instantiateEntity(Class clazz, Element entity) throws InstantiationException, IllegalAccessException{ return clazz.newInstance();
}
protected Object handleEntity(Element entity, Class clazz, Hashtable<String, Object> map) throws InstantiationException, IllegalAccessException {
Object object = this.instantiateEntity(clazz, entity);
for(Element attribute : helper.getElements(entity)){
Object value = this.handleAttribute(object, attribute, clazz, map); this.setAttribute(value, object, attribute.getNodeName());
}
map.put(entity.getAttribute("id"), object); return object;
}
protected void setAttribute(Object value, Object object, String nodeName) { ReflectionUtil.setField(value, object, object.getClass(), nodeName); }
protected Object handleAttribute(Object object, Element attribute, Class clazz, Hashtable<String, Object> map) {
Class type = getAttributeType(object, clazz, attribute.getNodeName()); Object ret = null;
if(Collection.class.isAssignableFrom(type)){ Collection c = new ArrayList();
for(Element value : helper.getElements(attribute)){
c.add(this.handleValue(object, value, map, String.class)); }
ret = c; }else{
return ret; }
protected Object handleValue(Object object, Element attribute, Hashtable<String, Object> map, Class type) {
Object ret = null;
if(attribute.getAttribute("ref") != null && !attribute.getAttribute("ref").isEmpty()){ ret = map.get(attribute.getAttribute("ref"));
}else{
ret = this.getValue(type, helper.getContent(attribute)); }
if(ret == null){
System.out.println(object + " - " + type + " - " + attribute.getNodeName()); throw new IllegalArgumentException();
}else{
return ret; }
}
protected Object getValue(Class type, String value) { Object ret = null;
if(type.equals(String.class)){ ret = value;
}else if(type.equals(byte[].class)){ try{
File file = new File(value);
byte[] foto = new byte[(int) file.length()]; FileInputStream reader = new FileInputStream(file); reader.read(foto);
ret = foto; }catch(IOException e){
e.printStackTrace(); }
}
return ret; }
protected Class getAttributeType(Object object, Class clazz, String nodeName) { return ReflectionUtil.getField(clazz, nodeName).getType();
}
}
Essa classe define pontos de extensão a serem usados pela classe que manipula
Publicacao usando AOM:
2.2.4.6. AOMXmlGenerator.java
public class AOMXmlGerador extends ReflectionXmlGerador {
public AOMXmlGerador(String xml) throws SAXException, IOException, ParserConfigurationException{ super(xml);
}
@Override
return p; }
@Override
protected Class getAttributeType(Object object, Class clazz, String nodeName) { Class c = null;
try{
c = ReflectionUtil.getField(clazz, nodeName).getType(); }catch(IllegalStateException e){
try {
Publicacao publicacao = (Publicacao) object; AOMHelper helper = AOMHelper.getInstance();
c = Class.forName(
helper.getTipoPublicacao(publicacao.getTipo()).getPropertyType(nodeName).getTipo()); } catch (ClassNotFoundException e1) {
e1.printStackTrace(); }
}
return c; }
@Override
protected void setAttribute(Object value, Object object, String nodeName) { try{
super.setAttribute(value, object, nodeName); }catch(IllegalStateException e){
Publicacao publicacao = (Publicacao) object;
publicacao.addProperty(new PropriedadePublicacao(nodeName, (Serializable) value));
} }
}
A nova classe
GerarBanco
ficou assim:
2.2.4.7. GerarBanco.java
public class GerarBanco {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, SAXException, IOException, ParserConfigurationException { Configuration conf = new AnnotationConfiguration();
conf.configure();
SchemaExport se = new SchemaExport(conf); se.create(true, true);
popularBanco(); }
private static void popularBanco() throws ClassNotFoundException, InstantiationException, IllegalAccessException, SAXException, IOException, ParserConfigurationException { try{
Hashtable<String, Object> map = new Hashtable<String, Object>();
}catch(RGMSException e){ e.printStackTrace(); }
}
private static void inserirLinhasPesquisa(Hashtable<String, Object> map) throws ClassNotFoundException, InstantiationException, IllegalAccessException, SAXException, IOException, ParserConfigurationException, RGMSException {
for(Object object : new ReflectionXmlGerador("linhaspesquisa.xml").inserir(map)){ Facade.getInstance().inserirLinhaPesquisa((LinhaPesquisa) object); }
}
private static void inserirNoticias(Hashtable<String, Object> map) throws ClassNotFoundException, InstantiationException, IllegalAccessException, SAXException, IOException, ParserConfigurationException, RGMSException {
for(Object object : new ReflectionXmlGerador("noticias.xml").inserir(map)){ Facade.getInstance().inserirNoticia((Noticia) object);
} }
private static void inserirPublicacoes(Hashtable<String, Object> map) throws ClassNotFoundException, InstantiationException, IllegalAccessException, SAXException, IOException, ParserConfigurationException, RGMSException {
for(Object object : new AOMXmlGerador("publicacoes.xml").inserir(map)){ Facade.getInstance().inserirPublicacao((Publicacao) object); }
}
private static void inserirMembros(Hashtable<String, Object> map) throws ClassNotFoundException, InstantiationException, IllegalAccessException, SAXException, IOException, ParserConfigurationException, RGMSException {
for(Object object : new ReflectionXmlGerador("membros.xml").inserir(map)){ Facade.getInstance().inserirMembro((Membro) object);
} }
}
2.2.5. Situação posterior
Depois deste refatoramento, a inserção de dados fica separada da definição do mesmo,
sendo que a descrição dos dados agora ocorre livre de código, em arquivos .xml. A inserção
de dados no banco está mais genérica, ligada apenas a um diretório de arquivos xml para
inserção.
TreeMap em função do número de linhas de código
TreeMap em função da complexidade
3. Adicionando nova funcionalidade
Nesta seção, uma funcionalidade escolhida será adicionada ao sistema de forma que se
possa gerar uma versão com ou sem tal funcionalidade.
3.1. Funcionalidade escolhida
Como nova funcionalidade no sistema, será implementada a opção de oferecer a
importação de publicações via bibtex. Esta foi a funcionalidade escolhida pois utiliza
diretamente as subclasses de Publicacao, que foram transformadas nesta refatoração em
adaptive model-objects, como foi descrito na sessão anterior. A implementação deste serviço
ficou muito mais fácil com o uso de AOMs, como será visto nesta sessão.
Como este serviço é opcional, foi necessária a aplicação de técnicas de compilação
condicional em todo código inserido especialmente para para esta nova funcionalidade. Deste
modo, apenas os clientes que optarem por esta funcionalidade irão receber uma versão do
sistema que inclui estes bytecodes na versão executável do mesmo.
3.2. Implementação da funcionalidade
Como pré-processador foram utilizados o software
Antenna
, que permite o uso de
diretivas de pré-processamento em código java, e o
Velocity
, que permite a geração de
arquivos .jsp de acordo com algumas diretivas também.
Primeiro, vamos adicionar a funcionalidade em si no projeto. Depois modificaremos o
sistema para permitir que a mesma seja separada em um código opcional, que será compilado
apenas de acordo com o cliente para o qual o executável será destinado.
3.2.1. Formato do arquivo BibTex
A possibilidade de importação de publicações via bibtex exige do sistema a conversão
de arquivos no padrão bibtex em objetos do tipo Publicacao. Na versão do sistema não
refatorada, isso iria exigir um conversor específico para cada tipo de publicação suportado pelo
sistema, e em uma futura modificação em subtipos de publicação ou a inserção de um novo
tipo levaria este código a ser modificado também. Com a versão já refatorada do sistema foi
possível inserir um código mais genérico e flexível para a conversão de um bibtex em uma
publicação.
Um arquivo bibtex gerado pelo atual sistema tem a seguinte estrutura:
3.2.1.1. Exemplo de entrada num arquivo .bib
atributeName_1 = atributeValue_1, atributeName_2 = atributeValue_2, ...
}
O que muda entre os antigos subtipos de Publicacao são apenas o entry type e a lista
de atributos. Uma vez que cada instância de publicação tem sua lista (no caso implementado
aqui, uma hashtable) de atributos definida em um arquivo independente, não no código, o
mapeamento entre um objeto Publicacao e um bibtex pode ser feito com apenas um codigo
que relaciona estes dois arquivos.
Utilizando uma biblioteca chamada
JavaBib, a manipulaçao de arquivos bibtex fica
mais simples e legível agora que temos uma definição mais genérica de uma Publicação.
3.2.2. Acrescentando a funcionalidade
Utilizando esta classe, o código de
BibTexAspectque escreve o arquivo bibtex então é
adicionado de um método que realiza a criação de uma publicação a partir de um bibtex. O
código anterior que gerava este tipo de arquivo foi alterado para usar a biblioteca JavaBib
também. Depois destas modificações, a classe ficou assim:
3.2.2.1. BibTexAspect.aj
package br.ufpe.cin.rgms;
import java.io.File;
import java.io.FileNotFoundException; import java.io.PrintWriter;
import bibtex.dom.BibtexEntry; import bibtex.dom.BibtexFile;
import br.ufpe.cin.rgms.publicacao.modelo.PropriedadePublicacao; import br.ufpe.cin.rgms.publicacao.modelo.Publicacao;
//#ifdef PUBLICACAO_BIBTEX_IMPORT import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; import java.util.List;
import bibtex.parser.BibtexParser; import bibtex.parser.ParseException;
import br.ufpe.cin.rgms.publicacao.modelo.TipoPublicacao; import br.ufpe.cin.rgms.util.ListGenerator;
import br.ufpe.cin.rgms.util.aom.AOMHelper; //#endif
privileged public aspect BibtexAspect {
public File Publicacao.createBibTex() throws FileNotFoundException{ BibtexFile file = new BibtexFile();
BibtexEntry entry = file.makeEntry(this.getTipo(), this.getTitulo());
String autoresMembros = (this.listaAutoresMembros()!=null)? this.listaAutoresMembros(): ""; String autoresNaoMembros = (this.listaAutoresNaoMembros()!=null)?
this.listaAutoresNaoMembros():"";
entry.setField("title", file.makeString(this.getTitulo())); entry.setField("year", file.makeString(this.getAno()));
for(PropriedadePublicacao p : this.getPropriedades()){
entry.setField(p.getNome(), file.makeString(p.getValor().toString())); }
file.addEntry(entry);
File arq = new File(this.getTitulo() + ".bib"); PrintWriter pw = new PrintWriter(arq);
file.printBibtex(pw); pw.close(); return arq; } //#ifdef PUBLICACAO_BIBTEX_IMPORT
public static List<Publicacao> Publicacao.createFromFile(File file) throws IOException, ParseException{ BibtexFile bib = new BibtexFile();
BibtexParser parser = new BibtexParser(true); parser.parse(bib, new FileReader(file));
List<Publicacao> list = new ArrayList<Publicacao>();
for(Object o : bib.getEntries()){
BibtexEntry entry = (BibtexEntry) o;
String author =
StringsUtil.removerColchetesOuAspas(entry.getFields().get("author").toString()); String year =
StringsUtil.removerColchetesOuAspas(entry.getFields().get("year").toString()); String title =
StringsUtil.removerColchetesOuAspas(entry.getFields().get("title").toString());
Publicacao ret = new Publicacao(ListGenerator.createListaMembro(author), ListGenerator.createListaNaoMembro(author), title, year, null, entry.getEntryType());
AOMHelper helper = AOMHelper.getInstance();
TipoPublicacao tipo = helper.getTipoPublicacao(ret.getTipo());
for(Object field : entry.getFields().keySet()){
if(tipo.getPropertyType(field.toString()) != null){
ret.addProperty(new PropriedadePublicacao(field.toString(), entry.getFieldValue(field.toString()).toString())); } } list.add(ret); } return list; } //#endif }
3.2.2.2. AdicionarPublicacoesFromBibTexServlet.java
//#ifdef PUBLICACAO_BIBTEX_IMPORT
package br.ufpe.cin.rgms.publicacao.controle;
public class AdicionarPublicacoesFromBibTexServlet extends RGMSUploadFormServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { byte[] bytes = this.getArquivo();
if(bytes != null){
File file = File.createTempFile("bibtex", "publicacao");
FileOutputStream fos = new FileOutputStream(file); fos.write(bytes);
fos.close();
try {
List<Publicacao> publicacoes = Publicacao.createFromFile(file);
for (Publicacao publicacao : publicacoes) {
Facade.getInstance().inserirPublicacao(publicacao);
}
this.handleSuccess(request, response); } catch (Exception e) {
this.handleError(request, response, e);
}
} else {
this.handleError(request, response, new IllegalArgumentException("Problemas ao ler informações do arquivo recebido.")); }
}
protected void handleError(HttpServletRequest request, HttpServletResponse response, Exception erro) { request.setAttribute("publicacaostatus", erro.getMessage());
}
protected void handleSuccess(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException { request.setAttribute("publicacaostatus", "Publicacao inserida com sucesso.");
} } //#endif
O último passo consiste em adicionar na camada View a opção de fazer upload de um
arquivo .bib para a inserção de publicações e habilitar o servlet definido somente se tal
funcionalidade estiver habilitada. Para isso, o JSP
adicionarpublicacao.jsp
foi modificado,
e um novo formulário com o campo de upload foi adicionado na página. Para isso foi utilizada a
ferramenta Velocity.
3.2.2.3. adicionarpublicacao.jsp
...
#if($publicacao_from_bibtex)
enctype="multipart/form-data"> <p>
<LABEL for="bibtexfile">
<%out.println(Properties.getProperty(this.getServletContext(),"pdf"));%> </LABEL> <input onkeypress="return noenter();" type="hidden" name="MAX_FILE_SIZE" value="500" />
<input name="bibtexfile" type="file" /> </p>
</FORM> #end
...
E no web.xml:
3.2.2.4. web.xml
#if($publicacao_from_bibtex) <servlet>
<servlet-name>AdicionarPublicacoesFromBibTex</servlet-name>
<servlet-class>br.ufpe.cin.rgms.publicacao.controle.AdicionarPublicacoesFromBibTexServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>AdicionarPublicacoesFromBibTex</servlet-name> <url-pattern>/AdicionarPublicacoesFromBibTex.do</url-pattern> </servlet-mapping>
#end
Para concluir, foi feito um gerador para poder gerar versões do RGMS com ou sem a
funcionalidade opcional adicionada, preprocessando a parte do velocity e permitindo gerar o
código final com o Antenna.
adicionarpublicacao.jsp gerado pelo Velocity no RGMSGenerator. Perceba também que o projeto gerado não contém o pacote util.generator
3.2.2.5. RGMSGenerator.java
public class RGMSGenerator {
private static VelocityContext context;
private static List<String> skipFolders;
public static void processDirectory(File dir, String path)
throws ResourceNotFoundException, ParseErrorException, Exception{ new File("../" + path).mkdirs();
if(dir.isDirectory()){
for(File file : dir.listFiles()){ if(file.isDirectory()){
if(!skipFolders.contains(file.getName())){
processDirectory(file, path + "/" + file.getName()); }
}else{
File f = new File("../" + path + "/" +
file.getName()).getAbsoluteFile(); f.createNewFile(); processFile(f, file); } } } }
private static void processFile(File f, File original) throws ParseErrorException, Exception { try{ System.out.println(original.getAbsolutePath()); if(original.getName().endsWith(".jsp") || original.getName().endsWith(".xml") || original.getName().endsWith(".properties") || original.getName().endsWith(".java")){
Template t = ve.getTemplate( original.getAbsolutePath() );
PrintWriter writer = new PrintWriter(f); t.merge( context, writer );
writer.close(); }else{ copy(f, original); } }catch(ResourceNotFoundException e){ copy(f, original); } }
private static void copy(File f, File original) throws IOException{ FileCopyUtils.copy(original, f);
}
public static void main( String args[] ) throws ResourceNotFoundException, ParseErrorException, MethodInvocationException, Exception
{
ve = new VelocityEngine();
Properties config = new Properties(); config.put("resource.loader", "file"); config.put("file.resource.loader.class",
"org.apache.velocity.runtime.resource.loader.FileResourceLoader"); config.put("file.resource.loader.path", "");
config.put("file.resource.loader.cache", "true");
skipFolders = new ArrayList<String>(); skipFolders.add(".svn");
skipFolders.add("generator"); skipFolders.add("build"); skipFolders.add(".classes");
context = new VelocityContext();
Properties props = new Properties();
props.load(RGMSGenerator.class.getResourceAsStream("RGMSGenerator.properties"));
for(String feature : props.stringPropertyNames()){ context.put(feature, props.get(feature)); }
File file = new File("").getAbsoluteFile(); processDirectory(file, "/Generated/RGMS_Work"); }
}
O ideal é que essa classe seja substituída posteriormente por um gerador menos
mecânico. Essa classe foi criada apenas para efeito de teste. Após todas estas modificações,
na versão do projeto que inclui a nova funcionalidade a página de inserção de publicação ficou
assim:
Tela de sucesso mostrada após inserção de publicações por meio de um arquivo bibtex.
Para poder testar, outro erro teve que ser corrigido no
ListGenerator
onde a busca de
membros no banco de dados, dada uma lista de nomes, era feita. Depois disto, a classe
ListGenerator
foi toda modificada, ficando assim:
3.2.2.6. ListGenerator.java
package br.ufpe.cin.rgms.util;
public class ListGenerator {
public static List<Membro> createListaMembro(String autoresMembros){ List<Membro> ret = new ArrayList<Membro>();
List<Membro> membros = Facade.getInstance().getMembros();
String[] names = autoresMembros.split(",");
for (String string : names) {
for (Membro membro : membros) {
if(string.equals(membro.getNome()+" "+membro.getSobrenome())){
ret.add(membro);
}
}
}
return ret;
}
public static List<String> createListaNaoMembro(String autoresNaoMembros){ List<String> lista = Arrays.asList(autoresNaoMembros.split(",")); return lista;
4. Otimizando refatoramentos anteriores
4.1. Impactos das alterações
Após as mudanças feitas no problema 1 principalmente, o mecanismo de validação
definido no refatoramento da etapa anterior não é mais completo pois algumas propriedades de
Publicacao não podem ser obtidas por reflexão como decorrência do uso do padrão AOM.
4.2. Adaptando o mecanismo de validação
Precisamos alterar o mecanismo de validação definido anteriormente para que ele
possa obter as propriedades de uma Publicacao não só por reflexão, mas também pela lista de
propriedades associada a ela.
4.2.1. Situação anterior
A interface de definição de regras de validação é da forma:
public interface IValidationRule {
public void setParameters(String[] parameters);
public boolean validate(Object value);
}
O xml publicacao-aom.xml era da seguinte forma:
<?xml version="1.0" encoding="ISO-8859-1"?>
<aom xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="file:publicacao-aom.xsd">
<publicacao-types> <publicacao-type>
<entry-type>conference</entry-type>
<text-option>Artigo em Conferência</text-option>
<optional-attributes> <attribute>
<name>pages</name>
<class>java.lang.String</class> <text>paginas</text>
</attribute> </optional-attributes> <required-attributes>
<attribute>
<name>booktitle</name>
<text>conferencia</text>
</attribute> </required-attributes> </publicacao-type>
... <publicacao-types> </aom>
Apenas há regra de obrigatoriedade em atributos. Isso se torna um pouco limitado.
4.2.2. Otimizando a solução
Vamos fazer de forma similar ao antigo mecanismo: associar a cada propriedade de
publicação um conjunto de regras. Isso pode ser atingido utilizando o padrão
RuleObject
que se adequa ao AOM perfeitamente. Dessa forma, publicacao-aom.xml pode ter um atributo
assim:
4.2.2.1. Novo trecho do publicacao-aom.xml
<attribute name=”pages”>
<class>java.lang.String</class> <text>paginas</text>
<validation> <rule>
<class>br.ufpe.cin.rgms.base.validation.rule.RangeLengthRule</class> <param>
<name>min</name> <value>1</value> </param>
<param>
<name>max</name> <value>20</value> </param>
</rule> <rule>
<class>br.ufpe.cin.rgms.base.validation.rule.RequiredFieldValidationRule</class> </rule>
</validation> </attribute>
E a interface de regra de validação associa um nome a cada parâmetro:
4.2.2.2. IValidationRule
public interface IValidationRule {
public void setParameters(Hashtable<String, String> parameters);
public boolean validate(Object value);
}
4.2.2.3. Exemplo de RuleObject - RangeLengthRule
public class RangeLengthRule implements IValidationRule {
private int max; private int min;
@Override
public void setParameters(Hashtable<String, String> parameters) { this.max = Integer.parseInt(parameters.get("max")); this.min = Integer.parseInt(parameters.get("min")); }
public boolean checkLength(Object value){ int length;
if(value != null){
if(value instanceof String){
length = ((String) value).length(); }else if(value instanceof Collection<?>){
length = ((Collection<?>) value).size(); }else{
throw new IllegalArgumentException(); }
}else{
throw new IllegalArgumentException(); }
return length >= this.min && length <= this.max; }
@Override
public boolean validate(Object value) { boolean valid = value != null;
if(valid){
if(value instanceof PropriedadePublicacao){
value = ((PropriedadePublicacao) value).getValor(); }
valid = this.checkLength(value); }
return valid; }
}
Antes a validação era definida nas demais classes com arquivos de propriedades.
Preferiu-se alterar para uma representação com xml pelo fato de ser mais legível:
4.2.2.4. AbstractBusinessEntity.validate.properties
4.2.2.5. AbstractBusinessEntity.validation.xml
<?xml version="1.0" encoding="UTF-8"?>
<validation xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="file:../../../../../validation.xsd"> <attribute name="id">
<rule>
<class>br.ufpe.cin.rgms.base.validation.rule.RequiredFieldValidationRule</class> </rule>
</attribute> </validation>
Para isso, o PropertiesValidationUtil foi substituido por um XMLValidationUtil:
4.2.2.6. XMLValidationUtil.java
@Service(value = "XMLValidationUtil")
public class XMLValidationUtil implements IValidationUtil {
private IValidationRuleFactory factory;
@Autowired
public void setValidationRuleFactory(
@Qualifier(value="ValidationRuleFactory") IValidationRuleFactory factory){ this.factory = factory;
}
private boolean validate(AbstractBusinessEntity object, Class clazz){ try{
XMLHelper helper = XMLHelper.getInstance(); Document xml =
helper.getXML(clazz.getResourceAsStream(String.format("%s.validation.xml", clazz.getSimpleName()))); boolean validate = true;
Iterator<Element> attributes =
helper.getElements(xml.getDocumentElement(), "attribute").iterator();
while(attributes.hasNext() && validate){ Element property = attributes.next();
String propertyName = property.getAttribute("name"); Iterator<Element> rules =
helper.getElements(property, "rule").iterator();
while(rules.hasNext() && validate ){ Element rule = rules.next();
Hashtable<String, String> map = new Hashtable<String, String>(); Iterator<Element> params =
helper.getElements(rule, "param").iterator();
while(params.hasNext()){
Element param = params.next(); String name =
helper.getContent(helper.getMatchingElement(param, "name")); String value =
helper.getContent(helper.getMatchingElement(param, "value")); map.put(name, value);
}
IValidationRule validation = factory.createValidationRule( helper.getContent(helper.getMatchingElement(rule, "class")), map);
ReflectionUtil.getFieldValue(object, object.getClass(), propertyName));
} }
return validate; }catch(IOException e){
throw new IllegalStateException(); }catch(ParserConfigurationException e){
throw new IllegalStateException(); } catch (SAXException e) {
throw new IllegalStateException(); }
}
public boolean validate(AbstractBusinessEntity object){ Class<?> c = object.getClass();
boolean valid = true;
while(!c.equals(Object.class) && valid){ valid = this.validate(object,c); c = c.getSuperclass();
}
return valid; }
}
4.2.2.7. Alteração no AOMHelper
Com isso, o seguinte método foi acrescentado no AOMHelper:
public boolean validate(Publicacao publicacao){
TipoPublicacao tipo = this.getTipoPublicacao(publicacao.getTipo()); boolean valid = true;
Iterator<String> fields = tipo.getPropertyTypes().keySet().iterator();
while(valid && fields.hasNext()){ String field = fields.next();
TipoPropriedade property = tipo.getPropertyTypes().get(field); Iterator<IValidationRule> rules = property.getRules().iterator();
while(valid && rules.hasNext()){
valid = rules.next().validate(publicacao.getProperty(field)); }
}
return valid; }
4.2.2.8. Alteração no PublicacaoManager
Por fim, no PublicacaoManager, o código de validar foi estendido:
@Override
if(!AOMHelper.getInstance().validate(tipo)){
throw new RGMSException("Dados inválidos na inserção"); }
}
4.2.3. Situação posterior
O xml de publicacao-aom.xml ficou no seguinte formato:
<?xml version="1.0" encoding="ISO-8859-1"?>
<aom xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="file:publicacao-aom.xsd"> <publicacao-types>
<publicacao-type>
<entry-type>conference</entry-type>
<text-option>artigo_conferencia</text-option> <attribute-list>
<attribute name="pages">
<class>java.lang.String</class> <text>paginas</text>
<validation> <rule>
<class>br.ufpe.cin.rgms.base.validation.rule.RangeLengthRule</class> <param>
<name>min</name> <value>1</value> </param>
<param>
<name>max</name> <value>20</value> </param>
</rule> <rule>
<class>br.ufpe.cin.rgms.base.validation.rule.RequiredFieldValidationRule</class> </rule>
</validation> </attribute>
<attribute name="booktitle">
<class>java.lang.String</class> <text>conferencia</text>
<validation> <rule>
<class>br.ufpe.cin.rgms.base.validation.rule.RequiredFieldValidationRule</class> </rule>
</validation> </attribute>
<attribute name="month"> <name>month</name>
<class>java.lang.String</class> <text>mes</text>
</attribute> </attribute-list> </publicacao-type> <publicacao-type>
<entry-type>article</entry-type>
<attribute name="pages">
<class>java.lang.String</class> <text>paginas</text>
</attribute>
<attribute name="number">
<class>java.lang.String</class> <text>numero</text>
</attribute>
<attribute name="volume">
<class>java.lang.String</class> <text>volume</text>
</attribute>
<attribute name="journal">
<class>java.lang.String</class> <text>jornal</text>
<validation> <rule>
<class>br.ufpe.cin.rgms.base.validation.rule.RequiredFieldValidationRule</class> </rule>
</validation> </attribute>
</attribute-list> </publicacao-type> <publicacao-type>
<entry-type>phdthesis</entry-type> <text-option>pos</text-option> <attribute-list>
<attribute name="month">
<class>java.lang.String</class> <text>mes</text>
</attribute>
<attribute name="school">
<class>java.lang.String</class> <text>universidade</text> <validation>
<rule>
<class>br.ufpe.cin.rgms.base.validation.rule.RequiredFieldValidationRule</class> </rule>
</validation> </attribute>