• Nenhum resultado encontrado

Atividade7

N/A
N/A
Protected

Academic year: 2021

Share "Atividade7"

Copied!
40
0
0

Texto

(1)

Universidade Federal de Pernambuco – UFPE

Centro de Informática - CIn

Tópicos Avançados em

Engenharia de Software

Utilizando técnicas de orientação

a aspectos e mixins

Dupla:

Caio César Sabino Silva ([email protected]) Laís Sousa de Andrade ([email protected])

Professor: Paulo Borba ([email protected])

(2)

Sumário

1. Introdução ... 3

2. Resolvendo problemas encontrados ... 4

2.1. Controle de Transações com Banco de Dados em ManagerImpl... 4

2.1.1. Situação anterior ... 4

2.1.2. Valores observados ... 5

2.1.3. Princípios utilizados ... 6

2.1.4. Refatorando o código ... 6

2.1.5. Situação posterior ... 7

2.2. Ajuste de código no GerarBanco.java ... 9

2.2.1. Situação anterior ... 10

2.2.2. Valores observados ... 12

2.2.3. Princípios utilizados ... 12

2.2.4. Refatorando o código ... 12

2.2.5. Situação posterior ... 16

2.3. Separação do concern Validação ... 17

2.3.1. Situação anterior ... 17

2.3.2. Valores observados ... 18

2.3.3. Princípios utilizados ... 19

2.3.4. Refatorando o código ... 19

2.3.5. Situação posterior ... 22

2.4. Servlets com código bastante similar ... 24

2.4.1. Situação anterior ... 24

2.4.2. Valores observados ... 25

2.4.3. Princípios utilizados ... 25

2.4.4. Refatorando o código ... 25

2.4.5. Situação posterior ... 28

3. Adicionando funcionalidades novas ... 30

3.1. Funcionalidade escolhida ... 30

3.2. Implementando a funcionalidade ... 33

3.2.1. Adicionando o módulo do twitter: src.aspects/twitter ... 33

(3)

1. Introdução

Nesta atividade, alguns problemas de reuso de modularidade identificados nas primeiras etapas do projeto serão corrigidos, algumas soluções encontradas anteriormente serão melhoradas pelo uso da técnica de orientação a aspectos.

Além disso, uma funcionalidade será adicionada ao projeto, dentre as possíveis no conjunto de requisitos.

(4)

2. Resolvendo problemas encontrados

Nesta seção, alguns problemas encontrados serão resolvidos com a técnica de orientação a aspectos.

2.1. Controle de Transações com Banco de Dados em ManagerImpl

No código do ManagerImpl, há código de controle de transação com o banco de dados, de forma que o Manager está dependente indiretamente de uma implementação específica do Dao que tenha suporte a controle de transações. Além disso, todo método da classe ManagerImpl tem mistura dos concerns regras de negócios e controle de transação.

2.1.1. Situação anterior

(5)

Clone Set 7 - O quadrado em destaque possui trechos de transação replicados dentro do mesmo módulo ManagerImpl.

2.1.2. Valores observados

Pode-se observar que o código do ManagerImpl:

● está relacionado a múltiplos concerns: Transação com o banco de dados, implementação de funcionalidade do sistema;

● possui repetição de código entre métodos, pois a parte que trata da transação é a mesma em todos os métodos desta classe;

● implementa uma variação opcional do Dao de forma obrigatória: o controle de transação deve ser existente dependendo do tipo de implementação do Dao.

(6)

2.1.3. Princípios utilizados

No refatoramento, utilizamos dois princípios:

● Minimização de repetições, uma vez que o código de controle de transação pode ser extraído para um único ponto;

● Separação de concerns, deixando o controle de transações separado da implementação de uma funcionalidades do sistema;

2.1.4. Refatorando o código

A princípio, criou-se um source folder src.aspects/hibernate_dao, de forma que representa a variação associada a um Dao que utiliza o Hibernate (e consequentemente usando transações). Foram movidas as classes para esse source folder:

Agora vamos adicionar o aspecto que representa o controle de transação na Facade de forma que diversas operações de negócio podem ocorrer numa mesma transação com o banco de dados:

2.1.4.1. HibernateTransactionManagerAspect.aj

public aspect HibernateTransactionManagerAspect {

pointcut transactionMethod(Facade facade) : call(public * Facade.*(..)) && !(call(public void Facade.set*(..)))

&& !(call(public * Facade.get*(..))) && !(call(public Facade.new(..))) && !this(Facade) && target(facade);

before(Facade facade) : transactionMethod(facade) { Persistence.getInstance().beginTransaction();

(7)

}

after(Facade facade) returning : transactionMethod(facade) { Persistence.getInstance().commit();

}

after(Facade facade) throwing(Exception e) : transactionMethod(facade) { Persistence.getInstance().rollback();

} }

2.1.5. Situação posterior

Após as alterações, a classe ManagerImpl ficou assim:

2.1.5.1. ManagerImpl

public class ManagerImpl<Tipo extends AbstractBusinessEntity> implements IManager<Tipo> { private static ApplicationContext context;

protected Dao<Tipo> dao; static {

ManagerImpl.context =

new ClassPathXmlApplicationContext("classpath:applicationContext.xml"); }

protected void validar(Tipo tipo) throws RGMSException{ IValidationUtil validation =

(IValidationUtil) context.getBean("XMLValidationUtil"); if(!validation.validate(tipo)){

throw new RGMSException("Dados inválidos na inserção"); }

}

public Dao<Tipo> getDao() { return dao;

}

public void setDao(Dao<Tipo> dao) { this.dao = dao;

}

public void inserir(Tipo tipo) throws RGMSException { validar(tipo);

(8)

this.dao.adicionar(tipo); }

public List<Tipo> listar() {

return this.dao.listarTudo(); }

public Tipo consultarUnicoResultado(long id) { return this.dao.procurar(id);

}

public void remover(Tipo tipo){ this.dao.remover(tipo); }

public void remover(long id){ this.dao.remover(id); }

public void alterar(Tipo tipo) throws RGMSException{ this.validar(tipo);

this.dao.atualizar(tipo); }

public void rebind(Tipo tipo){ this.dao.rebind(tipo); }

public Tipo searchOnUniqueField(String field, Object value) { return this.dao.queryOnUniqueField(field, value); }

public List<Tipo> list(HashMap<String, Object> restrictions) { return this.dao.queryOnRestrictionMap(restrictions); }

public boolean exists(Tipo membro, String[] fields) { return this.dao.exists(membro, fields); }

public List<Tipo> consulta(String atributo, String query){ return this.dao.consulta(atributo,query);

} }

Todas as chamadas de método à classe Persistence puderam ser removidas com o refatoramento. E todo o sistema de persistência com banco de dados usando o Hibernate ficou separado no src.aspects/hibernate_dao, inclusive as implementações do Dao para o mesmo.

(9)

2.1.5.2. Gráfico dos clones

O quadrado de azul em destaque é a classe ManagerImpl que não possui nenhum clone.

2.1.5.3. Impactos do refatoramento

● Redução do espalhamento do concern de persistência de forma que ele se limita ao source folder de hibernate_dao e à interface Dao.

● Eliminação de conjuntos de clone de código na classe ManagerImpl

2.2. Ajuste de código no GerarBanco.java

Esse refatoramento foi apenas pra eliminar um clone de código que existia dentro da classe GerarBanco que surgiu por causa da solução proposta com o uso de Reflection e AOM.

(10)

2.2.1. Situação anterior

No gráfico de clones a seguir, podemos ver no centro, um clone grande dentro de uma única classe.

2.2.1.1. Gráfico dos clones

2.2.1.2. 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);

(11)

}

private static void popularBanco() throws ClassNotFoundException, InstantiationException, IllegalAccessException, SAXException, IOException, ParserConfigurationException { try{

Hashtable<String, Object> map = new Hashtable<String, Object>(); inserirMembros(map); inserirPublicacoes(map); inserirNoticias(map); inserirLinhasPesquisa(map); }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);

} }

(12)

2.2.2. Valores observados

Pode-se perceber que o código do gerador, embora esteja reutilizável para objetos diferentes nos xmls, possui um número fixo de métodos para cada classe. O código é específico para as classes existentes.

2.2.3. Princípios utilizados

Nesse refactoring, o princípio utilizado foi o de generalização da implementação.

2.2.4. Refatorando o código

Neste refatoramento vamos separar e tornar o mais genérico possível o concern de povoamento do banco de dados. Criamos para isso um source folder chamado

insertion_script:

Para permitir que o código fosse variável para as classes a serem povoadas, foi usado um xml insertion.xml, que basicamente associa a cada entidade de negócios um gerador (normal ou com AOM) e um arquivo com os dados de povoamento:

(13)

2.2.4.1. insertion.xml

<?xml version="1.0" encoding="ISO-8859-1"?> <xml> <populated_classes> <class> <name>br.ufpe.cin.rgms.membro.modelo.Membro</name> <file>membros.xml</file> <generator>br.ufpe.cin.rgms.util.schema.ReflectionXmlGerador</generator> </class> <class> <name>br.ufpe.cin.rgms.publicacao.modelo.Publicacao</name> <file>publicacoes.xml</file> <generator>br.ufpe.cin.rgms.util.schema.AOMXmlGerador</generator> </class> <class> <name>br.ufpe.cin.rgms.noticia.modelo.Noticia</name> <file>noticias.xml</file> <generator>br.ufpe.cin.rgms.util.schema.ReflectionXmlGerador</generator> </class> <class> <name>br.ufpe.cin.rgms.linhasdepesquisa.modelo.LinhaPesquisa</name> <file>linhaspesquisa.xml</file> <generator>br.ufpe.cin.rgms.util.schema.ReflectionXmlGerador</generator> </class> </populated_classes> </xml>

Dessa forma, o código do gerador ficou:

2.2.4.2. GerarBanco.java

public class GerarBanco {

public void gerar() throws ClassNotFoundException, InstantiationException, IllegalAccessException, SAXException, IOException, ParserConfigurationException{

Configuration conf = new AnnotationConfiguration(); conf.configure();

SchemaExport se = new SchemaExport(conf); se.create(true, true);

this.popularBanco(); }

private void popularBanco() throws ClassNotFoundException,

InstantiationException, IllegalAccessException, SAXException, IOException, ParserConfigurationException {

try{

Hashtable<String, Object> map = new Hashtable<String, Object>(); XMLHelper helper = XMLHelper.getInstance();

Document xml = helper.getXML(

(14)

for(Element clazz : helper.getElements(xml.getDocumentElement(), "class")){

Class<?> generator = Class.forName(helper.getContent( helper.getMatchingElement(clazz, "generator"))); String file = helper.getContent(

helper.getMatchingElement(clazz, "file")); this.inserir(generator, file, map);

}

}catch(RGMSException e){ e.printStackTrace(); }

}

private void inserir(Class<?> clazz, String file, Hashtable<String, Object> map) throws ClassNotFoundException, InstantiationException,

IllegalAccessException, SAXException, IOException, ParserConfigurationException, RGMSException{

ReflectionXmlGerador gerador = (ReflectionXmlGerador) clazz.newInstance(); gerador.setXml(file);

for(Object object : gerador.inserir(map)){ Facade.getInstance().inserir(object); }

}

public static void main(String[] args) throws ClassNotFoundException,

InstantiationException, IllegalAccessException, SAXException, IOException, ParserConfigurationException {

new GerarBanco().gerar(); }

}

Na fachada, o método inserir foi adicionado e utiliza Reflection para chamar o inserir correto:

2.2.4.3. Método inserir da Facade

Basicamente o método busca a classe logo abaixo de AbstractBusinessEntity do objeto recebido, pois e invoca o método inserir + nome da classe, utilizando reflexão.

public void inserir(Object object) throws RGMSException{ try {

Class<?> clazz = object.getClass();

while(!clazz.getSuperclass().equals(AbstractBusinessEntity.class)){ clazz = clazz.getSuperclass();

(15)

Method method =

this.getClass().getDeclaredMethod("inserir" + clazz.getSimpleName(), clazz); method.invoke(this, object); } catch (SecurityException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } }

A reflexão é feita dentro do método inserir, pois se fosse feita fora da Facade, o aspecto de transação com o banco de dados não funcionaria por ser uma invocação por reflexão.

(16)

2.2.5. Situação posterior

Após o refatoramento, o gráfico dos clones gerado foi:

2.2.5.1. Gráfico dos clones

O retângulo de azul é a classe GerarBanco

2.2.5.2. Impactos do refatoramento

Os impactos do refatoramento foram:

● o gerador ficou mais genérico, podendo trocar de arquivo, mudar o método de geração; ● tanto as classes como os dados a serem povoados podem ser alterados com apenas

(17)

Após isso, há apenas 10 conjuntos de clone restantes:

2.3. Separação do concern Validação

Este refatoramento tem o mesmo intuito do primeiro, no sentido de separar concerns que são tratados em uma mesma classe. Neste caso o concern que será separado em um aspecto é Validação, que é tratado na classe ManagerImpl junto das regras de negócio, e em outras classes como AOMHelper. Como este concern já foi refatorado anteriormente, existem classes e interfaces no projeto que lidam com as regras de validação, que estão no pacote br.ufpe.cin.rgms.base.validation.

2.3.1. Situação anterior

(18)

No código da classe ManagerImpl temos o seguinte:

2.3.1.1. ManagerImpl.java

public class ManagerImpl<Tipo extends AbstractBusinessEntity> implements IManager<Tipo> { ...

protected void validar(Tipo tipo) throws RGMSException{ IValidationUtil validation =

(IValidationUtil) context.getBean("XMLValidationUtil"); if(!validation.validate(tipo)){

throw new RGMSException("Dados inválidos na inserção"); }

} ...

public void inserir(Tipo tipo) throws RGMSException { validar(tipo);

this.dao.adicionar(tipo); }

public List<Tipo> listar() {

return this.dao.listarTudo(); }

...

public void alterar(Tipo tipo) throws RGMSException{ this.validar(tipo);

this.dao.atualizar(tipo); }

... }

Estes são os métodos que lidam com validação nesta classe, enquanto todo o resto lida apenas com regras de negócio.

2.3.2. Valores observados

Pode-se observar que o código do ManagerImpl possui entrelaçamento de múltiplos

(19)

2.3.3. Princípios utilizados

No refatoramento, utilizamos o princípio de separação de concerns, deixando a validação dos dados separada das regras de negócio;

2.3.4. Refatorando o código

Todo o mecanismo de validação foi movido para o source folder criado, com o nome

src.aspects/validation (como pode ser visto abaixo). O intuito desse refatoramento é que esse

source folder pode ser tirado e o projeto deve continuar funcionando (mas sem o mecanismo de validação), de forma que seja um módulo, do qual nenhum outro dependa diretamente.

Após isso, foi definido o aspecto que liga a camada de negócios ao mecanismo de validação:

2.3.4.1. ValidationAspect.aj

privileged public aspect ValidationAspect { private static ApplicationContext context; static{

ValidationAspect.context =

new ClassPathXmlApplicationContext("classpath:applicationContext.xml"); }

private IValidationRuleFactory AOMHelper.factory; @Autowired

public void AOMHelper.setValidationRuleFactory(

@Qualifier(value="ValidationRuleFactory") IValidationRuleFactory factory){ this.factory = factory;

(20)

}

private Collection<IValidationRule> TipoPropriedade.rules; public TipoPropriedade.new(String tipo, String text,

Collection<IValidationRule> rules){ this(tipo, text);

this.setRules(rules); }

public Collection<IValidationRule> TipoPropriedade.getRules() { return rules;

}

public void TipoPropriedade.setRules(Collection<IValidationRule> rules) { this.rules = rules;

}

pointcut loadAOMAttribute(AOMHelper helper, TipoPublicacao tipo, Element attribute)

: execution(public TipoPropriedade AOMHelper.loadAttributes(TipoPublicacao, Element)) && args(tipo,attribute) && this(helper);

TipoPropriedade around(AOMHelper helper, TipoPublicacao tipo, Element attribute) : loadAOMAttribute(helper, tipo, attribute){

TipoPropriedade ret = proceed(helper, tipo, attribute); XMLHelper xmlHelper = XMLHelper.getInstance();

Collection<IValidationRule> rules = new ArrayList<IValidationRule>(); for(Element rule : xmlHelper.getElements(attribute, "rule")){

Hashtable<String, String> map = new Hashtable<String, String>(); for(Element param : xmlHelper.getElements(rule, "param")){

String paramName = xmlHelper.getContent(xmlHelper.getMatchingElement(param, "name")); String value = xmlHelper.getContent(xmlHelper.getMatchingElement(param, "value")); map.put(paramName, value); } rules.add(helper.factory.createValidationRule(

xmlHelper.getContent(xmlHelper.getMatchingElement(rule, "class")), map)); }

ret.setRules(rules); return ret;

}

public static boolean validarAOM(Publicacao publicacao){ AOMHelper helper = AOMHelper.getInstance();

TipoPublicacao tipo = helper.getTipoPublicacao(publicacao.getTipo()); boolean valid = true;

(21)

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; }

public static void validar(AbstractBusinessEntity tipo) throws RGMSException { IValidationUtil validation =

(IValidationUtil) ValidationAspect.context.getBean("XMLValidationUtil"); if(!validation.validate(tipo)){

throw new RGMSException("Dados inválidos"); }

if(tipo instanceof Publicacao){

if(!ValidationAspect.validarAOM((Publicacao)tipo)){ throw new RGMSException("Dados inválidos"); }

} }

pointcut insertOrUpdate(AbstractBusinessEntity value) : (execution(public void ManagerImpl.inserir(..))

|| execution(public void ManagerImpl.alterar(..))) && args(value);

before(AbstractBusinessEntity value) throws RGMSException : insertOrUpdate( value ){

ValidationAspect.validar(value); }

(22)

2.3.5. Situação posterior

Após isso, a classe ManagerImpl ficou dessa forma:

2.3.5.1. ManagerImpl.java

public class ManagerImpl<Tipo extends AbstractBusinessEntity> implements IManager<Tipo> { protected Dao<Tipo> dao;

public Dao<Tipo> getDao() { return dao;

}

public void setDao(Dao<Tipo> dao) { this.dao = dao;

}

public void inserir(Tipo tipo) throws RGMSException { this.dao.adicionar(tipo);

}

public List<Tipo> listar() {

return this.dao.listarTudo(); }

public Tipo consultarUnicoResultado(long id) { return this.dao.procurar(id);

}

public void remover(Tipo tipo){ this.dao.remover(tipo); }

public void remover(long id){ this.dao.remover(id); }

public void alterar(Tipo tipo) throws RGMSException{ this.dao.atualizar(tipo);

}

public void rebind(Tipo tipo){ this.dao.rebind(tipo); }

public Tipo searchOnUniqueField(String field, Object value) { return this.dao.queryOnUniqueField(field, value); }

(23)

public List<Tipo> list(HashMap<String, Object> restrictions) { return this.dao.queryOnRestrictionMap(restrictions); }

public boolean exists(Tipo membro, String[] fields) { return this.dao.exists(membro, fields); }

public List<Tipo> consulta(String atributo, String query){ return this.dao.consulta(atributo,query);

} }

2.3.5.2. Impactos do refatoramento

Dentre os impactos desse refatoramento, tivemos:

● Independência do mecanismo de validação das regras de negócio, podendo ser colocado ou removido apenas com o uso do source folder criado. O código do projeto funciona adequadamente sem o source folder validation (como pode ser visto abaixo) quanto com o mesmo.

● Separação dos concerns, de forma que não há mais entrelaçamento dos dois concerns mencionados no problema no ManagerImpl e no AOMHelper.

(24)

2.4. Servlets com código bastante similar

No projeto, há um certo número de Servlets com código bastante parecido, que basicamente obtém um objeto, o coloca como atributo e depois redireciona a um jsp. Outros Servlets apenas preprocessam algo e depois redirecionam para um jsp.

2.4.1. Situação anterior

A seguir mostraremos o gráfico dos clones ilustrando o problema:

(25)

2.4.2. Valores observados

Percebe-se que esses Servlets possui código repetitivo entre si e não definem ponto

de extensão para outras classes similares.

2.4.3. Princípios utilizados

Vamos usar os princípios de minimização de repetições e definição de ponto de

extensão.

2.4.4. Refatorando o código

Três Servlets básicos foram criados:

2.4.4.1. AbstractRedirectServlet.java

Esse Servlet cobre os do tipo: DetalharServlet, RemoverServlet, AlterarServlet,

DeslogarServlet.

public abstract class AbstractRedirectServlet extends HttpServlet { private static final long serialVersionUID = 8077470282316643830L; private String redirectTo;

public AbstractRedirectServlet(String redirectTo) { this.redirectTo = redirectTo;

}

(26)

@Override

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

this.process(request, response);

RequestDispatcher view = request.getRequestDispatcher(this.redirectTo); view.forward(request, response);

}

@Override

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

this.doGet(request, response); }

}

2.4.4.2. AbstractBinaryWriterServlet.java

Esse Servlet cobre os do tipo: FotoServlet, PdfServlet. public abstract class AbstractBinaryWriterServlet extends HttpServlet {

private static final long serialVersionUID = -784138797313843365L; public abstract byte[] getBinary(HttpServletRequest request); public void writeResponseHeader(HttpServletResponse response){

response.setHeader("Cache-Control", "no-store"); response.setHeader("Pragma", "no-cache"); response.setDateHeader("Expires", 0); }

@Override

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

byte[] binary = this.getBinary(request); this.writeResponseHeader(response);

ServletOutputStream out = response.getOutputStream(); out.write(binary);

out.flush(); out.close(); }

(27)

2.4.4.3. RGMSFiltrarServlet.java

Esse foi o Servlet que mais remove repetição de código nas suas subclasses que são os Servlets do tipo FiltrarServlet.

public abstract class RGMSFiltrarServlet<T> extends RGMSFormServlet { private static final long serialVersionUID = -1865877348749078372L; private String redirectTo;

private String listField; private String[] filter;

public RGMSFiltrarServlet(String redirectTo, String[] filter, String listField) { this.redirectTo = redirectTo;

this.filter = filter; this.listField = listField; }

public abstract List<T> filter(HashMap<String, Object> map); @Override

protected void doPost(HttpServletRequest request,

HttpServletResponse response) throws IOException, ServletException { for(String f : filter){

this.addField(f, request, this.getFormfields()); }

List<T> filterList = this.filter(this.getFormfields()); request.setAttribute(listField, filterList);

request.setAttribute("campos", this.getFormfields());

RequestDispatcher view = request.getRequestDispatcher(this.redirectTo); view.forward(request, response);

} }

(28)

2.4.5. Situação posterior

Para ilustrar a situação posterior, mostraremos tanto o gráfico dos clones como a Polymetric View.

2.4.5.1. Polymetric View

De preto, temos o AbstractRedirectServlet; de vermelho, o RGMSFiltrarServlet (pela altura onde os nós abaixo estão, pode-se perceber quanto de código pôde ser reusado nas superclasses) e de azul, temos os dois

(29)

2.4.5.2. Gráfico dos clones

Dois conjuntos de clones foram eliminados, restando apenas 8. Os clones restantes parecem pouco interessantes. Apenas metade deles possui LEN >= 50. Os que apresentam LEN marcados como vermelho são apenas clones nos Servlets de Detalhar e Remover em código que de fato representa 3 linhas de código (se desconsiderarmos as declarações).

(30)

2.4.5.3. Impactos do refatoramento

Os impactos do refatoramento foram:

● definição de pontos de extensão para diversos Servlets;

● eliminação ou redução de repetição de código entre alguns Servlets, de modo que o clone de tamanho máximo não representa um clone relevante.

3. Adicionando funcionalidades novas

Nesta etapa, uma funcionalidade opcional prevista nos requisitos do sistema será adicionada. Utilizaremos para isso os benefícios da técnica de orientação a aspectos para permitir o acréscimo de tal funcionalidade apenas aos clientes que a desejarem.

3.1. Funcionalidade escolhida

A funcionalidade escolhida foi:

O sistema deve oferecer a possibilidade de atualizar feeds rss ou posts no twitter com base nas modificações feitas.

No cadastro do sistema, solicita-se o login do usuário no Twitter, de valor apenas informativo, pois o Twitter exige que uma autenticação seja fornecida pelo usuário para confirmar que ele deseja que a aplicação possa usar sua conta para mandar mensagens e etc, utilizando apenas esta autorização para as operações com a API. A princípio pensou-se em fornecer também a senha, mas foi considerado um pedido invasivo.

Dessa forma, após o cadastro o sistema exibe uma url a qual deve ser acessada pelo usuário para permitir o acesso pela aplicação. Uma vez feito isso, o sistema poderá informar via twitter alterações / inserções de publicação do sistema daquele usuário.

(31)

Tela de cadastro com informações do Twitter

(32)

Tela do Twitter que deve ser visitada pelo usuário

Tela mostrada quando o usuário permite o acesso da aplicação, com o código PIN que deve ser colocado na tela do RGMS mostrada acima

(33)

Tweet enviado automaticamente pelo RGMS indicando uma alteração numa publicação de um membro

3.2. Implementando a funcionalidade

Para a implementação dessa funcionalidade de forma opcional, foi necessário utilizar as técnicas de compilação condicional e orientação a aspectos. A orientação a aspectos foi utilizada para inserir atributos à classe Membro, que agora deveria também guardar informação sobre a conta do twitter do usuário, e para definir o comportamento que será adicionado para permitir a notificação por meio de status do twitter. A compilação condicional foi utilizada com o Velocity, que ajudou a inserir componentes nas páginas .jsp necessárias, e a definir o mapeamento de um Servlet que precisou ser definido no web.xml.

3.2.1. Adicionando o módulo do twitter: src.aspects/twitter

Primeiramente, para comunicação entre a aplicação e a API do twitter, foi utilizada a biblioteca Twitter4j, que é opensource e bem simples de manipular. Foi definido um novo source folder, o src.aspects/twitter, que separa todo o concern de atualização de status. Este source ficou com a seguinte arquitetura:

Dentro dele temos a classe TwitterHandler, que é apenas uma abstração das funcionalidades que são necessárias da API do twitter. Ela lida com a biblioteca Twitter4j diretamente.

(34)

3.2.1.1 TwitterHandler.java

public class TwitterHandler {

private static TwitterFactory factory;

private static final String consumerKey = "RvDjOJaByJUqQoVNtorQtg";

private static final String consumerSecret = "Oy6ttJxlHzNLz7nUaZYsd9c05yXrql4xNpfJpjPMXo"; private static TwitterFactory getTwitterFactory(){

if(factory == null){

factory = new TwitterFactory(); }

return factory; }

public static void updateStatus(AccessToken accessToken, String newStatus) throws TwitterException{

Twitter twitter = getTwitterFactory().

getOAuthAuthorizedInstance(consumerKey, consumerSecret, accessToken); twitter.updateStatus(newStatus);

}

public static RequestToken getRequestToken() throws TwitterException{ Twitter twitter = getTwitterFactory().getInstance();

twitter.setOAuthConsumer(consumerKey, consumerSecret); RequestToken requestToken = twitter.getOAuthRequestToken(); return requestToken;

}

public static AccessToken getAccessToken(RequestToken requestToken, String pin) throws TwitterException {

Twitter twitter = getTwitterFactory().getInstance(); twitter.setOAuthConsumer(consumerKey, consumerSecret); AccessToken accessToken = null;

if(pin.length() > 0){

accessToken = twitter.getOAuthAccessToken(requestToken, pin); }else{

throw new IllegalArgumentException(); }

return accessToken; }

(35)

Os atributos consumerKey e consumerSecret foram criados pelo Twitter para a aplicação que foi cadastrada no mesmo. Ela permite que a aplicação possa ser reconhecida e autorizada por usuário da rede. Temos depois o aspecto TwitterUpdateAspect, que cria todos os pointcuts necessários para a atualização pelo twitter.

3.2.1.2 TwitterUpdateAspect.aj

privileged public aspect TwitterUpdateAspect { private String Membro.twitterAccount; @Lob

private byte[] Membro.accessToken;

private Boolean Membro.atualizarTwitterStatus; @Basic

public String Membro.getTwitterAccount(){ return twitterAccount;

}

public void Membro.setTwitterAccount(String newAccount){ twitterAccount = newAccount;

} @Basic

public Boolean Membro.getAtualizarTwitterStatus(){ return atualizarTwitterStatus;

}

public void Membro.setAtualizarTwitterStatus(Boolean b) { atualizarTwitterStatus = b;

}

public AccessToken Membro.getAccessToken(){ AccessToken ret = null;

if(accessToken != null){

ret = (AccessToken) SerializationUtils.deserialize(accessToken); }

return ret; }

public void Membro.setAccessToken(AccessToken newAT){ accessToken = SerializationUtils.serialize(newAT); }

(36)

public void Membro.updateTwitterStatus(String status) throws RGMSException { try{

TwitterHandler.updateStatus(getAccessToken(), status); } catch(TwitterException e){

throw new RGMSException(e.getMessage()); }

}

pointcut alterarModoUsoTwitter(HttpServletRequest request, Membro membro) : (

execution(protected void AdicionarMembroServlet.handleObject(HttpServletRequest, Membro)) ||

execution(protected void AlterarDadosMembroServlet.handleObject(

HttpServletRequest, Membro)) )

&&

args(request, membro);

after(HttpServletRequest request, Membro membro) throws RGMSException : alterarModoUsoTwitter(request, membro) {

if(membro.getAtualizarTwitterStatus()

&& membro.getAccessToken() == null){ try { RequestToken requestToken = TwitterHandler.getRequestToken(); request.getSession().setAttribute("twitterRequest", requestToken); } catch (TwitterException e) {

throw new RGMSException("Erro ao tentar acessar o Twitter."); }

} }

pointcut inserirPublicacao(Membro membro, Publicacao publicacao) :

execution(public void Facade.inserirPublicacao(Membro, Publicacao)) && args(membro, publicacao);

pointcut alterarPublicacao(Membro membro, Publicacao publicacao) :

execution(public void Facade.alterarPublicacao(Membro, Publicacao)) && args(membro, publicacao);

pointcut removerPublicacao(Membro membro, Publicacao publicacao) :

execution(public void Facade.removerPublicacao(Membro, Publicacao)) && args(membro, publicacao);

(37)

after(Membro membro, Publicacao publicacao) returning throws RGMSException : inserirPublicacao(membro, publicacao) {

try {

updateMembroStatus(membro, publicacao, "Esta nova publicação acaba de ser inserida no Research Group Management System. Veja esta publicação no site agora mesmo!");

} catch (Exception e) {

throw new RGMSException("Erro ao acessar o Twitter. Certifique-se de que esta aplicação tem acesso à sua conta.");

} }

after(Membro membro, Publicacao publicacao) returning throws RGMSException : alterarPublicacao(membro, publicacao) {

try {

updateMembroStatus(membro, publicacao, "Esta publicação foi atualizada no Research Group Management System. Veja esta publicação no site agora mesmo!");

} catch (Exception e) {

throw new RGMSException("Erro ao acessar o Twitter. Certifique-se de que esta aplicação tem acesso à sua conta.");

} }

after(Membro membro, Publicacao publicacao) returning throws RGMSException : removerPublicacao(membro, publicacao) {

try {

updateMembroStatus(membro, publicacao, "Esta publicação foi removida no Research Group Management System.");

} catch (Exception e) {

throw new RGMSException("Erro ao acessar o Twitter. Certifique-se de que esta aplicação tem acesso à sua conta.");

} }

private void updateMembroStatus(Membro membro, Publicacao publicacao, String message) throws FileNotFoundException, IOException, RGMSException {

if(membro.getAtualizarTwitterStatus()){ membro.updateTwitterStatus(String.format("%s: %s", publicacao.getTitulo(), message)); } } }

(38)

Para receber o PIN que o usuário fornece no cadastro, e com ele conseguir o AccessToken para a aplicação no twitter, foi necessário a criação de um Servlet.

3.2.1.3 RequestAccessServlet.java

public class RequestAccessServlet extends HttpServlet { @Override

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

String pin = request.getParameter("pin"); RequestToken requestToken =

(RequestToken) request.getSession().getAttribute("twitterRequest"); Membro membro = Facade.getInstance().getMembro(

((Membro) request.getSession().getAttribute("usuario")).getEmail() );

try {

AccessToken at = TwitterHandler.getAccessToken(requestToken, pin); membro.setAtualizarTwitterStatus(true); membro.setAccessToken(at); System.out.println(membro.getAtualizarTwitterStatus()); } catch (TwitterException e) { e.printStackTrace(); membro.setAtualizarTwitterStatus(false);

request.setAttribute("membrostatus", "Erro ao tentar acesso ao Twitter."); }

try{

Facade.getInstance().alterarMembro(membro); } catch(Exception e1){

e1.printStackTrace(); // nao deve acontecer }

request.getSession().setAttribute("twitterRequest", null);

request.setAttribute("membrostatus", "Aplicação liberada para acesso ao Twitter."); RequestDispatcher view = request.getRequestDispatcher("membrostatus.jsp");

view.forward(request, response); }

(39)

3.2.2. Alterando as páginas

As páginas inserirmembro.jsp e alterarmembro.jsp são agora originadas a partir de um template do Velocity pre-processado pelo RGMSGenerator. O velocity.xml é agora:

3.2.2.1. velocity.xml

<velocity> <files>

<preprocessing input="src/hibernate.cfg.vm" output="src/hibernate.cfg.xml" />

<preprocessing input="WebContent/WEB-INF/web.vm" output="WebContent/WEB-INF/web.xml" />

<preprocessing input="WebContent/adicionarpublicacao.vm" output="WebContent/adicionarpublicacao.jsp" /> <preprocessing input="WebContent/adicionarmembro.vm" output="WebContent/adicionarmembro.jsp" /> <preprocessing input="WebContent/alterarmembro.vm" output="WebContent/alterarmembro.jsp" /> </files> <context> <variable name="publicacao_from_bibtex">true</variable> <variable name="driver">org.postgresql.Driver</variable> <variable name="dialect">org.hibernate.dialect.PostgreSQLDialect </variable> <variable name="url">jdbc:postgresql://localhost/postgres/rgms </variable> <variable name="twitter">true</variable> <variable name="user">postgres</variable> <variable name="password">password</variable> </context> </velocity>

3.2.2.2. web.vm

O template do web.xml ganhou o seguinte trecho:

#if ($twitter) <servlet> <servlet-name>RequestAccess</servlet-name> <servlet-class>br.ufpe.cin.rgms.twitter.controle.RequestAccessServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>RequestAccess</servlet-name> <url-pattern>/RequestAccess.do</url-pattern> </servlet-mapping> #end

(40)

3.2.2.3. alterarmembro.vm e inserirmembro.vm

O trecho de código do twitter só fica presente se a funcionalidade estiver habilitada.

#if ($twitter)

<p>

<LABEL for="twitteraccount">

<%out.println(Properties.getProperty(this.getServletContext(),"twitteraccount") + ":");%> </LABEL>

<input onkeypress="return noenter();" type="text" size="10" name="twitteraccount" value="<% if(membro.getTwitterAccount() != null) out.print(membro.getTwitterAccount()); %>">

</p>

<p style="text-align: center;">

<INPUT type="checkbox" name="atualizartwitterstatus" for="atualizartwitterstatus" value="true" <% if(membro.getAtualizarTwitterStatus() != null && membro.getAtualizarTwitterStatus()) {out.print("checked=\"checked\"");}%>/>

<LABEL><%out.println(Properties.getProperty(this.getServletContext(),"atualizartwitterstatus") + ":");%></LABEL>

</p>

Referências

Documentos relacionados