Executando expressões lógicas dinâmicas em java

Esses dias me deparei com o seguinte problema: como gerar expressões lógicas a partir de atributos anotados de um objeto como no exemplo abaixo, que são construídas pelo usuário através de uma interface gráfica?

public class Contact {

  @MyAnnotion
  private String name;

  @MyAnnotation(operators={Operator.Equal, Operator.GreaterThan....})
  private Date birthday;

  @MyAnnotion 
  private String address;
 
  @MyAnnotation(operators={Operator.Equal, Operator.GreaterThan....})
  private Integer age;

}

Digamos que queremos construir regras como “nome contains ‘Joao’ AND birthday <= '10/12/2013' OR age < 18", ou seja queremos criar regras para serem aplicadas nos objetos Contacts e selecionar os objetos de acordo com aqueles que atendem a regra ou não. Construir a regra em si não é um grande problema. O desafio está em executar a regra construída obedecendo a precedência dos operadores lógicos, e havendo parênteses, aumenta a complexidade, pois temos que executar os mais internos primeiro.

Uma das soluções, apontada por um amigo meu, é gerar um programa java script e utilizar a engine de script para avaliar a expressão lógica gerada. Por exemplo a expressão “nome igual ‘Eduardo’ and idade > 10” seria traduzido para algo assim:

if('Eduardo' == 'Eduardo' && 30 > 10){ return true; } else { return false; }

Basicamente geramos uma string contendo um código semelhante ao mostrado acima substituindo o campos (nome,idade no exemplo) pelos seus respectivos valores e os operadores pelos seus correspondentes na linguagem javascript.

Abaixo segue o código completo do programa em java, mostrando a solução que adotamos e que funcionou muito bem, muito superior ao que tínhamos antes que gerava um SQL, mas tinha um problema grave, pois quando estamos criando ou editando um objeto ainda não colocamos o objeto (Contact no caso desse artigo) no banco, logo as regras que estão lá não conseguirão ser aplicadas sobre os novos dados. Mas salvar o objeto no banco para poder aplicar as regras e apagá-lo caso, o usuário desista da edição ou criação do novo contato, é um convite para erros de inconsistência no banco. O jeito foi executar as regras sem precisar do banco e essa solução de usar a script engine da JVM caiu como uma luva.

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class ExecutaRegra {
	
	public static void main(String[] args){
		try {
			String expressaoJavascript = "if('Teste'=='Teste1' || 1==0){r=true} else {r=false}";

			ScriptEngineManager mgr = new ScriptEngineManager();
			ScriptEngine jsEngine = mgr.getEngineByName("JavaScript");
			boolean result = (Boolean) jsEngine.eval(expressaoJavascript);
			
			System.out.println("Resultado: " + result);
			
		} catch (ScriptException ex) {
			ex.printStackTrace();
		} catch (SecurityException e) {
			e.printStackTrace();
		} 
	}
}
Advertisements

Jetty com JSF2, JPA2 e Weld

Seguindo aquele experimento (post) de utilizar o jetty para desenvolver um aplicativo com JSF e JPA, também surgiu a necessidade de ver como fazer para uilizar o Weld, o framework de injeção de dependência. Basicamente, para poder utilizar o Weld no jetty deve-se seguir os seguintes passos:

1) No web.xml criamos um listener para o weld, como mostrado nas linhas em destaque abaixo:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
  </welcome-file-list>
  <context-param>
    <param-name>facelets.DEVELOPMENT</param-name>
    <param-value>true</param-value>
  </context-param>
  <servlet>
    <servlet-name>Faces Servlet</servlet-name>
    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>*.jsf</url-pattern>
  </servlet-mapping>
  <listener>
    <listener-class>org.jboss.weld.environment.servlet.Listener</listener-class>
  </listener>
  
   <listener>
    <listener-class>com.sun.faces.config.ConfigureListener</listener-class>
  </listener>
  
  <resource-env-ref>
    <description>Object factory for the CDI Bean Manager</description>
    <resource-env-ref-name>BeanManager</resource-env-ref-name>
    <resource-env-ref-type>javax.enterprise.inject.spi.BeanManager</resource-env-ref-type>
  </resource-env-ref>
  
   <context-param>
<param-name>primefaces.THEME</param-name>
<param-value>bootstrap</param-value>
</context-param>
  
<!-- <context-param> -->
<!-- <param-name>primefaces.skin</param-name> -->
<!-- <param-value>none</param-value> -->
<!-- </context-param> -->
  
</web-app>

e adicionar o arquivo jetty-env.xml no WEB-INF com o seguinte conteúdo:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://jetty.mortbay.org/configure.dtd">
<Configure id="webAppCtx" class="org.mortbay.jetty.webapp.WebAppContext">
<New id="beanManager" class="org.mortbay.jetty.plus.naming.Resource">
   <Arg>
     <Ref id="webAppCtx" />
   </Arg>
  <Arg>BeanManager</Arg>
  <Arg>
    <New class="javax.naming.Reference">
      <Arg>javax.enterprise.inject.spi.BeanManager</Arg>
      <Arg>org.jboss.weld.resources.ManagerObjectFactory</Arg>
     <Arg />
    </New>
</Arg>
</New>
</Configure>

Um projeto completo está disponível aqui.

para rodar basta entrar no diretório e digitar:

mvn jetty:run

depois, acesse o link: http://localhost:8080/contactlist/list.jsf

A diferença de projeto para o anteior, que não usava o weld, é que este usa o hibernate e o sqlite como banco de dados. Então pra quem quer usar o jetty com essas duas tecnologias vale a pena dar uma olhada no pom.xml.

Usando JSF2 e JPA2 com Jetty

Trabalhar com java para web muitas vezes é um exercício de paciência enorme, principalmente para fazer alguma coisa bem simples. Você pode gastar muito tempo para configurar direito o maven (praticamente nunca trabalhei em projeto que usasse maven ou ant), isso quando não se perde mais de um dia pra isso. Outro detalhe é o servidor de aplicação (jboss por exemplo) que entre vários restarts para fazer deploy toma muito tempo, por que até hoje não vi nenhum desses servidores de aplicação java que, mesmo com hot-deploy habilitado não dê um PermGem exception depois de seguidos re-deploys.

Resolvi tentar o jetty, que me parecia bem rápido e é utilizado de forma embarcada, em aplicativo como o Solr, que tem uma boa performance.

O Jetty é um servidor web feito em java muito leve. Apesar de ser um container servlet podemos estendê-lo para também ser um container EJB, por exemplo. Ele não vem com toda a stack do JEE, mas pesquisando um pouquinho na internet, consegui criar um pom.xml que permite usar JSF 2 (primefaces) e JPA 2 (Ebean, mas dá para usar o hibernate, simplesmente adicionando no pom as dependências).

A principal vantagem do jetty é a rapidez do deploy já que no boot ele não tem tanta coisa pedurada como o Jboss, que leva quase 8 segundos bootando na minha máquina.

Abaixo listo o pom.xml para um projeto baseado no Jetty, JSF e JPA:

 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>sh.app.sample-projects</groupId>
<artifactId>jsf2-jetty-maven</artifactId>
<version>SNAPSHOT</version>
<name>JSF 2, JPA 2, Jetty and Maven together</name>

<dependencies>
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-api</artifactId>
<version>${com.sun.faces.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-impl</artifactId>
<version>${com.sun.faces.version}</version>
<scope>runtime</scope>
</dependency>

<dependency>
<groupId>commons-fileupload</groupId>
<artifactId> commons-fileupload</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId> commons-io</artifactId>
<version>2.1</version>
</dependency>

<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.7.2</version>
</dependency>

<dependency>
<groupId>org.avaje</groupId>
<artifactId>ebean-spring</artifactId>
<version>2.7.7</version>
</dependency>

<dependency>
<groupId>postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.1-901.jdbc4</version>
</dependency>
    
     <!-- primefaces -->
<dependency>
<groupId>org.primefaces</groupId>
<artifactId>primefaces</artifactId>
<version>3.4.2</version>
</dependency>

<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>2.2.8</version>
<scope>compile</scope>
</dependency>

<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.0.0.GA</version>
<scope>compile</scope>
</dependency>

<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
</dependency>

<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>

<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>

<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2</version>
</dependency>

<dependency>
<groupId>asm</groupId>
<artifactId>asm</artifactId>
<version>3.1</version>
</dependency>

<dependency>
<groupId>javax.transaction</groupId>
<artifactId>jta</artifactId>
<version>1.1</version>
</dependency>

  <!-- SQLite database JDBC -->
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.6.0</version>
</dependency>

</dependencies>


<repositories>
<repository>
<id>prime-repo</id>
<name>PrimeFaces Maven Repository</name>
<url>http://repository.primefaces.org</url>
<layout>default</layout>
</repository>
<repository>
<id>JBoss repository</id>
<url>http://repository.jboss.com/maven2/</url>
</repository>
</repositories>

<build>
<finalName>contactlist</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.source.level}</source>
<target>${java.source.level}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>${jetty-maven-plugin.version}</version>
<configuration>
    <webApp>
     <contextPath>/contactlist</contextPath>
   </webApp>
   </configuration>
</plugin>
      
     <!-- Tomcat plugin for embedded tomcat -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>tomcat-maven-plugin</artifactId>
<configuration>
<path>/contactlist</path>
</configuration>
</plugin>

</plugins>
</build>

<properties>
<encoding>UTF-8</encoding>

<java.source.level>1.6</java.source.level>
<spring.version>3.1.3.RELEASE</spring.version>
<com.sun.faces.version>2.1.7</com.sun.faces.version>
<jetty-maven-plugin.version>8.1.2.v20120308</jetty-maven-plugin.version>
<org.slf4j.version>1.6.5</org.slf4j.version>
<junit.version>4.10</junit.version>
</properties>

</project>

Um exemplo de projeto pode ser encontrado aqui

Para rodar entre no diretório do projeto e digite:

mvn jetty:run

e depois acessar a url:

http://localhost:8080/contactlist/list.jsf

CDI Alguns conceitos

Abaixo uma apresentação, também disponível no slideshare, sobre os conceitos do CDI (Contexts and Dependency Injection for Java EE).