Gastaldi's Blog

Mais um blog sobre Java …

Arquivos da Categoria: Uncategorized

Liberado Seam Reports 3.0.0.Alpha1 !

Mais um módulo Seam 3 (Reports) foi liberado ! A versão 3.0.0.Alpha1 é a primeira versão do Seam Reports já lançada.

Seam Reports é uma extensão CDI que fornece uma API simples para compilação, população e renderização de relatórios utilizando frameworks de relatório já disponíveis no mercado.

Devo postar mais informações sobre este módulo em breve.

Mais informações em: http://www.seamframework.org/Seam3/ReportsModule

JCP Member

Finalmente recebi a confirmação de registro no JCP (http://jcp.org). Com isso, posso criar JSRs, juntar-me a expert groups e enfim, envolver-me mais com a comunidade Java.

Pode conferir em http://jcp.org/en/participation/members/G.

Multi-Tenância no Hibernate

Se você já usou o Hibernate, sabe que ele é um framework poderoso para mapeamento objeto-relacional. Neste post apresento como podemos resolver de uma forma simples uma necessidade bastante comum: Acessar diversos bancos de dados com apenas um hibernate.cfg mapeado.

A solução para este problema depende na forma de como os dados são recuperados do seu database:

1) Se os bancos estiverem separados por conexões diferentes, crie uma classe que implemente a interface ConnectionProvider e configure no Hibernate.

2) Se o banco for gerenciado por apenas um pool de conexões, mas acessa dados de outros bancos a partir da mudança do SCHEMA, você pode utilizar um Interceptor para isso:

import static org.apache.commons.lang.StringUtils.defaultIfEmpty;

import java.util.regex.Pattern;
import org.hibernate.EmptyInterceptor;

/**
 * Interceptor para alterar SCHEMA dinamicamente
 * 
 * @author george
 * 
 */
public class SchemaReplaceInterceptor extends EmptyInterceptor {

	private static final long serialVersionUID = 1L;
	private Pattern pattern;

	private String defaultSchema;

	public SchemaReplaceInterceptor(String token, String defaultSchema) {
		pattern = Pattern.compile(token);
		this.defaultSchema = defaultSchema;
	}

	@Override
	public String onPrepareStatement(String sql) {
		String novoSchema = recuperaNomeSchema();
		String novoSQL = pattern.matcher(sql).replaceAll(novoSchema);
		return super.onPrepareStatement(novoSQL);
	}

	protected String recuperaNomeSchema() {
             //TODO: Implementar - Recuperar de um ThreadLocal 
	}
}

Serviço REST para postar mensagens no Pastebin.com

O PasteBin é um serviço que permite que você publique trechos de código e conteúdo temporário em geral. Normalmente para evitar ter que salvar num arquivo texto e transmitir via email, ou outro protocolo, basta apenas você entrar no site, colar o conteúdo que você deseja compartilhar e aí disponibilizar a URL que ele irá gerar para quem você quiser.

É possível integrar com o JBoss RestEasy para que essa publicação seja encapsulada no Client Framework, usando a API definida em http://pastebin.com/api.php

Crie a interface de serviço

import javax.ws.rs.Consumes;
import javax.ws.rs.FormParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import org.jboss.resteasy.annotations.Form;

/**
 * Publica um conteúdo no Pastebin.com
 *
 * @author George Gastaldi
 * 
 */
@Path("/")
public interface PasteBinService {

	/**
	 * Publica o código fonte e retorna a URL publicada
	 * 
	 * @param pasteCode
	 * @return
	 */
	@POST
	@Path("/api_public.php")
	@Consumes(MediaType.TEXT_PLAIN)
	@Produces(MediaType.TEXT_PLAIN)
	public String publish(@FormParam("paste_code") String pasteCode);
	/**
	 * Publica o código fonte e retorna a URL publicada
	 * 
	 * @param pasteCode
	 * @return
	 */
	@POST
	@Path("/api_public.php")
	@Consumes(MediaType.TEXT_PLAIN)
	@Produces(MediaType.TEXT_PLAIN)
	public String publish(@FormParam("paste_code") String pasteCode, @Form PasteBinParams params);
}

Aqui estou usando um recurso bacana do RestEasy que é o encapsulamento dos parametros em uma classe Java a parte.
Aqui está o código-fonte da classe de parametros:


import javax.ws.rs.FormParam;

public class PasteBinParams {

	/**
	 * - for adding a title or name to your paste.
	 */
	@FormParam("paste_name")
	private String pasteName;

	/**
	 * - for sending confirmation email with paste link.
	 */
	@FormParam("paste_email")
	private String pasteEmail;

	/**
	 * - for using a certain subdomain.
	 */
	@FormParam("paste_subdomain")
	private String pasteSubdomain;

	/**
	 * - for making it public (0) or private (1).
	 */
	@FormParam("paste_private")
	private int pastePrivate;

	/**
	 * - for adding expiration date. N = Never, 10M = 10 Minutes, 1H = 1 Hour,
	 * 1D = 1 Day, 1M = 1 Month.
	 */
	@FormParam("paste_expire_date")
	private String pasteExpireDate;

	/**
	 * - for adding syntax highlighting.
	 */
	@FormParam("paste_format")
	private String pasteFormat;
     
         ... getters e setters omitidos ...
}

Agora é só testar !

	public static void main(String[] args) {
		// Necessário inicializar
		RegisterBuiltin.register(ResteasyProviderFactory.getInstance());
		PasteBinService service = ProxyFactory.create(PasteBinService.class,"http://pastebin.com");
		String url = service.publish("Hello REST World !");

		System.out.println("URL: " + url);

		PasteBinParams params = new PasteBinParams();
		params.setPasteName("George Gastaldi");
		params.setPasteEmail("gastaldi@apache.org");
		params.setPasteFormat("xml");
		params.setPasteSubdomain("gastaldi");
		params.setPasteExpireDate("10M");

		url = service.publish("<root><tag>Hello XML World !</tag></root>",
				params);
		System.out.println("URL: " + url);
	}

Serviço REST para conversão de moeda

Há algum tempo atrás, quando ainda estava conhecendo o JBoss RestEasy, encontrei um serviço que realiza a conversão de moedas baseado em um script CGI. O Site é este aqui : http://www.xe.com

Pois bem, o Java possui a classe Currency, e a saída do site é um trecho em HTML, logo, basta criarmos um MessageBodyReader para ler o conteúdo do trecho HTML e extrair as partes necessárias.

Comecei com a criação da interface REST:

package com.george.rest.currency;

import java.math.BigDecimal;
import java.util.Currency;

import javax.ws.rs.GET;
import javax.ws.rs.QueryParam;

/**
 * REST interface para recuperar Informações do site XE (http://www.xe.com) em tempo real
 * @author george
 *
 */
public interface ICurrencyConverter {

	public final static String REST_SERVICE_URL  = "http://www.xe.com/ucc/convert.cgi";

	/**
	 * Chama um serviço REST e devolve informações do câmbio atual
	 * @param amount
	 * @param from
	 * @param to
	 * @return
	 */
	@GET
	public abstract CurrencyConversion convert(
			@QueryParam("Amount") BigDecimal amount,
			@QueryParam("From") Currency from, 
			@QueryParam("To") Currency to);

}

Para permitir retornar mais informações (data da conversão, quantia, conversão de um valor para outro), decidi encapsular o retorno da chamada em um POJO:

package com.george.rest.currency;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Currency;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class CurrencyConversion implements Serializable {

	private static final long serialVersionUID = 1L;

	// USD
	private Currency sourceCurrency;
	// BRL
	private Currency targetCurrency;

	private Date rateTimestamp;

	private BigDecimal amount;

	// 1 USD = 2.21995 BRL
	private BigDecimal amountSourceToTarget;

	// 1 BRL = 0.450461 USD
	private BigDecimal amountTargetToSource;

	public Currency getSourceCurrency() {
		return sourceCurrency;
	}

	public Currency getTargetCurrency() {
		return targetCurrency;
	}

	public Date getRateTimestamp() {
		return rateTimestamp;
	}

	public BigDecimal getAmount() {
		return amount;
	}

	public BigDecimal getAmountSourceToTarget() {
		return amountSourceToTarget;
	}

	public BigDecimal getAmountTargetToSource() {
		return amountTargetToSource;
	}

	public static CurrencyConversion parse(String html) throws ParseException {
		DateFormat df = new SimpleDateFormat(
				"'Live rates at 'yyyy.MM.dd HH:mm:ss ZZZ");
		Pattern valores = Pattern
				.compile("<span class=\"XEsmall\">(.*)</span>");
		Matcher matcher = valores.matcher(html);
		Date data = (matcher.find()) ? df.parse(matcher.group(1)) : null;
		String sourceToTarget = (matcher.find()) ? matcher.group(1) : null;
		String targetToSource = (matcher.find()) ? matcher.group(1) : null;
		sourceToTarget = cleanCopyright(sourceToTarget);
		targetToSource = cleanCopyright(targetToSource);
		CurrencyConversion cc = new CurrencyConversion();

		Pattern p = Pattern
				.compile("([0-9\\.]+) (\\w{3}) = ([0-9\\.]+) (\\w{3})");
		Matcher matcher2 = p.matcher(sourceToTarget);
		if (matcher2.find()) {
			cc.amount = new BigDecimal(matcher2.group(1));
			cc.sourceCurrency = Currency.getInstance(matcher2.group(2));
			cc.amountSourceToTarget = new BigDecimal(matcher2.group(3));
			cc.targetCurrency = Currency.getInstance(matcher2.group(4));
			Matcher matcher3 = p.matcher(targetToSource);
			if (matcher3.find()) {
				cc.amountTargetToSource = new BigDecimal(matcher3.group(3));
			}
		}
		cc.rateTimestamp = data;
		return cc;
	}

	private static String cleanCopyright(String copyrightedString) {
		String ret = copyrightedString;
		if (ret != null) {
			int idx = ret.indexOf("<!--");
			ret = ret.substring(0, idx);
		}
		return ret;
	}

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("[");
		for (Field field : getClass().getDeclaredFields()) {
			try {
				sb.append(field.getName()).append("=").append(field.get(this))
						.append(", ");
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		sb.deleteCharAt(sb.length() - 1);
		sb.deleteCharAt(sb.length() - 1);
		sb.append("]");
		return sb.toString();
	}

}

É necessário escrever um MessageBodyReader para que o provider JAX-RS possa entender como traduzir o conteúdo HTML no objeto CurrencyConversion.

package com.george.rest.currency;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.text.ParseException;

import javax.ws.rs.Consumes;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.Provider;

@Provider
@Consumes("text/html")
public class CurrencyConversionMessageBodyReader implements
		MessageBodyReader<CurrencyConversion> {

	public boolean isReadable(Class<?> type, Type genericType,
			Annotation[] annotations, MediaType mediaType) {
		return type == CurrencyConversion.class || type == String.class;
	}

	public CurrencyConversion readFrom(Class<CurrencyConversion> type,
			Type genericType, Annotation[] annotations, MediaType mediaType,
			MultivaluedMap<String, String> httpHeaders, InputStream entityStream)
			throws IOException, WebApplicationException {
		String input = readInputStreamToString(entityStream);
		try {
			return CurrencyConversion.parse(input);
		} catch (ParseException e) {
			throw new WebApplicationException(e);
		}
	}

	private String readInputStreamToString(InputStream entityStream)
			throws IOException {
		byte[] b = new byte[1024];
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		for (int i = entityStream.read(b); i != -1; i = entityStream.read(b)) {
			baos.write(b, 0, i);
		}
		return baos.toString();
	}
}

Por último, mas não menos importante, está a chamada do serviço REST usando o JBoss RestEasy Client Framework.Neste exemplo, pretendo saber o valor de 1 USD (US Dollar) em BRL (Brazilian Real).

package com.george.app;

import java.math.BigDecimal;
import java.util.Currency;
import java.util.Locale;

import org.jboss.resteasy.client.ProxyFactory;
import org.jboss.resteasy.spi.ResteasyProviderFactory;

import com.george.rest.currency.CurrencyConversion;
import com.george.rest.currency.CurrencyConversionMessageBodyReader;
import com.george.rest.currency.ICurrencyConverter;

public class CurrencyTest {

	public static void main(String[] args) throws Exception {
		Currency brl = Currency.getInstance(new Locale("pt","BR"));
		Currency usd = Currency.getInstance(Locale.US);
		//Registra o Reader explicitamente
                ResteasyProviderFactory provider = ResteasyProviderFactory.getInstance();
		provider.addMessageBodyReader(CurrencyConversionMessageBodyReader.class);

		// Chama o serviço de conversão 
		ICurrencyConverter currencyConverter = ProxyFactory.create(ICurrencyConverter.class, ICurrencyConverter.REST_SERVICE_URL);
		CurrencyConversion response = 
			currencyConverter.convert(new BigDecimal("1.00"),brl,usd);
		System.out.printf("Rate Timestamp: %1$tF %tT %n", response.getRateTimestamp());
		System.out.printf("Amount: %.2f", response.getAmountSourceToTarget());
	}
}

Você terá uma saída semelhante a seguinte:

Rate Timestamp: 2010-04-27 01:49:28
Amount: 0,57

Registrar uma String no JNDI

Em muitas aplicações, existem parâmetros que variam de acordo com o ambiente (pools de conexão, roles, etc) e que são publicadas na árvore JNDI da aplicação. Para parametrizarmos uma string qualquer (seja ela uma URL, uma informação qualquer) muitos desenvolvedores preferem criar um arquivo de propriedades ou deixar fixo no código (bleargh !). Minha sugestão é utilizar o contexto JNDI. Para isso faça o seguinte:

Registre o serviço abaixo em um arquivo com sufixo -service.xml (pode ser no jboss-service.xml dentro do conf):

<mbean code="org.jboss.naming.JNDIBindingServiceMgr"
         name="jboss.tests:service=JNDIBindingServiceMgr">
      <attribute name="BindingsConfig" serialDataType="jbxb">
         <jndi:bindings
            xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
            xmlns:jndi="urn:jboss:jndi-binding-service:1.0"
            xs:schemaLocation="urn:jboss:jndi-binding-service:1.0 resource:jndi-binding-service_1_0.xsd">
            <jndi:binding name="urls/MinhaURL">
               <jndi:value type="java.lang.String">http://www.meuserver.com/service</jndi:value>
            </jndi:binding>
         </jndi:bindings>
      </attribute>
      <depends>jboss:service=Naming</depends>
   </mbean>

Na nossa aplicação web, devemos alterar o web.xml para incluir o seguinte resource-ref:

<resource-ref>
        <res-ref-name>QualquerCoisa</res-ref-name>
        <res-type>java.lang.String</res-type>
        <res-auth>Container</res-auth>
        <res-sharing-scope>Shareable</res-sharing-scope>
</resource-ref>	

e no jboss-web.xml :

<jboss-web>
	<resource-ref>
		<res-ref-name>QualquerCoisa</res-ref-name>
		<res-type>java.lang.String</res-type>
		<jndi-name>urls/MinhaURL</jndi-name>
	</resource-ref>
</jboss-web>

Pronto ! Agora é só fazer:

InitialContext ctx = new InitialContext();
String strNoJNDI = (String) ctx.lookup("java:comp/env/QualquerCoisa");

e você terá o valor publicado no seu servidor de aplicação (totalmente desacoplado da aplicação).

Registrando um MBean no JNDI

No JBoss, para registrar um serviço MBean no JNDI da sua aplicação (a fim de ser acessível remotamente), utilize o seguinte serviço:

	<mbean code="org.jboss.invocation.jrmp.server.JRMPProxyFactory"
		name="george.proxy:type=adaptor,name=MeuProxy,protocol=jrmp,service=proxyFactory">
		<!-- Use the standard JRMPInvoker from conf/jboss-service.xxml -->
		<depends optional-attribute-name="InvokerName">jboss:service=invoker,type=jrmp</depends>
		<!-- The target MBean -->
		<depends optional-attribute-name="TargetName">dominio:name=MeuMBean</depends>
		<!-- Where to bind the proxy factory -->
		<attribute name="JndiName">NomeJNDI</attribute>
		<!-- Invoke target method instead of invoke(Invocation mi) -->
		<attribute name="InvokeTargetMethod">true</attribute>
		<!-- Comma-separated list of exported interfaces -->
		<attribute name="ExportedInterfaces">InterfaceDoMBean</attribute>
		<!-- client-side interceptors -->
		<attribute name="ClientInterceptors">
			<interceptors>
				<interceptor>org.jboss.proxy.ClientMethodInterceptor</interceptor>
				<interceptor>org.jboss.proxy.SecurityInterceptor</interceptor>
				<interceptor>org.jboss.jmx.connector.invoker.client.InvokerAdaptorClientInterceptor</interceptor>
				<interceptor>org.jboss.invocation.InvokerInterceptor</interceptor>
			</interceptors>
		</attribute>
	</mbean>

Proposta de Design By Contract com Hibernate Validator

Há um tempo atrás, tive uma idéia de aplicar um Design by Contract através de anotações do Hibernate Validator, ou seja, permitir que durante a chamada de um parâmetro, fosse possível realizar validações dos parâmetros em si, e não apenas do estado da classe, algo assim:

public class Calculator {

   public int add(
      @NotNullParam(mssage="Parametro {1} não deve ser nulo !")
      @Positive(message = "Parametro {1} deve ser positivo. Atual: {0}")
      Integer a,

      @NotNullParam
      @Positive(message = "Segundo Parametro: {0}")
      Integer b) {

      return a + b;
   }
}

Assim, quando o método fosse chamado, a validação ocorreria nos parâmetros informados em tempo de execução.
Você pode conferir o código completo da implementação aqui.

Em tempo, encontrei um post do Ricardo Ferreira no seu blog Architecture Journal sobre o Spring DBC, uma nova proposta para subprojeto Spring criada por ele muito interessante : Introdução ao Spring DBC.

Plugins Maven para JIRA

O JIRA é uma ferramenta de issue tracking bastante usado em muitas empresas.
Porém, que muitas empresas precisam é de uma integração maior com as ferramentas de desenvolvimento, principalmente com o Maven.
Para tanto, criei um projeto no google code contendo plugins Maven que integram com o WebService do JIRA. Um dos plugins cria versões no JIRA conforme a versão especificada no pom.xml

Link para quem Quiser Conferir: http://code.google.com/p/jira-maven-plugins/

Tuning and Slimming JBossAS

Este link contém informações de como “tunar” o seu JBoss para obter o máximo de performance:

Tuning and Slimming JBossAS