Moneta 1.2 e nova JSR [Próximos passos]

Uma vez entregue a versão 1.1, que dentre diversas coisas corrige diversos bugs, o próximo passo será a modularização da money-api, tanto na sua API como na implementação. Atualmente lidamos com os seguintes pacotes:

  • javax.money – local aonde fica o core da API
  • javax.money.convert – Responsável por realizar a converção de valores, cotação
  • javax.money.format – Responsável pela formatação do Currency e MonetaryAmount
  • javax.money.spi – Service Provider Interface

Com essa nova proposta a ideia é tornar alguns pacotes opcionais:

  • javax.money : obrigatório
  • javax.money.convert: opcional
  • javax.money.format: opcional
  • javax.money.spi: opcional

Do lado da implementação, seguiríamos um passo semelhante, porém, dentro do módulo do convert ele será subdivido em cada implementação, ou seja, um módulo para convert IMF e um para convert ECB.

  • Módulo obrigatório
  • Módulo formatação
  • Módulo SPI
  • Módulo de convert IMF
  • Módulo de convert ECB

Lançamento do Moneta 1.1

 

A implementação de referência da JSR 354, money-api. Lança uma nova versão. O moneta, o nome da implementação de referência do money-api, vem algumas novidades além da correção de mais de vinte bugs. Dentre eles podemos destacar:

Além das correções foram adicionados novos métodos construtores para as implementações do MonetaryAmount.

  • zero(CurrencyUnit currency) – retorna um valor monetário igual a zero com a moeda informada.
  • ofMinor(CurrencyUnit currency, long amountMinor) – retorna o valor monetário a partir da moeda informada e do valor em centavos, por exemplo, o ofMinor(USD, 1234) retorna uma instância USD 12,34.
  • ofMinor(CurrencyUnit currency, long amountMinor, int factionDigits) – retorna o valor monetário a partir da moeda informada e do valor em centavos, por exemplo, o ofMinor(USD, 1234, 2) retorna uma instância USD 12,34. Diferente do método de criação anterior, utiliza o número de dígitos informado ao invés do número de dígitos da moeda.

 

MonetaryAmount money = Money.zero(dollar);
MonetaryAmount oneDollar = Money.ofMinor(dollar, 12_34);

MonetaryAmount money = FastMoney.zero(dollar);
MonetaryAmount oneDollar = FastMoney.ofMinor(dollar, 12_34);

MonetaryAmount money = RoundedMoney.zero(dollar);
MonetaryAmount oneDollar = RoundedMoney.ofMinor(dollar, 12_34);

Outro ponto é que a classe MonetaryUtil foi depreciada, o maior motivo é a duplicidade do nome, afinal, o termo util cabe qualquer coisa. Ao invés disso, foram criadas duas classes utilitárias:
A classe MonetaryOperators : utilitária da interface MonetaryOperator, nele contém diversas implementações interessantes que fazem a vida do desenvolvedor mais fácil, como por exemplo,

MonetaryAmount amount = ////USD 12.23
amount.with(MonetaryOperators.majorPart());//USD 12
amount.with(MonetaryOperators.minorPart());//USD 0.23
amount.with(MonetaryOperators.percent(10));//USD 1.223

MonetaryQueries: utilitária da interface MonetaryQuery, nele contém diversas implementações interessantes e que facilita a extração de informações do montante financeiro.

MonetaryAmount amount = //USD 12.32
amount.query(MonetaryQueries.convertMinorPart());//1232
amount.query(MonetaryQueries.extractMajorPart());//12
amount.query(MonetaryQueries.extractMinorPart());//32

Outra novidade está relacionado a cotação, agora é possível realizar buscas de cotação no IMF.

CurrencyUnit dollar = Monetary.getCurrency("USD");
CurrencyUnit real = Monetary.getCurrency("BRL");

MonetaryAmount money = FastMoney.of(10, dollar);
MonetaryAmount money2 = FastMoney.of(10, real);

LocalDate localDate = Year.of(2009).atMonth(Month.JANUARY).atDay(9);
ExchangeRateProvider provider = MonetaryConversions.getExchangeRateProvider(ExchangeRateType.IMF_HIST);
ConversionQuery query = ConversionQueryBuilder.of().setTermCurrency(dollar).set(localDate).build();

CurrencyConversion currencyConversion = provider.getCurrencyConversion(query);
MonetaryAmount result = currencyConversion.apply(money2);
MonetaryAmount monetaryAmount = money.add(result);

Também foi adicionado um novo recurso para formatar o MonetaryAmount.

MonetaryAmountFormat defaultFormat = MonetaryAmountDecimalFormatBuilder.newInstance().build();
MonetaryAmountFormat patternFormat = MonetaryAmountDecimalFormatBuilder.of("¤ ###,###.00").build();
MonetaryAmountFormat localeFormat = MonetaryAmountDecimalFormatBuilder.of(Locale.US).build();

CurrencyUnit currency = Monetary.getCurrency("BRL");
MonetaryAmount money = Money.of(12, currency);
String format = defaultFormat.format(money);//$12.00
MonetaryAmount moneyParsed = Money.parse(format, defaultFormat);//or using defafult.parse(format);

Com isso foi discutido sobre a nova versão do moneta, a versão 1.1. Nessa versão além de ter realizado diversas correções, foram adicionados novos recursos para formatação, cotação e deixar API cada vez mais fácil. Com essa versão finalizada, os esforços já foram iniciados para a próxima versão que o maior benefício é a modularização do moneta.

Novidades do CDI 2.0

cdi_herologo

A especificação de injeção de contexto e dependência, o CDI, certamente foi umas das grandes novidades que surgiram com o Java EE 6. Ele funciona basicamente como uma “cola” para o Java EE, realizando integrações entre os seus beans, recurso, dentro do servidor de aplicação. Nessa nova versão, o CDI 2.0, que está previsto para junho de 2016 teremos algumas novidades. E esse post tem o objetivo de dar uma visão delas. Vale lembrar como ainda se tem tempo para o lançamento muitas dessas novidades ainda estão sendo discutidas e codificadas.

  • A API foi subdividida, agora é possível utilizar uma pequena parte dela, ao invés, de todo o contexto. Assim, será possível rodar em dispositivos que tem menos memória e processamento, por exemplo, a IoT, plataformas embarcadas e até mesmo no Android. Sim, a ideia é realizar um CDI lite, assim irão o básico de injeção de dependência (definição de bean, @Inject, qualificadores, produtores, evento) até todos os recurso.
  • Melhorias nos eventos: definir ordem para os eventos além de poder lançar eventos de forma assíncrona.
  • O uso de programação orientada a aspecto para melhorar os interceptors e decoradores.

Para exemplificar o uso de CDI, será imaginado um simples aplicativo, venda de livros, utilizando apenas o Java SE. O primeiro passo será a criação de um projeto maven e será adicionado a dependência do CDI 2.0, ainda na fase de desenvolvimento, com a sua implementação de referência, o weld.

    <dependencies> 
        <dependency> 
            <groupId>org.jboss.weld.se</groupId> 
            <artifactId>weld-se-core</artifactId> 
            <version>3.0.0.Alpha15</version> 
        </dependency> 
    </dependencies>

Adicionado a dependência, o próximo passo é container do CDI, uma característica interessante é que ele implementa o AutoCloseable, recurso oriundo do Java 7 que permite que a JVM feche o recurso tão logo sai do bloco try. Assim ao executar o código abaixo é possível ver no log o Weld SE container STATIC_INSTANCE shut down.

import org.jboss.weld.environment.se.Weld; 
import javax.enterprise.inject.spi.CDI; 

public class App { 
    public static void main(String[] args) { 

        try (CDI<Object> container = new Weld().initialize()) { 

        } 
    } 
} 

É possível fazer as mesmas coisas como no Java EE, assim é possível realizar injeção de dependência apenas com a anotação @Inject.

public class PaymentMethod { 

    public void payment() { 
        System.out.println("Doing payment"); 
    } 
} 

public class Checkout { 

    @Inject 
    private PaymentMethod paymentMethod; 

    public void checkout() { 
        System.out.println("doing checkout "); 
        paymentMethod.payment(); 
    } 
} 

Retornando a classe App, é possível iniciar uma classe a partir do container do CDI, nesse exemplo será a classe Checkout. O CDI será responsável por iniciar a classe e injetar todas as suas dependências.

public class App { 
    public static void main(String[] args) { 

        try (CDI<Object> container = new Weld().initialize()) { 
            Checkout checkout = container.select(Checkout.class).get(); 
            checkout.checkout(); 
        } 
    } 
} 

Também é possível ensinar o CDI a criar uma instância utilizando o método produtor, por exemplo:

public class User implements Serializable {

private String name;

private String email;

public User(String name, String email) {
this.name = name;
this.email = email;
}

String getName() {
return name;
}

String getEmail() {
return email;
}

@Override
public String toString() {
final StringBuilder sb = new StringBuilder("User{");
sb.append("name='").append(name).append('\'');
sb.append(", email='").append(email).append('\'');
sb.append('}');
return sb.toString();
}
}

public class UserProducer {

@Produces
public User getUser() {
return new User("Otavio", "otaviojava@domain.com");
}
}

Nesse exemplo, a classe User não possui um construtor default, assim o CDI não sabe como instanciar, assim foi utilizado o @Produces dentro da classe UserProducer para ensinar o CDI como instanciar a classe usuário. Assim é possível injetá-lo, por exemplo, dentro da classe Checkout:

public class Checkout {

@Inject
private PaymentMethod paymentMethod;
@Inject
private User user;

public void checkout() {
System.out.println("doing checkout: " + user);
paymentMethod.payment();
}
}

O outro recurso que também estará disponível para SE são os qualificadores. Imagine que agora será possível ter dois métodos de pagamento:

  • Cartão de crédito
  • Dinheiro

Assim o método de pagamento passará a ser uma interface com duas implementações. Umas das maneiras para ensinar qual implementação o CDI utilizará seria a partir dos qualificadores.

public enum PaymentType {
CREDIT_CARD, CASH
}

@Qualifier
@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
public @interface Payment {
PaymentType value();
}

public interface PaymentMethod {

void payment();
}

@Payment(PaymentType.CREDIT_CARD)
public class CreditCard implements PaymentMethod {
@Override
public void payment() {
System.out.println("Payment with credit card");
}
}

@Payment(PaymentType.CASH)
@Default
public class Cash implements PaymentMethod {
@Override
public void payment() {
System.out.println("Payment with cash");
}
}

Nesse exemplo, foi criado a anotação Payment que possui um atributo, o enum PaymentType, que define os tipos de pagamento. Na implementação Cash, além do qualificador ele possui a anotação @Default, quer dizer que caso não seja definido nenhuma qualificação o CDI o injetará como padrão.

@Inject
@Payment(PaymentType.CREDIT_CARD)
private PaymentMethod paymentMethod;

Será injetado a implementação de cartão de crédito.

@Inject
@Payment(PaymentType.CASH)
private PaymentMethod paymentMethod;

@Inject
private PaymentMethod paymentMethod;

Será injetado a implementação de pagamento em dinheiro.

Uma outra possibilidade dos qualificadores é definir qual instância será utilizada em tempo de execução, basta utilizar Instance com selector.

public class Checkout {

@Inject
@Any
private Instance payments;
@Inject
private User user;

public void checkout(PaymentType type) {
System.out.println("doing checkout: " + user);
PaymentMethod paymentMethod = payments.select(new PaymentSelector(type)).get();
paymentMethod.payment();
}
}

public class PaymentSelector extends AnnotationLiteral implements Payment {

private final PaymentType type;

PaymentSelector(PaymentType type) {
this.type = type;
}

@Override
public PaymentType value() {
return type;
}
}

Definido a forma de pagamento, o próximo passo será o envio de e-mail e entregar o livro. Assim, se pode trabalhar com eventos.

public class SendEmail {

public void sendEmail(@Observes User user) {
System.out.println("Sending email to user: " + user);
}
}

public class DeliveryBook {

public void delivery(@Observes User user) {
System.out.println("Delivering a book to: " + user);
}
}

public class Checkout {

@Inject
@Any
private Instance payments;
@Inject
private User user;
@Inject
private Event userEvent;

public void checkout(PaymentType type) {
System.out.println("doing checkout: " + user);
PaymentMethod paymentMethod = payments.select(new PaymentSelector(type)).get();
paymentMethod.payment();
userEvent.fire(user);
}
}

Um recurso possível agora no 2.0 é definir a ordem em que os eventos serão chamados.

public class DeliveryBook {

public void delivery(@Observes @Priority(Interceptor.Priority.APPLICATION+2) User user) {
System.out.println("Delivering a book to: " + user);
}
}

public class SendEmail {

public void sendEmail(@Observes @Priority(Interceptor.Priority.APPLICATION+1) User user) {
System.out.println("Sending email to user: " + user);
}
}

Outra possibilidade é realizar a chamada de forma assíncrona, para isso basta trocar a nos observadores para ObservesAsync e chamar o método do evento.

public class SendEmail {

public void sendEmail(@ObservesAsync User user) {
System.out.println("Sending email to user: " + user);
}
}

public class DeliveryBook {

public void delivery(@ObservesAsync User user) {
System.out.println("Delivering a book to: " + user);
}
}

public class Checkout {

@Inject
@Any
private Instance payments;
@Inject
private User user;
@Inject
private Event userEvent;

public void checkout(PaymentType type) {
System.out.println("doing checkout: " + user);
PaymentMethod paymentMethod = payments.select(new PaymentSelector(type)).get();
paymentMethod.payment();
userEvent.fireAsync(user);
}
}

Com isso foram apresentados os novos recursos do CDI 2.0, vale lembrar que muitos ainda estão sendo trabalhados, assim esperem por mais novidades até o dia do lançamento.

Link para o código

Java 8: Stream API – Parte 3, conhecendo a API

17_1

Esse artigo será a continuação da visão geral da API de Stream API. No post anterior, tivemos os primeiros contatos com a API. Nesse post, nosso objetivo é continuar falando um pouco mais. Manteremos o mesmo modelo dos capítulos anteriores.

public class Team {
private List players;
…
}

public class Player {

private String name;

private int gols;

private Position position;

//...
}

public enum Position {
GOALKEEPER,DEFENDER, FORWARDS
}

É possível verificar se um, todos ou nenhum elemento dentro da estrutura atende a uma condição, para isso, se pode utilizar os métodos anyMath, allMatch ou noneMach respectivamente.

public boolean hasDefender() {
return players.stream().anyMatch(Player::isDefender);
}

public boolean noHasDefender() {
return players.stream().anyMatch(Player::isDefender);
}

public boolean justHasDefender() {
return players.stream().anyMatch(Player::isDefender);
}

Com o min e max retorna o maior e o menor elemento respectivamente como critério ele usa um Comparator que é passado como parâmetro.

public Player getGoalscorer() {
return players.stream().max(Comparator.comparing(Player::getGols).reversed()).get();
}

public Player getLessGoalscorer() {
return players.stream().min(Comparator.comparing(Player::getGols).reversed()).get();
}

Caso a nossa classe Player, tenha implementado o equals e hashCode, que é uma boa prática em Java, é possível usar o distinct, que semelhante ao comportamento do banco de dados, garante que os dados não se repitam.

public List getGoalscorer() {
return players.stream().distinct().collect(Collectors.toList());
}

Além do mapeamento normal, que é por referência, é possível realizar o mapeamento para três tipos primitivos: int, double, long com os métodos mapToInt, mapToDouble e mapToLong respectivamente. Não é necessário se preocupar caso esteja trabalhando com o wrapper, os objetos Long, Double, e Integer terão a conversão automática, autoinboxing. Com o IntStream, LongStream e DoubleStream é possível realizar algumas operações dentro da coleção como valor máximo, mínimo e média.

public int getMaxGoalsFromPlayer() {
return players.stream().mapToInt(Player::getGols).max().getAsInt();
}

public int getMinGoalsFromPlayer() {
return players.stream().mapToInt(Player::getGols).min().getAsInt();
}

public double getAverageGoalsFromPlayer() {
return players.stream().mapToInt(Player::getGols).average().getAsDouble();
}

Também é possível retornar o stream primitivo para o seu respectivo wrapper (Stream<Integer>, Stream<Double> ou Stream<Long>). Além da possibilidade realizar conversão entre os tipos primitivos.

public LongSummaryStatistics getMinGoalsFromPlayerAsLong() {
return players.stream().mapToInt(Player::getGols).asLongStream().summaryStatistics();
}

public List getGolsAsDouble() {
return players.stream().mapToInt(Player::getGols).asDoubleStream().boxed().collect(Collectors.toList());
}

Como último tópico será discutido o recurso flatmap. Com o flatmap é possível extrair um Stream dentro de outro Stream. Para exemplificar será adicionado mais um modelo: A seleção. Uma seleção é composta por diversos times de um país.

public class NationalTeam {

private List teams;

public NationalTeam(List teams) {
this.teams = teams;
}
}

Para um exemplo simples serão retornados todos os jogadores dos times, porém limitados por onze jogadores.

public List getPlayers() {
return teams.stream().flatMap(t -> t.getPlayers().stream()).limit(11L).collect(Collectors.toList());
}

Naturalmente, durante a competição a seleção seleciona os melhores onze jogadores para os campeonatos, assim como critério será selecionado os onze que fizeram mais gols.

public List getPlayers() {
return teams.stream().flatMap(t -> t.getPlayers().stream())
.sorted(Comparator.comparing(Player::getGols).reversed())
.limit(11L).collect(Collectors.toList());
}

Java 8: Stream API – Parte 2, conhecendo a API

17_1

Após falar sobre a motivação para usar o Stream API, o foco desse artigo será mostrar uma visão geral da API. Para exemplificar, será utilizado o mesmo modelo do artigo anterior, um time de futebol.

public class Team {
private List players;
//…
}

public class Player {

private String name;

private int gols;

private Position position;

//...
}

public enum Position {
GOALKEEPER,DEFENDER, FORWARDS
}

Mapeamento

É possível realizar o mapeamento e retornar apenas um campo desejável, por exemplo, retornar a listas de nomes dos jogadores.

public List getNames() {
return players.stream().map(Player::getName).
collect(Collectors.toList());
}

Collector vs Collectors

A interface Collector que realiza uma operação de redução mutável colocando os resultados em uma representação e a classe Collectors tem várias implementações úteis da interface.

Retornando Set
public List getPlayers() {
return players.stream().collect(Collectors.toList());
}
Retornando List
public Set getPlayers() {
return players.stream().collect(Collectors.toSet());
}
Criando o seu próprio retorno
public Queue getPlayers() {
return players.stream().collect(Collectors.toCollection(LinkedList::new));
}

Ordenação

É possível ordenar de forma natural, implementando o Comparable. Para exemplificar isso, a classe Player será modificado comparando os jogadores pelo nome.

public class Player implements Comparable {
@Override
public int compareTo(Player player) {
return name.compareTo(player.name);
}
}

Assim é possível ordenar pelo nome:

public List getPlayers() {
return players.stream().sorted()
.collect(Collectors.toList());
}

Ou:

public List getPlayers() {
return players.stream().sorted(Comparator.naturalOrder())
.collect(Collectors.toList());
}

Também é possível definir uma ordenação, para isso, basta usar o método comparing dentro do Comparator.

public List getPlayers() {
return players.stream().sorted(Comparator
.comparing(Player::getGols))
.collect(Collectors.toList());
}

Foma descendente.

public List getPlayers() {
return players.stream().sorted(Comparator
.comparing(Player::getGols).reversed())
.collect(Collectors.toList());
}

Juntar ordenações:

public List getPlayers() {
return players.stream().sorted(Comparator.comparing(Player::getName)
.thenComparing(Comparator.comparing(Player::getGols)))
.collect(Collectors.toList());
}

Filtragem.

Já discutido no primeiro post, vale adicionar que é possível, assim como as ordenações, realizar mais de uma condição. É possível combinar utilizando os operadores or ou and, além de negar o predicate usando o negate. Para o exemplo, serão listados todos os atacantes que fizeram mais de 3 gols.

public List getGoodFowarder() {
Predicate isFowarder = Player::isFowarder;
Predicate hasMoreThanThreeGolds = p -> p.getGols() >3;
return players.stream().filter(isFowarder.and(hasMoreThanThreeGolds)).collect(Collectors.toList());
}

public List getNotFowarder() {
Predicate isFowarder = Player::isFowarder;
return players.stream().filter(isFowarder.negate()).collect(Collectors.toList());
}

Caso se queria limitar o número de elementos na lista, pular elementos e também retornar o primeiro ou algum resultado existe os métodos limit, skip, findFirst e findAny respectivamente.

public List getThreeGoodFowarder() {
Predicate isFowarder = Player::isFowarder;
Predicate hasMoreThanThreeGolds = p -> p.getGols() >3;
return players.stream().filter(isFowarder.and(hasMoreThanThreeGolds)).limit(3).collect(Collectors.toList());
}

public Player getSecondGoodFowarder() {
Predicate isFowarder = Player::isFowarder;
Predicate hasMoreThanThreeGolds = p -> p.getGols() >3;
return players.stream().filter(isFowarder.and(hasMoreThanThreeGolds)).skip(1).findFirst().get();
}

public Player getAGoodFowarder() {
Predicate isFowarder = Player::isFowarder;
Predicate hasMoreThanThreeGolds = p -> p.getGols() >3;
return players.stream().filter(isFowarder.and(hasMoreThanThreeGolds)).findAny().get();
}

public List getFowarderOrDefenter() {
Predicate isFowarder = Player::isFowarder;
Predicate isDefener = Player::isDefender;
return players.stream().filter(isFowarder.or(isDefener)).limit(3).collect(Collectors.toList());
}

 

Java 8: Stream API – Parte 1, a motivação

17_1

Umas das novidades que entraram no Java 8, foi o stream, com esse recurso é possível realizar interações de maneira bastante fácil com as suas coleções. O primeiro item a ser explorado do Stream é a sua motivação, com isso será criado um exemplo prático com um modelo simples, um time de futebol.

public class Team {
private List players;
…
}

public class Player {

private String name;

private int gols;

private Position position;

...
}

public enum Position {
GOALKEEPER,DEFENDER, FORWARDS
}

Com isso, será realizado algumas manipulações dos jogadores, inicialmente o código será utilizando o Java 7. Como primeiro método, teremos um que retorne apenas os atacantes.

public List getForwarders() {
List forwarders = new ArrayList<>();
for (Player player : players) {
if(player.isFowarder()) {
forwarders.add(player);
}
}
return Collections.unmodifiableList(forwarders);
}

Seguindo esse raciocínio, o próximo método será um que retorne apenas os defensores.

public List getDefenders() {
List defenders = new ArrayList<>();
for (Player player : players) {
if(player.isDefender()) {
defenders.add(player);
}
}
return Collections.unmodifiableList(defenders);
}

Ao analisar o código existe uma forte semelhança entre os dois códigos, na verdade existe uma única diferença: a condição para adicionar o jogador na lista. Possivelmente os próximos métodos seguirão o mesmo raciocínio. Assim, será refatorado esse código criando uma interface Filtro.

public interface PlayerFilter {
boolean filter(Player player);
}

Com a refatoração, utilizando o PlayerFilter se obtém o seguinte código:

public List getForwarders() {
return getFilteredPlayers(new PlayerFilter() {
@Override
public boolean filter(Player player) {
return player.isFowarder();
}
});
}

public List getDefenders() {

return getFilteredPlayers(new PlayerFilter() {
@Override
public boolean filter(Player player) {
return player.isDefender();
}
});
}

private List getFilteredPlayers(PlayerFilter filter) {

List filteredPlayers = new ArrayList<>();
for (Player player : players) {
if(filter.filter(player)) {
filteredPlayers.add(player);
}
}
return Collections.unmodifiableList(filteredPlayers);

}

Ainda é possível realizar mais uma refatoração sem utilizar o Stream, é possível reparar que o código desejável é a lógica dentro das implementações de PlayerFilter, assim com lambda é possível deixar o código ainda mais legível.

public List getForwarders() {
return getFilteredPlayers(player -> player.isFowarder());
}

public List getDefenders() {
return getFilteredPlayers(player -> player.isDefender());
}

Se pode deixar ainda mais simples utilizando o method reference. Da seguinte forma:

public List getForwarders() {
return getFilteredPlayers(Player::isFowarder);
}

public List getDefenders() {
return getFilteredPlayers(Player::isDefender);
}

Será que ninguém pensou nisso antes? A API de Stream é uma nova estrutura de dados que nasceu no Java 8 para trabalhar com fluxos de dados de modo sequencial ou paralelo de maneira muito fácil e intuitiva. De maneira geral segue algumas vantagens dessa API:

  • Maneira fácil e intuitiva para manipular sequencia de dados (Sequencial ou paralelo).
  • A Stream não impacta a lista já existente, assim toda operação de filtragem, por exemplo, não removerá elementos da lista original.
  • Um Stream só será executado quando tiver um método que encerra o pipeline[1].

 

Tão logo é criado o Stream e realizado a filtragem, o próximo passo é retornar as informações, para fazer isso é esperado a interface Collector, um padrão muito comum no Java é criar uma classe utilitária de interface apenas acrescentando o ‘s‘, assim a classe utilitária de <i>Collector</i> é <i>Collectors</i> tem alguns métodos que são muito úteis.

public List getForwarders() {
return players.stream().filter(Player::isFowarder).collect(Collectors.toList());
}

public List getDefenders() {
return players.stream().filter(Player::isDefender).collect(Collectors.toList());
}

Um ponto é que mesmo criando uma nova lista, talvez não seja uma boa ideia deixar o usuário da API modificar a lista oriunda da classe Team, ou seja, queremos deixar a lista apenas como read-only, exatamente como estava como código em Java. Um método que existe dentro do Collectors para resolver isso é o “collectingAndThen”. No nosso caso, será passado a conversão para a lista e em seguida utilizaremos o mesmo método para a deixar-lha imodificável.

<br />public List getForwarders() {

return players.stream().filter(Player::isFowarder).collect(Collectors.
collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
}

public List getDefenders() {
return players.stream().filter(Player::isDefender).collect(Collectors.
collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
}

Com isso, foi discutido de forma resumida a motivação de utilizar o Stream API, para manter os posts de forma curto, mais detalhes sobre a API serão feitas num próximo post.

public class Player {

private String name;

private int gols;

private Position position;

public Player(String name, int gols, Position position) {
this.name = name;
this.gols = gols;
this.position = position;
}

public String getName() {
return name;
}

public int getGols() {
return gols;
}

public Position getPosition() {
return position;
}

public boolean isFowarder() {
return FORWARDER.equals(position);
}

public boolean isDefender() {
return DEFENDER.equals(position);
}

}
public class Team {

private List players;

public Team(List players) {
this.players = players;
}

public List getForwarders() {

return players.stream().filter(Player::isFowarder).collect(Collectors.
collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
}

public List getDefenders() {
return players.stream().filter(Player::isDefender).collect(Collectors.
collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
}

}
public class App
{
public static void main( String[] args )
{
List players = new ArrayList<>();
players.add(new Player("Bobo", 10, Position.FORWARDER));
players.add(new Player("Zé Carlos", 8, Position.FORWARDER));
players.add(new Player("Lima", 4, Position.DEFENDER));
players.add(new Player("Lima", 14, Position.DEFENDER));
players.add(new Player("Ronaldo", 1, Position.GOALKEEPER));
Team bahia = new Team(players);
List defenders = bahia.getDefenders();
List forwarders = bahia.getForwarders();
}
}
public enum Position {
GOALKEEPER,DEFENDER, FORWARDS
}

Referência:

Trabalhando com dinheiro no Mundo Java EE [MONEY-API]

jobtrends

As aplicações Java EE estão ficando cada vez mais comuns no mundo do desenvolvimento do software para web utilizando Java. Esse crescimento se deu principalmente em 2009 com o lançamento do Java EE 6 e o nascimento do Contexto de injeção de independência, o CDI. Umas das grandes vantagens do uso do Java EE, é que ela é uma especificação Java, assim não existe o vendor lock-in, em outras palavras, não é necessário ficar preso em um fabricante. Outro ponto interessante é que caso não seja necessário usar todo o container do Java EE, é possível utilizar alguns recursos em um servidor simples, por exemplo, no caso é possível rodar o CDI, JSF, Bean validator, etc. em um container como o Tomcat. Outro ponto importante que vale ressaltar é que cada componente, citado anteriormente, é especificado separadamente, uma JSR, essas JSRs são regidas pelo JCP e todos podem participar, dar feedback, reportar bugs, etc. na concepção até o final dessa especificação Java.

O Java EE é certamente a plataforma mais popular para softwares no mundo corporativo, esses softwares têm diversos objetivos. O que eles normalmente tem em comum é o fato de lidar com dinheiro. O valor monetário é muito comum em diversos softwares, por exemplo, sistemas financeiros, locadoras, livraria, sistemas de recursos humanos, contabilidade etc. Porém não existia um padrão para trabalhar com dinheiro no mundo Java, por anos vários desenvolvedores usavam os tipos primitivos ou tipos oriundos do Java para resolver este problema, por exemplo, Long, Double, Integer, String, etc. Utilizar tipos primitivos traz diversos problemas para um software, dentre eles, falta de encapsulamento, problemas no design da API numa aplicação, quebra do SOLID, dentre outros motivos (Como falar das desvantagens do tipo primitivos para representar dinheiro não é o foco desse artigo, ficaremos apenas com esses exemplos, para mais informações existe o money-api book o link estará abaixo desse artigo). Com o intuito de solucionar esse problema existe a JSR 354, o money-api, essa especificação foi desenvolvida por diversas empresas que lidam diretamente com dinheiro, dentre elas, o Credit Suisse, uns dos maiores e mais famosos banco do mundo, e o Paypal, um dos maiores providers de pagamento do mundo.

Para tornar o artigo mais didático, será imaginado um simples exemplo de uma aplicação Java EE, um sistema de compra de livros. Nesse aplicativo, se falará de cada componente do Java EE separadamente. Se utilizará a versão atual do Java EE, JavaEE 7. O foco desse artigo não será falar sobre a API de dinheiro, apenas sua integração com o Java EE.

JPA

Para o exemplo ficar simples, o modelo livro basicamente terá três atributos:

  • Id, que será um número sequencial
  • Nome, o nome do livro
  • Valor, o valor do livro
@Entity 
public class Book implements Serializable { 

@Id 
@GeneratedValue(strategy = GenerationType.IDENTITY) 
private Long id; 

@Column 
private String name; 

@Column 
private MonetaryAmount value; 

//getter and setter 
    
}

Um problema comum em lidar com tipos únicos, como o dinheiro, é que normalmente os bancos não tem suporte nesse tipo de informação. Uma solução para esse problema nasceu no JPA 2.1 com os converters. A criação de um converter é bastante simples, basta implementar a interface AttributeConverter.

public class MonetaryAmountConveter implements  AttributeConverter&amp;amp;lt;MonetaryAmount, BigDecimal&amp;amp;gt;{ 

    private static final CurrencyUnit CURRENCY = Monetary.getCurrency(&amp;quot;BRL&amp;quot;); 
    
    private static final MonetaryQuery&amp;amp;lt;BigDecimal&amp;amp;gt; EXTRACT_BIG_DECIMAL = (m) -&amp;amp;gt; m.getNumber() 
            .numberValue(BigDecimal.class); 
            
    @Override 
    public BigDecimal convertToDatabaseColumn(MonetaryAmount attribute) { 
       return Optional.ofNullable(attribute).orElse(FastMoney 
               .zero(CURRENCY)) 
               .query(EXTRACT_BIG_DECIMAL); 
    } 

    @Override 
    public MonetaryAmount convertToEntityAttribute(BigDecimal dbData) { 
        return Money.of(dbData, CURRENCY); 
    } 
} 

Converter do JPA que persiste apenas o valor numérico.

E definir no campo como ele será convertido, para isso existe a anotação Convert.

@Column
@Convert(converter = MonetaryAmountConveter.class)
private MonetaryAmount value;

No JPA 2.1 os converters trabalham com um campo com um campo único, assim se pode trabalhar com algumas opções:

  • Persistir apenas o número: em sistemas que trabalham apenas com uma única moeda, é possível, por exemplo, definir a moeda em uma constante, que sempre será a mesma, e assim persistir o valor numérico. Assim é possível realizar operações no próprio banco de dados, como soma, porém caso o projeto cresça e precise internacionalizar haverá um grande problema. Uma solução para contornar esse problema seria ter um tenancy para cada país/moeda.

  • Persistir o número e a moeda como String: Utilizando essa estratégia seria o suficiente para manter o sistema internacionalizável. Se teria as informações suficientes para poder recuperar tanto a moeda quanto o valor numérico, porém, seria mais difícil, por exemplo, usar recursos do banco de dados, por exemplo, realizar somatório, assim um somatório teria que ser serializado para o modelo e assim a operação ser realizada.

  • Persistir o número e a moeda em campos diferentes: Essa opção, infelizmente, não está disponível no JPA, conforme foi discutido anteriormente no JPA 2.1 o converter apenas aceita um único campo como converção. Porém, é possível utilizar o hibernate diretamente para realizar essa operação, algo bem semelhante ao JPA 2.1, porém implementando a interface CompositeUserType. Com isso é possível, realizar operações de soma dentro do banco de dados, porém, existe a possibilidade de somar sem levar em consideração da moeda, ou não levar em consideração o modo de arredondamento desejado, que as implementações do MonetaryAmount podem garantir.

O Jadira é um framework que possui diversas implementações do UserType, interface que define o tipo que será persistido no banco de dados semelhante ao AttributeConverter. O Jadira já possui suporte tanto para as JSR 310, time-api, quanto o JSR 354, money-api.

CDI

Não é interessante que a entidade Livro seja responsável por definir a moeda que o objeto vai utilizar, afinal, não é sua responsabilidade. Outro ponto é que existem diversas estratégias para a criação de uma moeda: Definir a moeda a partir do usuário ou definir a moeda a partir do tenancy, por exemplo, caso o usuário utilize as máquinas do “.com.br” a moeda será real, “.com.us” dollar etc. Em nenhum desses casos deixar essa lógica espalhada no código não é uma boa estratégia, ou deixar o livro responsável por isso, nesse caso haverá quebra do SOLID. Para isso, poderíamos utilizar o CDI. O contexto de injeção de dependência, CDI, funciona basicamente como cola no mundo Java EE.

public class MoneyProducer { 

    @Inject 
    private HttpServletRequest request; 

    @Produces 
    @RequestScoped 
    public CurrencyUnit getCurrency() { 
        return Monetary.getCurrency(request.getLocale()); 
    } 
} 

Criar uma moeda a partir do request

public class MoneyProducer { 

    @Inject 
    @Named(&amp;quot;money_from_configurations&amp;quot;) 
    private String money; 

    @Produces 
    @RequestScoped 
    public CurrencyUnit getCurrency() { 
        return Monetary.getCurrency(money); 
    } 
}

Pegando a informação da moeda a partir de uma configuração.

Com um produtor de moeda definida pelo CDI, se pode injetar a moeda e “magicamente” ela será instanciada. Para não ter diversos pontos injetando moeda e criando dinheiro com ‘n’ implementações, uma recomendação é ter uma classe especializada na criação do dinheiro, caso seja necessário, criar um qualificadores para definir cada tipo de implementação.

public class MonetaryAmountFactory { 

    @Inject 
    private CurrencyUnit currency; 
    
    
    public MonetaryAmount create(Number number) { 
        return Money.of(number, currency); 
    } 

} 

 

JAX-RS

É muito comum, realizar integração com outros sistemas e para realizá-lo, o modo mais comum, é via uma API rest, dessa forma a integração será transparente pela tecnologia, linguagem, etc. que o outro sistema utilize. No mundo Java EE existe a especificação que lida com isso, o JAX-RS. Assim como na implementação de JPA, não existe um converter nativo para a moeda, a interface CurrencyUnit, e para o dinheiro, a interface MonetaryAmount. Na nova versão do JAX-RS é possível criar os próprios converters, para isso é necessário:

Implementar a interface ParamConverterProvider e anota-la com a anotação @Provider. Essa interface tem apenas um método e como saida espera como saida a interface ParamConverter.

@Provider 
public class MonetaryAmountConverterProvider implements ParamConverterProvider { 


    @Override 
    public &amp;amp;lt;T&amp;amp;gt; ParamConverter&amp;amp;lt;T&amp;amp;gt; getConverter(Class&amp;amp;lt;T&amp;amp;gt; rawType, Type genericType, Annotation[] annotations) { 
        if (MonetaryAmount.class.isInstance(rawType)) { 
            return new ParamConverter&amp;amp;lt;T&amp;amp;gt;() { 
                @Override 
                public T fromString(String value) { 
                    if(value == null || value.isEmpty()) { 
                        return null; 
                    } 
                    return rawType.cast(Money.parse(value)); 
                } 

                @Override 
                public String toString(T value) { 
                    if(value == null) { 
                        return null; 
                    } 
                    return value.toString(); 
                } 
            }; 
        } 
        return null; 
    } 
} 


@Provider 
public class CurrencyUnitConverterProvider implements ParamConverterProvider { 

    private CurrencyUnitParamConverter converter = new CurrencyUnitParamConverter(); 

    @Override 
    public &amp;amp;lt;T&amp;amp;gt; ParamConverter&amp;amp;lt;T&amp;amp;gt; getConverter(Class&amp;amp;lt;T&amp;amp;gt; rawType, Type genericType, Annotation[] annotations) { 
        if (CurrencyUnit.class.isInstance(rawType)) { 
            return new ParamConverter&amp;amp;lt;T&amp;amp;gt;() { 
                @Override 
                public T fromString(String value) { 
                    if(value == null || value.isEmpty()) { 
                        return null; 
                    } 
                    return rawType.cast(Monetary.getCurrency(value)); 
                } 

                @Override 
                public String toString(T value) { 
                    if(value == null) { 
                        return null; 
                    } 
                    return value.toString(); 
                } 
            }; 
        } 
            return null; 
    } 
}

JSF

A interação com o usuário é um ponto muito importante nas aplicações, assim é necessário representar o dinheiro e permitir que o usuário informe a moeda ou dinheiro. Assim como as outras especificações do mundo Java EE, não existe suporte para a money-api, porém existe converters dos componentes do JSF. Para isso basta implementar a interface Converter.

@FacesConverter(&amp;quot;money.midas.CurrencyConverter&amp;quot;) 
public class CurrencyConverter implements Converter { 

	@Override 
	public Object getAsObject(FacesContext context, UIComponent component, 
			String value) { 
		 
		if (Objects.isNull(value)) { 
			return null; 
		} 
		return Monetary.getCurrency(value); 
	} 

	@Override 
	public String getAsString(FacesContext context, UIComponent component, 
			Object value) { 
		if (Objects.isNull(value)) { 
			return null; 
		} 
		return value.toString(); 
	} 

} 

@FacesConverter(&amp;quot;money.midas.MoneyConverter&amp;quot;) 
public class MoneyConverter implements Converter { 

	@Override 
	public Object getAsObject(FacesContext context, UIComponent component, 
			String value) { 
		 
		if (Objects.isNull(value)) { 
			return null; 
		} 
		return Money.parse(value); 
	} 

	@Override 
&amp;amp;lt;strong&amp;amp;gt;	public String getAsString(FacesContext context, UIComponent component, 
			Object value) { 
		if (Objects.isNull(value)) { 
			return null;
		} 
		return value.toString(); 
	} 
}

O Projeto Midas

Com o intuito de realizar a integração entre as especificaçõeshttp://jadira.sourceforge.net/ e outros frameworks e o money-api existe o projeto Midas, dentre os projetos que estão no midas dar suporte estão:

  • JSF
  • Bean Validation
  • JPA
  • JAX-RS
  • CDI
  • Google Guice
  • Spring

No midas, todos os converters do JSF, JPA, JAX-RS que foram citatos nesse artigo estão nesse projeto. Além dessas especificações, o midas tem suporte para o bean validation.

  • CurrencyAccepted: Define as moedas que serão aceitas dentro da moeda, CurrencyUnit, e do dinheiro, MonetaryAmount. É possível informar as moedas permitidas ou usando o código da moeda ou de * o Locale de onda a moeda veio.
  • CurrencyRejected: Define as moedas que não serão aceitas, rejeitadas, dentro da moeda, CurrencyUnit, e do dinheiro, MonetaryAmount. É possível informar as moedas permitidas ou usando o * código da moeda ou de o Locale de onda a moeda veio.
  • MonetaryMax: Define o valor máximo para o dinheiro.
  • MonetaryMin: Define um valor mínimo para o dinheiro.
@CurrencyAccepted(currencies = &amp;quot;BRL&amp;quot;) 
private CurrencyUnit currencyUnit; 

@CurrencyAccepted(currencies = &amp;quot;BRL&amp;quot;) 
@MonetaryMax(&amp;quot;10.12&amp;quot;) 
private MonetaryAmount money;

Com isso foi apresentado a integração entre o money-api e o Java EE. Ainda existe muito mais integrações para serem desenvolvidas com essa espificação que trabalha com dinheiro e , certamente, toda ajuda será bem-vinda por todos os membros da comunidade.

Referências

Dicas e considerações iniciais ao usar o Cassandra




    Ao se pensar em um banco de dados a primeira coisa que um desenvolvedor de software pensa é em realizar buscas, queries, realizar a normalização. Mesmo depois de tanto tempo com várias discussões com NOSQL, muitas pessoas ainda acreditam que o NOSQL, mas vale lembrar que muitas coisas mudaram no mundo do desenvolvimento de software e isso inclui os bancos de dados, existem muitas soluções além da convencional para armazenar informações. O objetivo desse artigo é discutir é ajudar aquelas pessoas que pretendem ao utilizam o Cassandra em pouco tempo fornecendo algumas dicas.
    Saber quando utilizar a tecnologia: Antes de escolher uma tecnologia é muito importante entender bem o seu problema e em seguida entender o motivo no qual a tecnologia escolhida será útil em seu projeto. Com o Cassandra não é diferente, ele é um banco NOSQL é interessante seu uso quando se precisa de uma alta disponibilidade e tolerância a falhas (seria o A e o P no teorema do CAP). Possui uma escalabilidade linear, ou seja, quanto mais nós em seu datacenter maior será o número de requisições por segundo.
    O Cassandra não é relacional: Uma coisa muito comum das pessoas ao aprender uma nova tecnologia é tentar realizar relações com a técnologia já conhecida, o problema é que em alguns casos os estudantes ultrapassam o estudo e tentam simular o SQL dentro do Cassandra. O Cassandra foi feito em cima de um outro “paradigma” de persistência o BASE é muito importante entender que ao tentar realizar emulações de ACID dentro de um BASE, não se conseguirá atingir nenhum dos objetivos (O erro ficará oculto com uma massa pequena, mas quando for para produção com uma grande massa o erro será desastroso).
    Não existe normalização no Cassandra: Um erro muito comum dentro do Cassandra é tentar realizar a normalização. Como já dito anteriormente o Cassandra foi feito em cima de outro paradigma. O fato é que a normalização se tornou muito popular em 1970, quando o armazenamento era muito caro, assim o desafio era conter a informação de forma econômica, dessa forma não repeti-la. Atualmente o armazenamento está ficando cada vez mais barato e o desafio mudou: É lidar, por exemplo, com um número de requisição cada vez maior (milhões, talvez bilhões).
    Hierarquia dentro do Cassandra: A hierarquia dos bancos relacionais segue o seguinte fluxo: banco, tabela e coluna. No Cassandra acontece de forma semelhante no topo temos o KeySpace, família de colunas, e a coluna, esse por sua vez é composto por um bloco com três informações: O nome do tampo, o valor do campo e o timestamp.
    No Cassandra não existe transação: O Cassandra foi feito para trabalhar com uma alta taxa de disponibilidade, desse modo, é inviável que exista transação, é possível enviar vários registros em uma mesma família de coluna ao mesmo tempo. Para saber qual versão é a mais recente ele utiliza o timestamp existente no campo, acontece que quando um campo é inserido, ele recebe o timestamp do momento que foi inserido.
    Nível de Consistência: O Cassandra trabalha em cima da replicação da informação para ter sua característica tolerante a falhas. Ao se criar um keyspace setta o fator de réplica, que define a quantidade de nós na qual a informação será duplicada. Após isso toda requisição, tanto escrita quanto leitura, é feita em cima do fator de réplica ao enviar uma informação para o Cassandra é importante entender a diferença entre disponibilidade e consistência. Se ao realizar uma requisição for definido uma consistência alta, por exemplo, o ALL que é o número de fator de réplica defina no momento da criação/alteração do keyspace, o processo só será finalizado quando enviar para todos os nós definido. Diferente de um nível baixo, como o ONE que enviará a solicitação para apenas um nó, deixando os outros nós prontos para trabalharem em outras requisições, realizando a réplica em background elevando assim o nível de disponibilidade.
    Não existe relacionamento: Uma boa prática de para usar o Cassandra é desnormalizando sua base, desse modo, não existem relacionamentos. Se, por exemplo, você tem duas tabelas, pessoa e endereço em uma relação um para um, no Cassandra o mais correto seria uma família de coluna contendo as duas tabelas, mesmo que existam duas possas que tenham o mesmo endereço, replicando o endereço.
    Busque informações pela chave: O uso de um campo auto-increment como chave no Banco de dados deve ser evitado. Dentro do Cassandra, por padrão, o único campo no qual se pode buscar informações é a chave, caso você queria adicionar mais campos “buscáveis” basta defini-lo como índice, mas se deve evitar por questão de performance. No caso de uma tabela pessoa a chave poderia ser o cpf ou o nickname.
    O único campo obrigatório é a chave: No Cassandra o único campo obrigatório é a chave, desse modo, podem existir registros com 10, 20, 100, ou nenhuma coluna, desde que o mesmo possua uma chave. Os campos são criados por demanda, se um registro não tiver o campo “telefone”, por exemplo, ele de fato não existirá, diferente do banco relacional em que a coluna existe para todos os registros com o valor null.
    Não existe Constraints: O constraints muito utilizado para regras dos seus dados não existem dentro do Cassandra, pode parecer estranho para alguns, mas atualmente tal recurso não se torna desnecessário. Não é muito comum, por exemplo, colocar para o usuário a mesma mensagem de erro que o banco retornou como o: “there is no unique constraint matching given keys for referenced table “tec_configurations”” ou “null value in column “pessoa_nome” violates not-null constraint” e sim “nickname já cadastrado” e “campo nome obrigatório”, ou seja, as regras já estão dentro do seu software. Outro exemplo, caso se insira, utilizando um insert, a mesma informação duas vezes, vai funcionar normalmente já que a informação é apenas colocada lá, caso ela já exista será sobrescrita.
    Views materializadas: Em alguns momentos precisamos realizar cálculos em uma aplicação (somatório, média, etc.). Imaginando um sistema que mede a temperatura de uma determinada cidade e os sensores enviam informação a cada milissegundo pode-se deixar essa informação preprocessada em uma família de colunas, muito semelhante as view materializadas nos bancos relacionais. Esse recurso é muito utilizado, é muito comum se ter várias famílias de colunas como estas, o ideal é que sua modelagem seja feita de acordo com sua busca. No caso do sistema de temperatura se pode ter uma família de colunas para média por dia, mês e ano, para acompanhar o histórico de temperatura de uma cidade.
    Sua aplicação também precisa escalar: Não adianta ter um banco escalável preparado para receber mil requisições por segundo, se sua aplicação envia apenas 1 requisição por segundo. Desse modo é importante entender que sua aplicação também precisa escalar.
    Cassandra Query Language: Para facilitar a vida do desenvolvedor existe o Cassandra Query Language, o CQL. Com ele é possível criar e modificar estruturas e realizar manipulações de dados de uma maneira mais tranquila e muito mais fácil. O interessante é que esse recurso possui uma sintaxe muito semelhante ao SQL. Para executar e verificar os CQLs se pode usar o DataStax DevCenter que possui sua interface baseada no eclipse.
    Coleções muitos grandes: Um recurso que entrou recentemente no banco é o uso de três coleções: o list (uma lista de informações), o set (uma lsita sem valor duplicado), e um map (um dicionário de dados, possui um valor para uma chave correspondente). Esse recurso é muito interessante e é importante utilizado, mas tome cuidado para que essas coleções não sejam muito grandes. O interessante é que ela no geral não ultrapassagem 260KB ou 75KB.
    Acompanhe seus nós: É interessante acompanhar a performance, topologia do seu datacenter. Na configuração o recomendado é que o commitlog e o sstable estejam em discos diferentes e que esses discos sejam SSDs. Outra dica importante é ter cuidado com o tamanho do heap na maioria dos casos o padrão resolverá (é feito um calculo de heap baseado em memória disponível), caso modifique o recomendado é que não ultrapasse os 8GB. Uma solução para acompanhar seus nós é o DataStax OpsCenter.
Concluindo, nesse pequeno artigo foi demonstrado algumas dicas inicias para os usuários do Cassandra, demonstrando que é muito comum tentar simular o SQL dentro do Cassandra, tenderá a ter consequências desastrosas.
Links: 

Easy-Cassandra, a versão 2.0.1

Lançada a nova versão do Easy-Cassandra, a versão 2.0.1, dentre as novidades podemos citar;

  • Refatoração nos contrutores das fábricas de sessão
  • QueryBuilder (InsertBuilder, UpdateBuilder, DeleteBuilder, SelectBuilder e BatchBuilder)
  • Maior velocidade na leitura e escrita
  • Suporte nos processos assíncronos
  • Maior suporte ao Cassandra com Spring-Data

Um ponto importante é que para facilitar a configuração foi criada uma classe que é passada no parâmetro, a classe ClusterInformation, dessa maneira fica mais simples (além do código mais limpo, afinal sobrecarga de construtores não é uma boa prática) de se criar simples configurações para a fábrica. Como, por exemplo:

ClusterInformation clusterInformation = ClusterInformation.create()
.addHost(HOST)
.withDefaultKeySpace(KEY_SPACE).withUser(USER).withPassword(PASS);
easyCassandraManager = new EasyCassandraManager(clusterInformation);

Criando configuração para a fábrica de conexão
Dessa forma a configuração para usar o Spring no Cassandra também mudou para:

<beans xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xsi:schemalocation="http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">







localhost








org.easycassandra.persistence.cassandra.spring.entity.Contact
org.easycassandra.bean.model.Step
org.easycassandra.bean.model.Weight









Nova configuração do Spring no Easy-Cassandra
Talvez a melhoria mais marcante nessa versão são os recursos dos QueryBuilders, os queryBuilders são maneiras muito mais fácil de fazer interações com o Cassandra com configurações adversas de uma maneira mais simples, com ele é possível definir o nível de consistência, definir o timeStamp, quais campos serão manipulados.

 SimpleID id = new SimpleID();
id.setIndex(ONE_HUNDRED_THIRTY);
id.setKey(ONE_HUNDRED_THIRTY);
UpdateBuilder update = template.updateBuilder(SimpleQueryBuilder.class, key);
update.put("map", "name", "otavioMap").value("value", TEN_DOUBLE).execute();

Simples exemplo inserindo valores a partir da chave

UpdateBuilder update = persistence.updateBuilder(SimpleQueryBuilder.class);
update.whereEq(Constant.KEY, Constant.ONE).whereEq(Constant.INDEX, Constant.ONE).addList("list", "otavioList").execute();

criando toda a query de atualização

 UpdateBuilder update = persistence.updateBuilder(SimpleQueryBuilder.class);
update.whereEq(Constant.KEY, Constant.ONE).whereEq(Constant.INDEX, Constant.ONE).value("value", 12D).executeAsync(new ResultAsyncCallBack() {
@Override
public void result(Boolean bean) {
// do some action
}
});

executando uma atualização assincronamente

InsertBuilder insert= persistence.updateBuilder(SimpleQueryBuilder.class);
Set set = new HashSet();
set.add("Linda");
Map map = new HashMap();
map.put("love", "Otavio and Poliana");
insert.value(Constant.KEY, Constant.ONE_HUNDRED).value(Constant.INDEX,
Constant.ONE_HUNDRED).value(Constant.LIST_COLUMN, Arrays.asList("Poliana", "Otavio", "Love")).value(Constant.SET_COLUMN, set).value("map", map).execute();

Realizando inserção

  SimpleID id = new SimpleID();
id.setIndex(ONE_HUNDRED_TWO);
id.setKey(ONE_HUNDRED_TWO);
SimpleBean simpleBean = new SimpleBean();
simpleBean.setId(id);
simpleBean.setValue(VALUE);
InsertBuilder insert2 = persistence.updateBuilder(simpleBean);
insert2.executeAsync((new ResultAsyncCallBack() {
@Override
public void result(Boolean bean) {
// do some action
}
});

realizando inserção de modo assíncrono

  DeleteBuilder delete = persistence.deleteBuilder(SimpleQueryBuilder.class); 
delete.whereEq(Constant.INDEX, ONE_HUNDRED_TWO).whereEq(Constant.KEY, ONE_HUNDRED_TWO).execute();

DeleteBuilder delete2 = persistence.deleteBuilder(SimpleQueryBuilder.class,"map", "list", "set"); //Delete specific columns
delete2 .whereEq(Constant.INDEX, ONE_HUNDRED_TWO).whereEq(Constant.KEY, ONE_HUNDRED_TWO).execute();

primeira query remove todas as colunas a partir da chave, na segunda query remove apenas os campos map, list e set.

 SimpleID id = new SimpleID();
id.setIndex(ONE_HUNDRED_TWO);
id.setKey(ONE_HUNDRED_TWO);

DeleteBuilder delete2 = persistence.deleteBuilder(SimpleQueryBuilder.class, id);
delete2.executeAsync((new ResultAsyncCallBack() {
@Override
public void result(Boolean bean) {
// do some action
}
});

remoção assíncrona

  SelectBuilder select = persistence.selectBuilder(SimpleQueryBuilder.class);
select.eq("name", "name");
select.in("index", ONE, TWO, THREE);
List result = select.execute();

Realizando o select em que o campo nome é igual a nome e o indice está em um, dois e três.

SelectBuilder select = persistence.selectBuilder(SimpleQueryBuilder.class);
select.eq(NAME, NAME).gt("index", THREE).asc(INDEX).executeAsync(new ResultAsyncCallBack>() {

@Override
public void result(List beans) {
// do some thing
}
});

Executando query assincronamente em que o indece seja mais que três e seja ordenado de forma descendente no índice.
O Batch é um recurso no cassandra que permite que se execute alterações no banco (inserção, atualização e remoção) de forma atômica, desse forma se pode enviar insert, updates e deletes como se fosse apenas uma requisição.

DeleteBuilder delete = dao.deleteBuilder();
delete.whereEq(Constant.INDEX, Constant.ONE_HUNDRED_TWO)
.whereEq(Constant.KEY, Constant.ONE_HUNDRED_TWO);

InsertBuilder insert = dao.insertBuilder();
insert.value(Constant.KEY, Constant.ONE_HUNDRED).value(Constant.INDEX,
Constant.ONE_HUNDRED);
insert.value(Constant.LIST_COLUMN,
Arrays.asList("Poliana", "Otavio", "Love"));

UpdateBuilder update = dao.update();
update.whereEq(Constant.KEY, Constant.ONE).whereEq(Constant.INDEX, Constant.ONE);
update.addList("list", "otavioList");

BatchBuilder batchBuilder = dao.batchBuilder();

batchBuilder.addOperations(delete, insert, update);
batchBuilder.executeAsync(new ResultAsyncCallBack() {
@Override
public void result(Boolean bean) {
// do some action
}
});

Executando processo de inserção, remoção e atualização de forma atômica com o BatchBuilder.
Vale lembrar que recurso do Batch deve ser usados em casos específicos, já que distribuir as requisições entre os nós, no cassandra, sempre será sua melhor amiga.
Esse artigo tem como objetivo ilustrar as mudanças dentro do Easy-Cassandra, se demonstrou dos recursos e da sua melhoria na velocidade a expectativa é que muitos mais recursos estejam por vir.

  1. https://github.com/otaviojava/Easy-Cassandra/wiki/Builders
  2. https://github.com/otaviojava/Easy-Cassandra/wiki
  3. https://github.com/otaviojava/Easy-Cassandra/
  4. https://github.com/otaviojava/Easy-Cassandra-samples

Como compilar código Java dinâmicamente

O Java é uma plataforma que se multiplicou exponencialmente, uma de sua grande vantagem é o fato de que você consegue com o mesmo código executar em diferentes plataformas, com isso se pode programar para servidor, dispositivos embarcados, desktop, etc. com a mesma linguagem. Além disso a JVM possui suporte para diversas linguagens, das quais possuem, diversas características e recursos interessantes. Um mito que existe é que o Java não compila em tempo de execução, o que faz muitas pessoas utilizem uma linguagem dinâmica, apenas por esse objetivo, mas será que isso é verdade?

Possuir uma linguagem dinâmica é muito interessante para alguns projetos específicos, por exemplo, quando se faz um projeto que realiza calculo tributário, como essa fórmula mudam muito ao ano e variam de município para município, nesse caso é melhor que o código fonte esteja em um banco de dados, por exemplo, para quando for necessário modificar o calculo não seja necessário compilar todo o código fonte e fazer o deploy, correndo o risco do sistema ficar fora do ar mesmo que por alguns instantes.

Sim é possível em Java compilar códigos dinamicamente. Na verdade isso é muito comum do que imaginamos! Por exemplo, o Hibernate para gerenciar das entidades, para facilitar ainda o seu uso a partir da versão 1.6 com a JSR 199 foi criada um API com essa finalidade.

Para demonstrar essa funcionalidade será usado a solução para o problema acima, fazer com que haja troca de fórmula, para facilitar a demonstração e focar na solução serão as 4 operações básicas, sendo que esse código fonte estará no banco de dados, no caso será simulado com arquivos dentro de um txt. Como não podemos referenciar uma classe não compilada, criaremos uma interface Operação, ela será implementada por nossas classes que estarão em nosso “banco de dados”.

public interface Calculo { 

Double calcular(Number valorA, Number valorB);

}

Interface na qual será utilizada como referencias as classes compiladas dinamicamente.

Explicando o processo de compilação passo a passo: A classe JavaCompiler, que tem a responsabilidade de fazer a compilação do código-fonte. A chamada ToolProvider.getSystemJavaCompiler() retorna este objeto. Se um compilador Java não estiver disponível, o retorno será null. Ele conta com o método getTask(), que retorna um objeto CompilationTask. De posse desse objeto, a chamada call() efetua a compilação do código e retorna um booleano indicando se ela foi feita com sucesso (true) ou se houve falha (false).

public class JavaDinamicoCompilador { 

private JavaCompiler compiler;

private JavaDinamicoManager javaDinamicoManager;

private JavaDinamicoClassLoader classLoader;

private DiagnosticCollector diagnostics;

public JavaDinamicoCompilador() throws JavaDinamicoException {
compiler = ToolProvider.getSystemJavaCompiler();
if (compiler == null) {
throw new JavaDinamicoException("Compilador não encontrado");
}

classLoader = new JavaDinamicoClassLoader(getClass().getClassLoader());
diagnostics = new DiagnosticCollector();

StandardJavaFileManager standardFileManager = compiler
.getStandardFileManager(diagnostics, null, null);
javaDinamicoManager = new JavaDinamicoManager(standardFileManager, classLoader);
}

@SuppressWarnings("unchecked")
public synchronized Class compile(String packageName, String className,
String javaSource) throws JavaDinamicoException
{
try {
String qualifiedClassName = JavaDinamicoUtils.INSTANCE.getQualifiedClassName(
packageName, className);
JavaDinamicoBean sourceObj = new JavaDinamicoBean(className, javaSource);
JavaDinamicoBean compiledObj = new JavaDinamicoBean(qualifiedClassName);
javaDinamicoManager.setSources(sourceObj, compiledObj);

CompilationTask task = compiler.getTask(null, javaDinamicoManager, diagnostics,
null, null, Arrays.asList(sourceObj));
boolean result = task.call();

if (!result) {
throw new JavaDinamicoException("A compilação falhou", diagnostics);
}

Class newClass = (Class) classLoader.loadClass(qualifiedClassName);
return newClass;

}
catch (Exception exception) {
throw new JavaDinamicoException(exception, diagnostics);
}
}
}
Classe responsável pela compilação

O processo de compilação envolve dois tipos de arquivos: os códigos-fonte escritos em Java e os arquivos compilados (bytecodes). Na Compiler API estes arquivos são representados por objetos de uma única interface, chamada JavaFileObject. Felizmente a API disponibiliza uma classe que implementa esta interface, chamada SimpleJavaFileObject e, na escrita de código de compilação dinâmica, deve-se criar uma subclasse de SimpleJavaFileObject e sobrescrever os métodos necessários.

public class JavaDinamicoBean extends SimpleJavaFileObject { 

private String source;

private ByteArrayOutputStream byteCode = new ByteArrayOutputStream();


public JavaDinamicoBean(String baseName, String source) {
super(JavaDinamicoUtils.INSTANCE.createURI(JavaDinamicoUtils.INSTANCE.getClassNameWithExt(baseName)),
Kind.SOURCE);
this.source = source;
}

public JavaDinamicoBean(String name) {
super(JavaDinamicoUtils.INSTANCE.createURI(name), Kind.CLASS);
}

@Override
public String getCharContent(boolean ignoreEncodingErrors) {
return source;
}

@Override
public OutputStream openOutputStream() {
return byteCode;
}

public byte[] getBytes() {
return byteCode.toByteArray();
}
}
Estrutura de dados que contem o codigo fonte e a classe compilada

Para representar os arquivos envolvidos será utilizado o ForwardingJavaFileManager que implementa a interface JavaFileManager.

public class JavaDinamicoManager extends ForwardingJavaFileManager { 
private JavaDinamicoClassLoader classLoader;


private JavaDinamicoBean codigoFonte;

private JavaDinamicoBean arquivoCompilado;

public JavaDinamicoManager(JavaFileManager fileManager, JavaDinamicoClassLoader classLoader)
{
super(fileManager);
this.classLoader = classLoader;
}

public void setSources(JavaDinamicoBean sourceObject, JavaDinamicoBean compiledObject) {
this.codigoFonte = sourceObject;
this.arquivoCompilado = compiledObject;
this.classLoader.addClass(compiledObject);
}

@Override
public FileObject getFileForInput(Location location, String packageName,
String relativeName) throws IOException
{
return codigoFonte;
}

@Override
public JavaFileObject getJavaFileForOutput(Location location,
String qualifiedName, Kind kind, FileObject outputFile)
throws IOException
{
return arquivoCompilado;
}

@Override
public ClassLoader getClassLoader(Location location) {
return classLoader;
}
}
Classe responsável por gerenciar as classes compiladas e não compiladas

Para que ela possa ser utilizada, a JVM deve ser capaz de reconhecê-la como uma classe da aplicação, a fim de que possa carregá-la quando chegar o momento. O componente responsável pelo carregamento das classes das aplicações Java durante a execução é o Class Loader.
Portanto, para que a JVM saiba da existência das novas classes compiladas, é necessário implementar um class loader customizado, que fica atrelado ao gerenciador de arquivos.
Ele deve estender a classe ClassLoader (do pacote java.lang) e tem a responsabilidade de carregar as classes recém-criadas.
Com isso foi discutido um pouco sobre a compilação dinâmica do Java no Java, objetivo aqui foi apenas de demonstrar o seu funcionamento básico além de uma pequena explicação do uso da API. Pela sua complexidade o ideal é que ela esteja encapsulado a ponto de ser utilizadas várias vezes em diversos projetos. O objetivo aqui não foi desmerecer em nenhum momento as outras linguagens de programação rodando ou não em cima da JVM, afinal todas elas têm sua importância. O objetivo foi demonstrar que não existe a necessidade de mudar de linguagem caso seu problema seja ter um código que seja compilado dinamicamente.

Referências: