Gastaldi's Blog

Mais um blog sobre Java …

Arquivos Mensais: abril 2010

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