quinta-feira, 19 de fevereiro de 2015

TDD - Porque tantas opniões distorcidas sobre esse assunto?

Vou começar lembrando que TDD é uma técnica de desenvolvimento e modelagem de software.
Como qualquer aspecto do assunto desenvolvimento de software esse também gera dúvidas, polêmicas, discursos pró e contra.
Não é minha intenção defender ou atacar a técnica, o objetivo é tentar esclarecer como ela nasceu e seus objetivos.

O primeiro e mais polêmico "mito" é que TDD é menos produtivo.

Muitos profissionais gostam de escrever pequenos trechos de código para garantir que a idéia funciona e depois partir
para o projeto com a idéia mais madura. Esses pequenos códigos geralmente são provas de conceito (poc) e ajudam o processo
de modelagem do sistema, já que influênciam as decisões.
Ainda nessa linha, outros preferem escrever códigos e depois usar as
ferramentas de depuração das IDE para verificar se tudo funciona.

Acho que vale algumas palavras sobre o assunto: "Teste Unitário como artefato para execução automática", já que ele é um instrumento importante
na visão de muitos profissionais (até mesmo para alguns desenvolvedores, ainda não acostumados com essa prática ou sem vontade de construir os artefatos).

O Teste Unitário


Esse tipo de teste visa testar a "menor parte testável" de um software (por exemplo: os métodos de uma classe), com foco na
entrada e saída, testando valores válidos e inválidos. Como a abordagem descrita propõe um "teste caixa preta" (onde a implementação
não precisa ser conhecida) qualquer profissional poderia escrever esses testes, desde que conhecesse as regras de negócio.

Os testes devem verificar:


  • Se para cada valor *válido* informado na entrada, uma saída apropriada foi gerada
  • Se para cada valor *inválido* informado na entrada, somente uma saída apropriada foi gerada
  • Se para cada estado *válido* na entrada, ocorrência de estado válido na saída
  • Se para cada estado *inválido* na entrada, ocorrência de estado inválido na saída


Durante o desenvolvimento de software sempre testamos partes dele, seja usando papel e lápis (teste seco ou de mesa), escrevendo pequenos
trechos de código, mentalmente, etc... então testes unitários são naturalmente parte do trabalho.

Porque então não são populares?
Na verdade, são bem populares.
O que não é muito popular é a finalidade (objetivo) desse tipo teste.

Não é o objetivo do teste unitário garantir que o software seja "bug free" (sem erros). Erros acontecem e quanto antes percebemos melhor.

É objetivo do teste garantir que a "menor parte testável" esteja funcionando.
Por exemplo, se um método de uma classe torna o estado de um objeto (instância dessa classe) inválido, além de outros aspectos, algum teste automático deveria avisar sobre essa condição e falhar.
Analogamente, se um método não cumpre seu objetivo (não faz o que deveria), um outro teste automático deveria avisar a condição.

Muitas vezes esse processo de testes é realizado com técnicas que não permitem a execução automática, assim a cada mudança no código alguém teria que executar novamente os passos para garantir que tudo funciona.

Hoje em dia é praticamente inconcebível que você não execute determinado trecho do código, afinal com as IDEs disponíveis basta apertar um botão para executar passo a passo, mas nem sempre foi possível fazer isso.

A evolução dos ambientes de desenvolvimento acabaram deixando as pessoas mais preguiçosas e é natural, por que eu ficaria imaginando o resultado de alguma implementação, se posso simplesmente executar e ver o resultado?

Como o software é o "conjunto de engrenagens" que permite usarmos o computador como uma máquina, sua característica abstrata pode ser confusa, executá-lo é como "girar uma alavanca" para ver os mecanismos funcionando, ou seja é um modo "concreto" de observá-lo.

Apesar de soar estranho "girar uma alavanca" é exatamente o tipo de comparação que eu gostaria de apresentar, criar um artefato como um teste unitário automático é como participar da Revolução Industrial, estaremos usando um "robô" (uma máquina) para realizar a tarefa, não criá-los porém é como voltar ao século XVII.

Os robôs são ótimos para executar tarefas repetitivas, não se esquecem de um passo no processo, não "faltam no trabalho" e estão a disposição de qualquer pessoa e/ou robô.

Apesar da natureza do teste permitir que qualquer profissional que conheça as regras de negócio possa criá-lo, escrever código para testar o código só faz sentido para o desenvolvedor. Além da prática com a linguagem, é (teoricamente) o maior interessado em saber se o código que escreveu atende seu objetivo, ou seja, se funciona (faz o que deveria).

Antes de criar seu próximo "printf" (cout, println, etc...) pense em escrever um teste que valide alguma condição, o código não dependerá mais do seu olho atento ao console para identificar uma cadeia de caracteres peculiar, melhor ainda, não dependerá dos olhos de qualquer outra pessoa para ser identificado como "certo ou errado".

Refactory (ou code refactoring)

É praticamente qualquer alteração no código para melhorar algum aspecto (seja diminuir a complexidade, melhorar a arquitetura, melhorar a modelagem, melhorar a testabilidade,  ou simplesmente torná-lo mais legível), sem alterar o "comportamento externo", ou seja, o domínio e contra-domínio são mantidos (dado uma entrada x, o resultado é y, antes e depois da modificação).

Então, como podem testes automáticos melhorarem o design da minha aplicação?


Se você e sua equipe começarem a pensar nos objetivos da aplicação e criar os artefatos de teste unitários (para execução automática) para determinar os cenários, quando terminarem o conjunto de testes, o código que será criado para passar nesses testes tende a ser mais coerente (primeira versão da modelagem).

Assim que todas as condições dos testes forem atendidas (o código passa em todos os testes) começam os refactories. É muito mais fácil e "seguro" fazer refactory em uma aplicação com testes para garantir os objetivos, dessa maneira, não é preciso se preocupar em quebrar alguma funcionalidade, já que, desde que o teste esteja passando, a funcionalidade está garantida.

Muito bem, mas quem vai testar se os testes estão realmente de acordo com o que foi especificado?
Ou seja, quem vai garantir os testes? Os testes dos testes?

A equipe vai manter e garantir que os testes estejam de acordo.

Por exemplo: se o resultado do teste foi positivo, mas algum comportamento parece incorreto, tem algo errado com o teste e/ou código. Uma revisão geral deixará claro o motivo e aplicando a correção dos problemas, temos de volta o cenário ideal.

Vale uma dica: sempre que existir um problema como esse, ou outros complicados, resolver os problemas é a prioridade máxima, não existe nada mais importante, a não ser que você goste de cenários caóticos na véspera de entrada em produção e/ou em produção.


TDD

Repita até o sucesso:
  1. Escreva os testes (novas funcionalidades ou melhorias)
  2. Escreva um código que passa nos testes (o mínimo possível, não se preocupe em acertar na primeira vez, nem em fazer algo muito elaborado)
  3. Refactory (Melhore o código até atingir os padrões desejados)
É isso!

WebSphere AS, ActiveMQ and XA Transaction

Hi there,
after a frustrating long time research on Google results from the "active mq xa webphsere" search, I decided to write a post about it. I lost several hours to realize that it is just about the minor details, as I couldn't find a text explaining the whole thing, I tried a bunch of different sets and it is finally working.
Here is what you need to do:
On WAS (WebSphere Application Server):
  1. Go to "Resources -> JMS -> JMS Providers" (Menu on the left)
  2. Pick up a scope (Cell, Node, Server)
  3. Click on the "New" button
  4. Set  the attributes as follows:
    • Name: ActiveMQ
    • <<Class path / Native library Path>> - If your application is deployed with "activemq" libs you don't need to worry about these attributes.
    • External initial context factory: org.apache.activemq.jndi.ActiveMQWASInitialContextFactory
    • External provider URL: tcp://localhost:61616
  5. Click on the "Apply" button
  6. On "Additional Properties" tab (at the right) click on "Custom properties" link
  7. Add the following properties:
    (Remember: it is a 'key=value' pair property, the first 'text box' is the 'key', the second one is the 'value')
    • java.naming.connectionFactoryNames = QueueConnectionFactory
    • xa = true
  8. Go to "Resources -> JMS -> Connection factories" (Menu on the left)
  9. Pick up a scope (the same as you did on step 2)
  10. Click on the "New"button
  11. Set the attributes as follows:
    • Name: Queue Connection Factory
    • JNDI name: QueueConnectionFactory
    • External JNDI name: QueueConnectionFactory
    • Security settings -> Mapping-configuration alias: ClientCointainer
  12. That is it! Done!

quinta-feira, 17 de maio de 2012

JPA (EclipseLink) and Complex Parameters Stored Procedures

As JAVA has a limited support to Stored Procedures (SP) and there still are people that think of it like one of the "Wonders of the Software's World", some frameworks and a hard work can help solving some complex problems that clients love to come up with.

In this sample I m working with ORACLE as my database server, so the scripts were made for it.

The SP that I m dealing here are far away from usual, they got TYPE definitions in the packages and cursors all mixed together.

The EclipseLink project (http://www.eclipse.org/eclipselink) has a great SP support but some SP calls could be a real nightmare.

Glossary:
TIPO means TYPE
ENTRADA means INPUT
USUARIO means USER
SISTEMA means SYSTEM
RETORNO means OUTPUT
MENSAGEM means MESSAGE
Procedure sample:

 PACKAGE  RBR_SP_SAMPLE AS  
   
  TYPE TIPO_ENTRADA IS RECORD (  
   NM_USUARIO VARCHAR2(30),  
   ID_SISTEMA NUMBER(15)  
  );  
   
  TYPE TIPO_RETORNO IS RECORD (  
   TIPO_MENSAGEM NUMBER(15),  
   DESC_MENSAGEM VARCHAR2(30)  
  );  

  PROCEDURE SP_LISTA_USUARIO (  
   PR_NM_USUARIO IN  RBR_SP_SAMPLE.TIPO_ENTRADA,  
   PR_VC_USUARIO OUT SYS_REFCURSOR,  
   PR_TIPO_RETORNO OUT RBR_SP_SAMPLE.TIPO_RETORNO  
  );  
   
 END RBR_SP_SAMPLE;  


Now we have our SP sample, now let's define the TYPES as OBJECTS:

 CREATE OR REPLACE TYPE O_TIPO_ENTRADA AS OBJECT (  
   NM_USUARIO VARCHAR2(30),  
   ID_SISTEMA  NUMBER(15)  
  );  
   
  CREATE OR REPLACE TYPE O_TIPO_RETORNO AS OBJECT (  
   DESC_MENSAGEM VARCHAR2(30),  
   TIPO_MENSAGEM NUMBER(15)  
  );  


Those objects will be very handy in the mapping process.

Ok then code...

Let's start defining an interface:

 package br.com.brainsoftware.rbr.storedprocedure;  
   
 import java.util.Collection;  
   
 public interface StoredProcedure<ENT, RET, TYPE> {  
   
      /**  
       * Call the stored procedure and prepare the results  
       *   
       * @throws StoredProcedureException  
       */  
      public void call(ENT entry) throws StoredProcedureException;  
   
      /**  
       * After call the <code>call()</code> method this one should return  
       * <code>true</code>  
       *   
       * @return  
       */  
      boolean isReady();  
   
      /**  
       * Only call this method after <code>call()</code> and  
       * <code>isReady()</code>  
       *   
       * @return  
       */  
      public RET getReturn();  
   
      /**  
       * Only call this method after <code>call()</code> and  
       * <code>isReady()</code>  
       *   
       * @return  
       */  
      public Collection<TYPE> getResults();  
 }  
   

Now an abstract class that implements the defined interface, controls the data source connection and defines the type and object mapping:


 package br.com.brainsoftware.rbr.storedprocedure;  
   
 import org.eclipse.persistence.logging.SessionLog;  
 import org.eclipse.persistence.sessions.DatabaseSession;  
 import org.eclipse.persistence.sessions.Project;  
 import org.eclipse.persistence.sessions.Session;  
   
 import br.com.brainsoftware.rbr..pojo.RWEntrada;  
 import br.com.brainsoftware.rbr..pojo.RWRetorno;  
 import br.com.brainsoftware.rbr..util.ConfigUtil;  
   
 public abstract class AbstractStoredProcedure<ENT, RET, TYPE> implements  
           StoredProcedure<ENT, RET, TYPE> {  
      protected Session session;  
      protected boolean ready = false;  
   
      public AbstractStoredProcedure() {  
           // Configuring connection properties  
           Project project = new Project(ConfigUtil.getLogin());  
   
           // Mapping input parameter type - O_TIPO_ENTRADA -> TipoEntrada  
           project.addDescriptor(TipoEntrada.getMapping());  
   
           // Mapping output parameter type - O_TIPO_RETORNO -> TipoRetorno  
           project.addDescriptor(TipoRetorno.getMapping());  
   
           // Connecting to database  
           session = project.createDatabaseSession();  
           session.setLogLevel(SessionLog.FINE);  
           ((DatabaseSession) session).login();  
      }  
 }  



Mapping classes:

 package br.com.brainsoftware.rbr.pojo;  
   
 import java.io.Serializable;  
   
 import org.eclipse.persistence.descriptors.ClassDescriptor;  
 import org.eclipse.persistence.mappings.DirectToFieldMapping;  
 import org.eclipse.persistence.mappings.structures.ObjectRelationalDataTypeDescriptor;  
 import org.eclipse.persistence.platform.database.jdbc.JDBCTypes;  
 import org.eclipse.persistence.platform.database.oracle.plsql.PLSQLrecord;  
   
 public class TipoEntrada implements Serializable {  
   
      public static final String PARAMETER_NAME = "PR_NM_USUARIO";  
      private static final long serialVersionUID = 2165067461541295168L;  
   
      // Mapping  
      private static ObjectRelationalDataTypeDescriptor recordDescriptor;  
      private static PLSQLrecord record;  
      static {  
           recordDescriptor = new ObjectRelationalDataTypeDescriptor();  
           recordDescriptor.descriptorIsAggregate();  
           recordDescriptor.setJavaClass(TipoEntrada.class);  
           recordDescriptor.setAlias("Entrada");  
           recordDescriptor.setStructureName("O_TIPO_ENTRADA");  
           DirectToFieldMapping nmUsuarioMapping = new DirectToFieldMapping();  
           nmUsuarioMapping.setAttributeName("nmUsuario");  
           nmUsuarioMapping.setFieldName("NM_USUARIO");  
           recordDescriptor.addMapping(nmUsuarioMapping);  
           DirectToFieldMapping idSistemaMapping = new DirectToFieldMapping();  
           idSistemaMapping.setAttributeName("idSistema");  
           idSistemaMapping.setFieldName("ID_SISTEMA");  
           recordDescriptor.addMapping(idSistemaMapping);  
   
           record = new PLSQLrecord();  
           record.setTypeName("RBR_SP_SAMPLE.TIPO_ENTRADA");  
           record.setCompatibleType("O_TIPO_ENTRADA");  
           record.setJavaType(TipoEntrada.class);  
           record.addField("NM_USUARIO", JDBCTypes.VARCHAR_TYPE);  
           record.addField("ID_SISTEMA", JDBCTypes.NUMERIC_TYPE);  
      }  
   
      private String nmUsuario;  
      private String idSistema;  
   
      public TipoEntrada() {  
      }  
   
      public TipoEntrada(String nmUsuario, String idSistema) {  
           super();  
           this.nmUsuario = nmUsuario;  
           this.idSistema = idSistema;  
      }  
   
      public String getNmUsuario() {  
           return nmUsuario;  
      }  
   
      public void setNmUsuario(String nmUsuario) {  
           this.nmUsuario = nmUsuario;  
      }  
   
      public String getIdSistema() {  
           return idSistema;  
      }  
   
      public void setIdSistema(String idSistema) {  
           this.idSistema = idSistema;  
      }  
   
      public String toString() {  
           return String.format("TipoEntrada [nmUsuario=%s, idSistema=%s]", nmUsuario,  
                     idSistema);  
      }  
   
      public static ClassDescriptor getMapping() {  
           return recordDescriptor;  
      }  
   
      public static PLSQLrecord getRecord() {  
           return record;  
      }  
 }  
   

 package br.com.brainsoftware.rbr.pojo;  
   
 import java.io.Serializable;  
   
 import org.eclipse.persistence.descriptors.ClassDescriptor;  
 import org.eclipse.persistence.mappings.DirectToFieldMapping;  
 import org.eclipse.persistence.mappings.structures.ObjectRelationalDataTypeDescriptor;  
 import org.eclipse.persistence.platform.database.jdbc.JDBCTypes;  
 import org.eclipse.persistence.platform.database.oracle.plsql.PLSQLrecord;  
   
 public class TipoRetorno implements Serializable {  
   
      public static final String PARAMETER_NAME = "PR_TIPO_RETORNO";  
      private static final long serialVersionUID = -8192064531359394512L;  
   
      // Mapping  
      private static ObjectRelationalDataTypeDescriptor recordDescriptor;  
      private static PLSQLrecord record;  
      static {  
           recordDescriptor = new ObjectRelationalDataTypeDescriptor();  
           recordDescriptor.descriptorIsAggregate();  
           recordDescriptor.setJavaClass(TipoRetorno.class);  
           recordDescriptor.setAlias("Retorno");  
           recordDescriptor.setStructureName("O_TIPO_RETORNO");  
           DirectToFieldMapping descMensagemMapping = new DirectToFieldMapping();  
           descMensagemMapping.setAttributeName("descMensagem");  
           descMensagemMapping.setFieldName("DESC_MENSAGEM");  
           recordDescriptor.addMapping(descMensagemMapping);  
           DirectToFieldMapping tipoMensagemMapping = new DirectToFieldMapping();  
           tipoMensagemMapping.setAttributeName("tipoMensagem");  
           tipoMensagemMapping.setFieldName("TIPO_MENSAGEM");  
           recordDescriptor.addMapping(tipoMensagemMapping);  
   
           record = new PLSQLrecord();  
           record.setTypeName("RBR_SP_SAMPLE.TIPO_RETORNO");  
           record.setCompatibleType("O_TIPO_RETORNO");  
           record.setJavaType(TipoRetorno.class);  
           record.addField("DESC_MENSAGEM", JDBCTypes.VARCHAR_TYPE);  
           record.addField("TIPO_MENSAGEM", JDBCTypes.NUMERIC_TYPE);  
      }  
   
      private String tipoMensagem;  
      private String descMensagem;  
   
      public TipoRetorno() {  
      }  
   
      public TipoRetorno(String tipoMensagem, String descMensagem) {  
           super();  
           this.tipoMensagem = tipoMensage;  
           this.descMensagem = descMensagem;  
      }  
   
      public String getTipoMensagem() {  
           return tipoMensagem;  
      }  
   
      public void setTipoMensagem(String tipoMensagem) {  
           this.tipoMensagem = tipoMensagem;  
      }  
   
      public String getDescMensagem() {  
           return descMensagem;  
      }  
   
      public void setDescMensagem(String descMensagem) {  
           this. descMensagem = descMensagem;  
      }  
   
      public String toString() {  
           return String.format("TipoRetorno [tipoMensagem=%s, descMensagem=%s]",tipoMensagem,  
                     descMensagem);  
      }  
   
      public static ClassDescriptor getMapping() {  
           return recordDescriptor;  
      }  
   
      public static PLSQLrecord getRecord() {  
           return record;  
      }  
 }  

An exception:

 package br.com.brainsoftware.rbr.storedprocedure;  
   
 public class StoredProcedureException extends Exception {  
   
      private static final long serialVersionUID = 6325249553440417092L;  
   
      public StoredProcedureException() {  
      }  
   
      public StoredProcedureException(String arg0) {  
           super(arg0);  
      }  
   
      public StoredProcedureException(Throwable arg0) {  
           super(arg0);  
      }  
   
      public StoredProcedureException(String arg0, Throwable arg1) {  
           super(arg0, arg1);  
      }  
   
 }  

By extend the AbstracStoredProcedure class, the next class will call the SP and prepares the data so they can be used properly and easier:

 package br.com.brainsoftware.rbr.storedprocedure;  
   
 import java.util.ArrayList;  
 import java.util.Collection;  
 import java.util.Collections;  
 import java.util.Enumeration;  
 import java.util.List;  
 import java.util.Vector;  
   
 import org.eclipse.persistence.platform.database.oracle.plsql.PLSQLStoredProcedureCall;  
 import org.eclipse.persistence.queries.DataReadQuery;  
 import org.eclipse.persistence.sessions.DatabaseRecord;  
   
 import br.com.brainsoftware.rbr.databasetype.OracleCursorDatabaseType;  
 import br.com.brainsoftware.rbr.pojo.Usuario;  
 import br.com.brainsoftware.rbr.pojo.TipoEntrada;  
 import br.com.brainsoftware.rbr.pojo.TipoRetorno;  
 import br.com.brainsoftware.rbr.util.ReflectionUtil;  
   
 /**  
  * Chama a procedure usando objetos de entrada e saida e tratando o resultado do  
  * cursor  
  *   
  */  
 public class ListaMaterial extends  
           AbstractStoredProcedure<RWEntrada, RWRetorno, Material> {  
   
      private static final String CURSOR_PARAMETER_NAME = "PR_VC_USUARIO";  
      private static final String PROCEDURE_NAME = "RBR_SP_SAMPLE.SP_LISTA_USUARIO";  
      private TipoRetorno retorno = null;  
      private List<Usuario> usuarios = Collections.emptyList();  
   
      @SuppressWarnings("unchecked")  
      public void call(TipoEntrada entrada) throws StoredProcedureException {  
           // Preparing the SP call  
           PLSQLStoredProcedureCall call = new PLSQLStoredProcedureCall();  
           call.setProcedureName(PROCEDURE_NAME);  
           call.addNamedArgument(TipoEntrada.PARAMETER_NAME, TipoEntrada.getRecord());  
           call.addNamedOutputArgument(TipoRetorno.PARAMETER_NAME,  
                     TipoRetorno.getRecord());  
           call.addNamedOutputArgument(CURSOR_PARAMETER_NAME,  
                     new OracleCursorDatabaseType());  
   
           // Preparing the query  
           DataReadQuery query = new DataReadQuery();  
           query.setCall(call);  
           query.addArgument(TipoEntrada.PARAMETER_NAME);  
   
           // Adding arguments  
           List<Object> queryArgs = new ArrayList<Object>();  
           queryArgs.add(entrada);  
           query.bindAllParameters();  
   
           // Executing query  
           Object result = session.executeQuery(query, queryArgs);  
   
           // Treating the results  
           Vector<DatabaseRecord> results = new Vector<DatabaseRecord>();  
           Enumeration<DatabaseRecord> records = ((Vector<DatabaseRecord>) result)  
                     .elements();  
           while (records.hasMoreElements()) {  
                DatabaseRecord record = records.nextElement();  
                retorno = (TipoRetorno) record.get(TipoRetorno.PARAMETER_NAME);  
                results = (Vector<DatabaseRecord>) record  
                          .get(CURSOR_PARAMETER_NAME);  
           }  
   
           try {  
                List<DatabaseRecord> outList = new ArrayList<DatabaseRecord>();  
                outList.addAll(results);  
                materiais = ReflectionUtil.mapper(outList, Usuario.class);  
                ready = true;  
           } catch (Exception e) {  
                throw new StoredProcedureException(e);  
           }  
      }  
   
      public boolean isReady() {  
           return ready;  
      }  
   
      public RWRetorno getReturn() {  
           return retorno;  
      }  
   
      public Collection<Usuario> getResults() {  
           return usuarios;  
      }  
 }  
   


A little hacking... the returning data must work like a cursor, therefore the next class implements that behavior:



 package br.com.brainsoftware.rbr.databasetype;  
   
 import static org.eclipse.persistence.internal.helper.DatabaseType.DatabaseTypeHelper.databaseTypeHelper;  
 import static org.eclipse.persistence.internal.helper.Helper.NL;  
   
 import java.util.List;  
 import java.util.ListIterator;  
   
 import oracle.jdbc.OracleTypes;  
   
 import org.eclipse.persistence.internal.helper.DatabaseField;  
 import org.eclipse.persistence.internal.helper.SimpleDatabaseType;  
 import org.eclipse.persistence.internal.sessions.AbstractRecord;  
 import org.eclipse.persistence.platform.database.DatabasePlatform;  
 import org.eclipse.persistence.platform.database.oracle.plsql.PLSQLStoredProcedureCall;  
 import org.eclipse.persistence.platform.database.oracle.plsql.PLSQLargument;  
 import org.eclipse.persistence.queries.StoredProcedureCall;  
 import org.eclipse.persistence.sessions.DatabaseRecord;  
   
 @SuppressWarnings("rawtypes")  
 public class OracleCursorDatabaseType implements SimpleDatabaseType {  
   
      public boolean isComplexDatabaseType() {  
           return false;  
      }  
   
      public boolean isJDBCType() {  
           return false;  
      }  
   
      public int getSqlCode() {  
           return OracleTypes.CURSOR;  
      }  
   
      public int getConversionCode() {  
           return getSqlCode();  
      }  
   
      public String getTypeName() {  
           return "SYS_REFCURSOR";  
      }  
   
      public int computeInIndex(PLSQLargument inArg, int newIndex,  
                ListIterator<PLSQLargument> i) {  
           inArg.outIndex = newIndex;  
           return ++newIndex;  
      }  
   
      public int computeOutIndex(PLSQLargument outArg, int newIndex,  
                ListIterator<PLSQLargument> iterator) {  
           outArg.outIndex = newIndex;  
           return newIndex;  
      }  
   
      public void buildInDeclare(StringBuilder sb, PLSQLargument inArg) {  
           // TODO Auto-generated method stub  
           System.out.println("buildInDeclare");  
      }  
   
      public void buildOutDeclare(StringBuilder sb, PLSQLargument outArg) {  
           sb.append(" ");  
           sb.append(databaseTypeHelper.buildTarget(outArg));  
           sb.append(" ");  
           sb.append(getTypeName());  
           sb.append(";");  
           sb.append(NL);  
      }  
   
      public void buildBeginBlock(StringBuilder sb, PLSQLargument arg,  
                PLSQLStoredProcedureCall call) {  
           // TODO Auto-generated method stub  
           System.out.println("buildBeginBlock");  
   
      }  
   
      public void buildOutAssignment(StringBuilder sb, PLSQLargument outArg,  
                PLSQLStoredProcedureCall call) {  
           String target = databaseTypeHelper.buildTarget(outArg);  
           sb.append(" :");  
           sb.append(outArg.outIndex);  
           sb.append(" := ");  
           sb.append(target);  
           sb.append(";");  
           sb.append(NL);  
      }  
   
      public void translate(PLSQLargument arg, AbstractRecord translationRow,  
                AbstractRecord copyOfTranslationRow,  
                List<DatabaseField> copyOfTranslationFields,  
                List<DatabaseField> translationRowFields,  
                List translationRowValues, StoredProcedureCall call) {  
           // TODO Auto-generated method stub  
           System.out.println("translate");  
      }  
   
      public void buildOutputRow(PLSQLargument outArg, AbstractRecord outputRow,  
                DatabaseRecord newOutputRow, List<DatabaseField> outputRowFields,  
                List outputRowValues) {  
           databaseTypeHelper.buildOutputRow(outArg, outputRow, newOutputRow,  
                     outputRowFields, outputRowValues);  
      }  
   
      public void logParameter(StringBuilder sb, Integer direction,  
                PLSQLargument arg, AbstractRecord translationRow,  
                DatabasePlatform platform) {  
           // TODO Auto-generated method stub  
           System.out.println("logParameter");  
      }  
 }  


Some util classes:

 package br.com.brainsoftware.rbr.util;  
   
 import org.eclipse.persistence.platform.database.oracle.Oracle11Platform;  
 import org.eclipse.persistence.sessions.DatabaseLogin;  
   
 public class ConfigUtil {  
   
      public static DatabaseLogin getLogin() {  
           String USERNAME = "user_name";  
           String PASSWORD = "password";  
           String URL = "jdbc:oracle:thin:@192.168.51.10:1521:XE";  
           String DRIVER = "oracle.jdbc.driver.OracleDriver";  
   
           DatabaseLogin login = new DatabaseLogin();  
           login.setUserName(USERNAME);  
           login.setPassword(PASSWORD);  
           login.setConnectionString(URL);  
           login.setDriverClassName(DRIVER);  
           login.setDatasourcePlatform(new Oracle11Platform());  
           ((DatabaseLogin) login).bindAllParameters();  
           return login;  
      }  
   
 }  
   

 package br.com.brainsoftware.rbr.util;  
   
 import java.lang.reflect.Constructor;  
 import java.lang.reflect.Field;  
 import java.lang.reflect.InvocationTargetException;  
 import java.lang.reflect.Method;  
 import java.math.BigDecimal;  
 import java.util.ArrayList;  
 import java.util.List;  
   
 import org.eclipse.persistence.sessions.DatabaseRecord;  
   
 public class ReflectionUtil {  
   
      public static Object invokeGetterMethod(Field field, Object object)  
                throws SecurityException, IllegalArgumentException,  
                NoSuchMethodException, IllegalAccessException,  
                InvocationTargetException {  
           String methodName = "get"  
                     + String.valueOf(field.getName().charAt(0)).toUpperCase()  
                     + field.getName().substring(1);  
           Class<?>[] parameterTypes = new Class[0];  
           Method method = object.getClass().getMethod(methodName, parameterTypes);  
           return method.invoke(object);  
      }  
   
      public static Object invokeSetterMethod(Field field, Object object,  
                Object value) throws SecurityException, IllegalArgumentException,  
                NoSuchMethodException, IllegalAccessException,  
                InvocationTargetException {  
           Method method = null;  
           Object result = null;  
           String methodName = "set" + field.getName();  
           for (Method m : object.getClass().getMethods()) {  
                if (methodName.equalsIgnoreCase(m.getName())) {  
                     method = m;  
                }  
           }  
           if (null != method) {  
                result = method.invoke(object, value);  
           }  
           return result;  
      }  
   
      public static <T> List<T> mapper(List<DatabaseRecord> records, Class<T> type)  
                throws SecurityException, IllegalArgumentException,  
                InstantiationException, IllegalAccessException,  
                NoSuchMethodException, InvocationTargetException {  
           List<T> result = new ArrayList<T>();  
           for (DatabaseRecord record : records) {  
                result.add(setValues(record, type));  
           }  
   
           return result;  
      }  
   
      @SuppressWarnings("unchecked")  
      private static <T> T setValues(DatabaseRecord record, Class<T> type)  
                throws InstantiationException, IllegalAccessException,  
                SecurityException, IllegalArgumentException, NoSuchMethodException,  
                InvocationTargetException {  
           Object result = type.newInstance();  
   
           for (Field field : result.getClass().getDeclaredFields()) {  
                Object value = record.get(field.getName().toUpperCase());  
                value = adjustType(field.getType(), value);  
   
                ReflectionUtil.invokeSetterMethod(field, result, value);  
           }  
   
           return (T) result;  
      }  
   
      private static Object adjustType(Class<?> type, Object value)  
                throws IllegalArgumentException, InstantiationException,  
                IllegalAccessException, InvocationTargetException {  
           Object result = value;  
   
           if (value == null) {  
                return result;  
           }  
   
           if (BigDecimal.class.equals(value.getClass())) {  
                BigDecimal new_name = (BigDecimal) value;  
                int helper = new_name.intValue();  
                for (Constructor<?> constructor : type.getDeclaredConstructors()) {  
                     Class<?>[] parameters = constructor.getParameterTypes();  
                     if (parameters.length == 1 && parameters[0].equals(int.class)) {  
                          result = constructor.newInstance(helper);  
                          break;  
                     }  
                }  
           }  
           return result;  
      }  
 }  
   


The POJO class (user data) and its members as well as the cursor statement, you can write by yourself. ;)

Now the main class:

   
      public static void main(String[] args) throws Exception {  
           TipoRetorno retorno = null;  
           List<Usuario> usuarios = Collections.emptyList();  
           ListaUsuario listaUsuario = new ListaUsuario();  
           TipoEntrada entrada = new TipoEntrada();  
           entrada.setNmUsuario("USER1");  
           entrada.setIdSistema("1");  
           listaUsuario.call(entrada);  
           if (listaUsuario.isReady()) {  
                retorno = listaUsuario.getReturn();  
                usuarios = (List<Usuario>) listaUsuario.getResults();  
           }  
           // Displaying the results  
           System.out.format("Retono: Tipo:[%s] Mensagem:[%s]%n",  
                     retorno.getTipoMensagem(), retorno.getDescMensagem());  
           for (Usuario usuario : usuarios) {  
                System.out.format("Usuario [%s]%n", usuario);  
           }  
   
      }  
   


Well, we are done. Wasn't that hard, was it?

I tried to solve the problem using EclipseLink's resources but... no luck, so I took a deep breath and started reading the code, this way I  would understand better how it works, then a implementation of DatabaseType interface and a couple of classes to create a interesting solution.

sexta-feira, 27 de janeiro de 2012

Flat TVs

Ainda está difícil de achar alguém que entenda sobre as tecnologias conversando por aí...
Vou postar aqui para ter como falar: "Na boa, vai no Google e digite: Ronaldo Blanc Flat TVs".

Inspiração:

Muita gente vem falar "de entendido":
"Eu vi e a imagem é muito melhor..."

Ah é?
Viu nada... é muito fácil enganar os olhos... pergunte para um mágico... nem precisa ser o Criss Angel, com aquelas maluquices.

As telas tem algumas características, vale a pena revê-las para não fazer confusão:

1. Resolução
É a quantidade de pixels(pontos) na tela.
Quanto mais ponto, melhor a imagem.

Vale lembrar:
1 kg de chumbo não pesa mais que 1 kg de algodão ou de pena, não interessa que você pense diferente, NÃO PESA!!!
Então não venha me dizer que uma LED Full HD(1920x1080) "tem resolução melhor" que uma LCD Full HD(1920x1080), NÃO TEM!!!

2. Contraste
O que importa nessa medida é como a cor preta se apresenta, seja ela um cinza bem escuro ou um preto profundo. Nesse quesito as TVs de Plasma e LED, tem uma enorme vantagem (com destaque para o plasma): como elas são feitas de células independentes o preto é preto! Na LCD o preto é meio cinza, pois existe uma luz atrás da tela iluminado a imagem gerada nela (backlight).  Como queremos cores brilhantes, o preto acaba sendo prejudicado na LCD, por ter a mesma fonte de luz para todos os pontos.

3. Brilho
Depende muito das condições de iluminação do ambiente, mas é uma medida importante, vamos analisar.

As TVs de LCD, por terem uma luz, óbvio, tem melhor brilho para ambiente iluminados. É uma vantagem do LCD para esse tipo de ambiente, pois você pode ver a imagem sem reflexos e etc...
Em locais pouco iluminados as TVs de plasma e de LED, são mais interessantes, pelo efeito criado pelo alto contraste, as imagens ficam com mais profundidade de cores, ou seja, mais vivas e mais reais*.

* Sobre realidade em imagens - É complicado usar esse termo, mas é o melhor aqui. Imagens reais, que se aproximam do mundo real, em geral são um pouco mais opacas e 'apagadas' do que queremos em uma TV, para vivermos o mundo de fantasia. Vai uma dica, a Sony tem trabalhado muito bem em trazer imagens reais, acontece que um ou outro vem e diz:  "Aquela imagem ali é melhor do que essa da Sony...", simplesmente porque está mais "colorida", no fim queremos mesmo ver isso.

4. Tempo de reposta
Apesar de todo mundo achar que as telas de LED são mais "legais", porque custam mais caro e é uma tecnologia nova, na verdade o tempo de resposta é menor nas telas de plasma, assim, para assistir algo mais "fluído", como um joguinho de poker :P (hahah), agora sério, um jogo de basquete, tênis ou futebol a TV de plasma é mais indicada.

Novas TVs tem altas frequências de refresh e etc... mas não podemos confundir o tempo de refresh(refresh rate) com o tempo de resposta (response time), basicamente é o seguinte:
Response time é o tempo que demora para um pixel ir do cinza->branco->cinza.
Refresh rate é relacionado com a imagem toda, é o tempo para atualizar e sincronizar um frame na tela.

5. Ângulo de visão
Depende de onde a pessoa está sentada em relação à TV a imagem pode parecer distorcida, invertida(em relação às cores) ou simplesmente 'bagunçada'. Aqui outra vez temos vantagens na tecnologia plasma.

Considerações:

1) Plasma, tem melhor contraste, menores tempos de resposta e refresh, maior ângulo de visão. WOW!!! "Eu quero uma dessas!!!"
Ok, eu também, mas nem tudo é virtude, como tudo, elas tem alguns defeitos:
a) Esquentam muito, para gerar o estado plasma é necessário muita energia e isso gera também calor.
b) Consomem bastante energia, cerca de 5-6 lâmpadas de 60W.
c) Geralmente (existem algumas exceções) o menor tamanho é 42".

Você pode estar pensando: "E daí Ronaldão?? Quanto maior a tela melhor...", muito bem,  eu concordo, mas devemos nos lembrar da regra: <Tamanho da TV> * 2,5 = distância que você deve estar dela.
Então em uma tela de 42"(106 cm), você deve estar no mínimo a 265 cm (2,6m) de distância, ou seja o tamanho da tela e o tamanho do cômodo devem ser compatíveis.

2) LCD, tem melhor brilho, consomem menos energia se comparadas com as TVs de plasma e tem bom preço.
Depois de tudo isso, você vem falar de LCD?
Bom,  eu mesmo tenho uma TV de LCD.
Por que?
Porque a LED estava um pouco cara, eu precisava de uma tela menor que 42" (Plasma) e essa estava em promoção :D (money talks).

Se o ambiente é muito iluminado (a minha sala tinha portas de vidro para a varanda, ou seja muito sol) LCD é a melhor opção, já que as outras opções são suscetíveis ao reflexo (glare).

a) "Baixa qualidade" de cores devido ao baixo contraste
b) Pode criar "borrões" em imagens com muito movimentos

3) LED
Todo mundo fala/quer uma TV com essa tecnologia. E eu também quero! :D
 O meu Vaio e o meu Mac são equipados com telas de LED/LCD.

"Peraí....LCD/LED????"
Sim, é LED porque a iluminação é por LEDs, ou seja, no lugar de uma única fonte de luz, existem várias.

"Legal, LED, o que isso?"
O diodo emissor de luz(LED), é famoso, por acender no seu controle remoto quando você aperta um botão, aquela luzinha vermelha, na TV, é o mesmo conceito, mas são peças menores, e um painel cheio de pequenos LEDs torna a iluminação mais interessante (chegando perto da plasma) já que agora a iluminação tem células individuais. WOW!

Os maiores atrativos são:
A) a espessura (são muito fininhas - vale um cuidado: as mais finas, geralmente, tem iluminação lateral, portanto não tem tanto ganho em contraste. As LED "backlit" são as que chegam mais perto da plasma) e
B) o baixo consumo de energia (uma tela de 55" consome o mesmo que 3 lâmpadas de 60W).
Assim como a tela de plasma as cores e o contraste são melhores que a de LCD.

Não tem muito o que criticar aqui, tem baixo consumo, bom contraste, são bem recentes, então tem boas taxas de refresh.

a) (Muito difícil de notar) Assim como a LCD, pode causar "borrões" em imagens com muito movimento. (Como é uma tecnologia mais nova, a tela tem bom refresh rate (100, 120, 200 Hz, ...) mas ainda perde no response time para o (quase instantâneo) da plasma.

Conclusões:

Se você gosta de esportes, reunir várias pessoas para assistir esses eventos e não se incomoda em gastar um pouco de energia... a sua TV tem que ter uma tela de PLASMA!

Parece que esse estado da matéria(plasma) é a coisa mais perfeita que apareceu para cortar aço como se fosse manteiga e para gerar imagens com cores vivas, altíssimo contraste e com alta velocidade. É realmente muito complicado chegar nas cores/velocidade do plasma, como cada pixel é controlado individualmente (em ligado/desligado) enquanto um tem um preto profundo o outro tem um brilho fantástico.

Vamos esperar as novas telas de OLED para ver o que elas podem fazer contra o plasma.
Por enquanto a tecnologia que ganha em quase todos os quesitos importantes para se analisar uma TV ainda é a plasma. As TVs de LED, tem seu espaço garantido nos próximos anos, fazem um bom trabalho tentando alcançar uma tecnologia que se adapta tão bem às nossas necessidades de cor/brilho/contraste/frequência (ficou devendo no consumo, não se pode ganhar todas).

Agora, relaxe e vá ver alguma coisa na sua TV não plana de tubo. :D hahahaha
Afinal, independente da qualidade da imagem, o importante é o seu interesse no assunto.

Abs,
Ronaldão

quarta-feira, 30 de novembro de 2011

JPA (EclipseLink) e Stored Procedures com parâmetros complexos

[English version: http://ronaldoblanc.blogspot.com.br/2012/05/jpa-eclipselink-and-complex-parameters.html]
Como o JAVA tem pouco suporte ao uso de stored procedures (SP) e ainda tem gente que acha que elas são as maravilhas do mundo dos software,  alguns frameworks e algum trabalho podem ajudar a resolver problemas complexos, como os clientes adoram criar.

Nesse caso estou usando o ORACLE como banco de dados e os códigos postados servem para esse banco de dados.

Bom, as SPs que estou usando nesse artigo são bem incomuns, tipos definidos dentro dos pacotes e cursores, tudo junto.

O EclipseLink (http://www.eclipse.org/eclipselink/) tem um bom suporte ao uso de SPs, mesmo assim algumas chamadas são bem complicadas. Para resolver um problema apresentado por um cliente, tive que analisar bem esse framework e criar uma pequena "extensão" dele.

Exemplo de procedure:

 PACKAGE  RBR_SP_SAMPLE AS  
   
  TYPE TIPO_ENTRADA IS RECORD (  
   NM_USUARIO VARCHAR2(30),  
   ID_SISTEMA NUMBER(15)  
  );  
   
  TYPE TIPO_RETORNO IS RECORD (  
   TIPO_MENSAGEM NUMBER(15),  
   DESC_MENSAGEM VARCHAR2(30)  
  );  

  PROCEDURE SP_LISTA_USUARIO (  
   PR_NM_USUARIO IN  RBR_SP_SAMPLE.TIPO_ENTRADA,  
   PR_VC_USUARIO OUT SYS_REFCURSOR,  
   PR_TIPO_RETORNO OUT RBR_SP_SAMPLE.TIPO_RETORNO  
  );  
   
 END RBR_SP_SAMPLE;  

Agora que temos um exemplo de SP, precisamos criar os tipos como Objects:

 CREATE OR REPLACE TYPE O_TIPO_ENTRADA AS OBJECT (  
   NM_USUARIO VARCHAR2(30),  
   ID_SISTEMA  NUMBER(15)  
  );  
   
  CREATE OR REPLACE TYPE O_TIPO_RETORNO AS OBJECT (  
   DESC_MENSAGEM VARCHAR2(30),  
   TIPO_MENSAGEM NUMBER(15)  
  );  


Esses objetos serão utéis para o mapeamento.

Ok, o código...

Para começar uma interface:

 package br.com.brainsoftware.rbr.storedprocedure;  
   
 import java.util.Collection;  
   
 public interface StoredProcedure<ENT, RET, TYPE> {  
   
      /**  
       * Call the stored procedure and prepare the results  
       *   
       * @throws StoredProcedureException  
       */  
      public void call(ENT entry) throws StoredProcedureException;  
   
      /**  
       * After call the <code>call()</code> method this one should return  
       * <code>true</code>  
       *   
       * @return  
       */  
      boolean isReady();  
   
      /**  
       * Only call this method after <code>call()</code> and  
       * <code>isReady()</code>  
       *   
       * @return  
       */  
      public RET getReturn();  
   
      /**  
       * Only call this method after <code>call()</code> and  
       * <code>isReady()</code>  
       *   
       * @return  
       */  
      public Collection<TYPE> getResults();  
 }  
   


Agora uma classe abstrata implementando a interface e preparando o acesso ao banco de dados com o mapeamento dos objetos e tipos:

 package br.com.brainsoftware.rbr.storedprocedure;  
   
 import org.eclipse.persistence.logging.SessionLog;  
 import org.eclipse.persistence.sessions.DatabaseSession;  
 import org.eclipse.persistence.sessions.Project;  
 import org.eclipse.persistence.sessions.Session;  
   
 import br.com.brainsoftware.rbr..pojo.RWEntrada;  
 import br.com.brainsoftware.rbr..pojo.RWRetorno;  
 import br.com.brainsoftware.rbr..util.ConfigUtil;  
   
 public abstract class AbstractStoredProcedure<ENT, RET, TYPE> implements  
           StoredProcedure<ENT, RET, TYPE> {  
      protected Session session;  
      protected boolean ready = false;  
   
      public AbstractStoredProcedure() {  
           // Configuring connection properties  
           Project project = new Project(ConfigUtil.getLogin());  
   
           // Mapping input parameter type - O_TIPO_ENTRADA -> TipoEntrada  
           project.addDescriptor(TipoEntrada.getMapping());  
   
           // Mapping output parameter type - O_TIPO_RETORNO -> TipoRetorno  
           project.addDescriptor(TipoRetorno.getMapping());  
   
           // Connecting to database  
           session = project.createDatabaseSession();  
           session.setLogLevel(SessionLog.FINE);  
           ((DatabaseSession) session).login();  
      }  
 }  


Classes com os mapeamentos:

 package br.com.brainsoftware.rbr.pojo;  
   
 import java.io.Serializable;  
   
 import org.eclipse.persistence.descriptors.ClassDescriptor;  
 import org.eclipse.persistence.mappings.DirectToFieldMapping;  
 import org.eclipse.persistence.mappings.structures.ObjectRelationalDataTypeDescriptor;  
 import org.eclipse.persistence.platform.database.jdbc.JDBCTypes;  
 import org.eclipse.persistence.platform.database.oracle.plsql.PLSQLrecord;  
   
 public class TipoEntrada implements Serializable {  
   
      public static final String PARAMETER_NAME = "PR_NM_USUARIO";  
      private static final long serialVersionUID = 2165067461541295168L;  
   
      // Mapping  
      private static ObjectRelationalDataTypeDescriptor recordDescriptor;  
      private static PLSQLrecord record;  
      static {  
           recordDescriptor = new ObjectRelationalDataTypeDescriptor();  
           recordDescriptor.descriptorIsAggregate();  
           recordDescriptor.setJavaClass(TipoEntrada.class);  
           recordDescriptor.setAlias("Entrada");  
           recordDescriptor.setStructureName("O_TIPO_ENTRADA");  
           DirectToFieldMapping nmUsuarioMapping = new DirectToFieldMapping();  
           nmUsuarioMapping.setAttributeName("nmUsuario");  
           nmUsuarioMapping.setFieldName("NM_USUARIO");  
           recordDescriptor.addMapping(nmUsuarioMapping);  
           DirectToFieldMapping idSistemaMapping = new DirectToFieldMapping();  
           idSistemaMapping.setAttributeName("idSistema");  
           idSistemaMapping.setFieldName("ID_SISTEMA");  
           recordDescriptor.addMapping(idSistemaMapping);  
   
           record = new PLSQLrecord();  
           record.setTypeName("RBR_SP_SAMPLE.TIPO_ENTRADA");  
           record.setCompatibleType("O_TIPO_ENTRADA");  
           record.setJavaType(TipoEntrada.class);  
           record.addField("NM_USUARIO", JDBCTypes.VARCHAR_TYPE);  
           record.addField("ID_SISTEMA", JDBCTypes.NUMERIC_TYPE);  
      }  
   
      private String nmUsuario;  
      private String idSistema;  
   
      public TipoEntrada() {  
      }  
   
      public TipoEntrada(String nmUsuario, String idSistema) {  
           super();  
           this.nmUsuario = nmUsuario;  
           this.idSistema = idSistema;  
      }  
   
      public String getNmUsuario() {  
           return nmUsuario;  
      }  
   
      public void setNmUsuario(String nmUsuario) {  
           this.nmUsuario = nmUsuario;  
      }  
   
      public String getIdSistema() {  
           return idSistema;  
      }  
   
      public void setIdSistema(String idSistema) {  
           this.idSistema = idSistema;  
      }  
   
      public String toString() {  
           return String.format("TipoEntrada [nmUsuario=%s, idSistema=%s]", nmUsuario,  
                     idSistema);  
      }  
   
      public static ClassDescriptor getMapping() {  
           return recordDescriptor;  
      }  
   
      public static PLSQLrecord getRecord() {  
           return record;  
      }  
 }  
   

 package br.com.brainsoftware.rbr.pojo;  
   
 import java.io.Serializable;  
   
 import org.eclipse.persistence.descriptors.ClassDescriptor;  
 import org.eclipse.persistence.mappings.DirectToFieldMapping;  
 import org.eclipse.persistence.mappings.structures.ObjectRelationalDataTypeDescriptor;  
 import org.eclipse.persistence.platform.database.jdbc.JDBCTypes;  
 import org.eclipse.persistence.platform.database.oracle.plsql.PLSQLrecord;  
   
 public class TipoRetorno implements Serializable {  
   
      public static final String PARAMETER_NAME = "PR_TIPO_RETORNO";  
      private static final long serialVersionUID = -8192064531359394512L;  
   
      // Mapping  
      private static ObjectRelationalDataTypeDescriptor recordDescriptor;  
      private static PLSQLrecord record;  
      static {  
           recordDescriptor = new ObjectRelationalDataTypeDescriptor();  
           recordDescriptor.descriptorIsAggregate();  
           recordDescriptor.setJavaClass(TipoRetorno.class);  
           recordDescriptor.setAlias("Retorno");  
           recordDescriptor.setStructureName("O_TIPO_RETORNO");  
           DirectToFieldMapping descMensagemMapping = new DirectToFieldMapping();  
           descMensagemMapping.setAttributeName("descMensagem");  
           descMensagemMapping.setFieldName("DESC_MENSAGEM");  
           recordDescriptor.addMapping(descMensagemMapping);  
           DirectToFieldMapping tipoMensagemMapping = new DirectToFieldMapping();  
           tipoMensagemMapping.setAttributeName("tipoMensagem");  
           tipoMensagemMapping.setFieldName("TIPO_MENSAGEM");  
           recordDescriptor.addMapping(tipoMensagemMapping);  
   
           record = new PLSQLrecord();  
           record.setTypeName("RBR_SP_SAMPLE.TIPO_RETORNO");  
           record.setCompatibleType("O_TIPO_RETORNO");  
           record.setJavaType(TipoRetorno.class);  
           record.addField("DESC_MENSAGEM", JDBCTypes.VARCHAR_TYPE);  
           record.addField("TIPO_MENSAGEM", JDBCTypes.NUMERIC_TYPE);  
      }  
   
      private String tipoMensagem;  
      private String descMensagem;  
   
      public TipoRetorno() {  
      }  
   
      public TipoRetorno(String tipoMensagem, String descMensagem) {  
           super();  
           this.tipoMensagem = tipoMensage;  
           this.descMensagem = descMensagem;  
      }  
   
      public String getTipoMensagem() {  
           return tipoMensagem;  
      }  
   
      public void setTipoMensagem(String tipoMensagem) {  
           this.tipoMensagem = tipoMensagem;  
      }  
   
      public String getDescMensagem() {  
           return descMensagem;  
      }  
   
      public void setDescMensagem(String descMensagem) {  
           this. descMensagem = descMensagem;  
      }  
   
      public String toString() {  
           return String.format("TipoRetorno [tipoMensagem=%s, descMensagem=%s]",tipoMensagem,  
                     descMensagem);  
      }  
   
      public static ClassDescriptor getMapping() {  
           return recordDescriptor;  
      }  
   
      public static PLSQLrecord getRecord() {  
           return record;  
      }  
 }  

A exceção:

 package br.com.brainsoftware.rbr.storedprocedure;  
   
 public class StoredProcedureException extends Exception {  
   
      private static final long serialVersionUID = 6325249553440417092L;  
   
      public StoredProcedureException() {  
      }  
   
      public StoredProcedureException(String arg0) {  
           super(arg0);  
      }  
   
      public StoredProcedureException(Throwable arg0) {  
           super(arg0);  
      }  
   
      public StoredProcedureException(String arg0, Throwable arg1) {  
           super(arg0, arg1);  
      }  
   
 }  

Classe que chama a SP e prepara os dados para serem usados de forma mais adequada:

 package br.com.brainsoftware.rbr.storedprocedure;  
   
 import java.util.ArrayList;  
 import java.util.Collection;  
 import java.util.Collections;  
 import java.util.Enumeration;  
 import java.util.List;  
 import java.util.Vector;  
   
 import org.eclipse.persistence.platform.database.oracle.plsql.PLSQLStoredProcedureCall;  
 import org.eclipse.persistence.queries.DataReadQuery;  
 import org.eclipse.persistence.sessions.DatabaseRecord;  
   
 import br.com.brainsoftware.rbr.databasetype.OracleCursorDatabaseType;  
 import br.com.brainsoftware.rbr.pojo.Usuario;  
 import br.com.brainsoftware.rbr.pojo.TipoEntrada;  
 import br.com.brainsoftware.rbr.pojo.TipoRetorno;  
 import br.com.brainsoftware.rbr.util.ReflectionUtil;  
   
 /**  
  * Chama a procedure usando objetos de entrada e saida e tratando o resultado do  
  * cursor  
  *   
  */  
 public class ListaMaterial extends  
           AbstractStoredProcedure<RWEntrada, RWRetorno, Material> {  
   
      private static final String CURSOR_PARAMETER_NAME = "PR_VC_USUARIO";  
      private static final String PROCEDURE_NAME = "RBR_SP_SAMPLE.SP_LISTA_USUARIO";  
      private TipoRetorno retorno = null;  
      private List<Usuario> usuarios = Collections.emptyList();  
   
      @SuppressWarnings("unchecked")  
      public void call(TipoEntrada entrada) throws StoredProcedureException {  
           // Preparing the SP call  
           PLSQLStoredProcedureCall call = new PLSQLStoredProcedureCall();  
           call.setProcedureName(PROCEDURE_NAME);  
           call.addNamedArgument(TipoEntrada.PARAMETER_NAME, TipoEntrada.getRecord());  
           call.addNamedOutputArgument(TipoRetorno.PARAMETER_NAME,  
                     TipoRetorno.getRecord());  
           call.addNamedOutputArgument(CURSOR_PARAMETER_NAME,  
                     new OracleCursorDatabaseType());  
   
           // Preparing the query  
           DataReadQuery query = new DataReadQuery();  
           query.setCall(call);  
           query.addArgument(TipoEntrada.PARAMETER_NAME);  
   
           // Adding arguments  
           List<Object> queryArgs = new ArrayList<Object>();  
           queryArgs.add(entrada);  
           query.bindAllParameters();  
   
           // Executing query  
           Object result = session.executeQuery(query, queryArgs);  
   
           // Treating the results  
           Vector<DatabaseRecord> results = new Vector<DatabaseRecord>();  
           Enumeration<DatabaseRecord> records = ((Vector<DatabaseRecord>) result)  
                     .elements();  
           while (records.hasMoreElements()) {  
                DatabaseRecord record = records.nextElement();  
                retorno = (TipoRetorno) record.get(TipoRetorno.PARAMETER_NAME);  
                results = (Vector<DatabaseRecord>) record  
                          .get(CURSOR_PARAMETER_NAME);  
           }  
   
           try {  
                List<DatabaseRecord> outList = new ArrayList<DatabaseRecord>();  
                outList.addAll(results);  
                materiais = ReflectionUtil.mapper(outList, Usuario.class);  
                ready = true;  
           } catch (Exception e) {  
                throw new StoredProcedureException(e);  
           }  
      }  
   
      public boolean isReady() {  
           return ready;  
      }  
   
      public RWRetorno getReturn() {  
           return retorno;  
      }  
   
      public Collection<Usuario> getResults() {  
           return usuarios;  
      }  
 }  
   

Um pouco de hacking... a classe que permite tratar os dados de retorno como CURSOR:

 package br.com.brainsoftware.rbr.databasetype;  
   
 import static org.eclipse.persistence.internal.helper.DatabaseType.DatabaseTypeHelper.databaseTypeHelper;  
 import static org.eclipse.persistence.internal.helper.Helper.NL;  
   
 import java.util.List;  
 import java.util.ListIterator;  
   
 import oracle.jdbc.OracleTypes;  
   
 import org.eclipse.persistence.internal.helper.DatabaseField;  
 import org.eclipse.persistence.internal.helper.SimpleDatabaseType;  
 import org.eclipse.persistence.internal.sessions.AbstractRecord;  
 import org.eclipse.persistence.platform.database.DatabasePlatform;  
 import org.eclipse.persistence.platform.database.oracle.plsql.PLSQLStoredProcedureCall;  
 import org.eclipse.persistence.platform.database.oracle.plsql.PLSQLargument;  
 import org.eclipse.persistence.queries.StoredProcedureCall;  
 import org.eclipse.persistence.sessions.DatabaseRecord;  
   
 @SuppressWarnings("rawtypes")  
 public class OracleCursorDatabaseType implements SimpleDatabaseType {  
   
      public boolean isComplexDatabaseType() {  
           return false;  
      }  
   
      public boolean isJDBCType() {  
           return false;  
      }  
   
      public int getSqlCode() {  
           return OracleTypes.CURSOR;  
      }  
   
      public int getConversionCode() {  
           return getSqlCode();  
      }  
   
      public String getTypeName() {  
           return "SYS_REFCURSOR";  
      }  
   
      public int computeInIndex(PLSQLargument inArg, int newIndex,  
                ListIterator<PLSQLargument> i) {  
           inArg.outIndex = newIndex;  
           return ++newIndex;  
      }  
   
      public int computeOutIndex(PLSQLargument outArg, int newIndex,  
                ListIterator<PLSQLargument> iterator) {  
           outArg.outIndex = newIndex;  
           return newIndex;  
      }  
   
      public void buildInDeclare(StringBuilder sb, PLSQLargument inArg) {  
           // TODO Auto-generated method stub  
           System.out.println("buildInDeclare");  
      }  
   
      public void buildOutDeclare(StringBuilder sb, PLSQLargument outArg) {  
           sb.append(" ");  
           sb.append(databaseTypeHelper.buildTarget(outArg));  
           sb.append(" ");  
           sb.append(getTypeName());  
           sb.append(";");  
           sb.append(NL);  
      }  
   
      public void buildBeginBlock(StringBuilder sb, PLSQLargument arg,  
                PLSQLStoredProcedureCall call) {  
           // TODO Auto-generated method stub  
           System.out.println("buildBeginBlock");  
   
      }  
   
      public void buildOutAssignment(StringBuilder sb, PLSQLargument outArg,  
                PLSQLStoredProcedureCall call) {  
           String target = databaseTypeHelper.buildTarget(outArg);  
           sb.append(" :");  
           sb.append(outArg.outIndex);  
           sb.append(" := ");  
           sb.append(target);  
           sb.append(";");  
           sb.append(NL);  
      }  
   
      public void translate(PLSQLargument arg, AbstractRecord translationRow,  
                AbstractRecord copyOfTranslationRow,  
                List<DatabaseField> copyOfTranslationFields,  
                List<DatabaseField> translationRowFields,  
                List translationRowValues, StoredProcedureCall call) {  
           // TODO Auto-generated method stub  
           System.out.println("translate");  
      }  
   
      public void buildOutputRow(PLSQLargument outArg, AbstractRecord outputRow,  
                DatabaseRecord newOutputRow, List<DatabaseField> outputRowFields,  
                List outputRowValues) {  
           databaseTypeHelper.buildOutputRow(outArg, outputRow, newOutputRow,  
                     outputRowFields, outputRowValues);  
      }  
   
      public void logParameter(StringBuilder sb, Integer direction,  
                PLSQLargument arg, AbstractRecord translationRow,  
                DatabasePlatform platform) {  
           // TODO Auto-generated method stub  
           System.out.println("logParameter");  
      }  
 }  

As classes utilitárias que ajudam muito no processo:

 package br.com.brainsoftware.rbr.util;  
   
 import org.eclipse.persistence.platform.database.oracle.Oracle11Platform;  
 import org.eclipse.persistence.sessions.DatabaseLogin;  
   
 public class ConfigUtil {  
   
      public static DatabaseLogin getLogin() {  
           String USERNAME = "user_name";  
           String PASSWORD = "password";  
           String URL = "jdbc:oracle:thin:@192.168.51.10:1521:XE";  
           String DRIVER = "oracle.jdbc.driver.OracleDriver";  
   
           DatabaseLogin login = new DatabaseLogin();  
           login.setUserName(USERNAME);  
           login.setPassword(PASSWORD);  
           login.setConnectionString(URL);  
           login.setDriverClassName(DRIVER);  
           login.setDatasourcePlatform(new Oracle11Platform());  
           ((DatabaseLogin) login).bindAllParameters();  
           return login;  
      }  
   
 }  
   

 package br.com.brainsoftware.rbr.util;  
   
 import java.lang.reflect.Constructor;  
 import java.lang.reflect.Field;  
 import java.lang.reflect.InvocationTargetException;  
 import java.lang.reflect.Method;  
 import java.math.BigDecimal;  
 import java.util.ArrayList;  
 import java.util.List;  
   
 import org.eclipse.persistence.sessions.DatabaseRecord;  
   
 public class ReflectionUtil {  
   
      public static Object invokeGetterMethod(Field field, Object object)  
                throws SecurityException, IllegalArgumentException,  
                NoSuchMethodException, IllegalAccessException,  
                InvocationTargetException {  
           String methodName = "get"  
                     + String.valueOf(field.getName().charAt(0)).toUpperCase()  
                     + field.getName().substring(1);  
           Class<?>[] parameterTypes = new Class[0];  
           Method method = object.getClass().getMethod(methodName, parameterTypes);  
           return method.invoke(object);  
      }  
   
      public static Object invokeSetterMethod(Field field, Object object,  
                Object value) throws SecurityException, IllegalArgumentException,  
                NoSuchMethodException, IllegalAccessException,  
                InvocationTargetException {  
           Method method = null;  
           Object result = null;  
           String methodName = "set" + field.getName();  
           for (Method m : object.getClass().getMethods()) {  
                if (methodName.equalsIgnoreCase(m.getName())) {  
                     method = m;  
                }  
           }  
           if (null != method) {  
                result = method.invoke(object, value);  
           }  
           return result;  
      }  
   
      public static <T> List<T> mapper(List<DatabaseRecord> records, Class<T> type)  
                throws SecurityException, IllegalArgumentException,  
                InstantiationException, IllegalAccessException,  
                NoSuchMethodException, InvocationTargetException {  
           List<T> result = new ArrayList<T>();  
           for (DatabaseRecord record : records) {  
                result.add(setValues(record, type));  
           }  
   
           return result;  
      }  
   
      @SuppressWarnings("unchecked")  
      private static <T> T setValues(DatabaseRecord record, Class<T> type)  
                throws InstantiationException, IllegalAccessException,  
                SecurityException, IllegalArgumentException, NoSuchMethodException,  
                InvocationTargetException {  
           Object result = type.newInstance();  
   
           for (Field field : result.getClass().getDeclaredFields()) {  
                Object value = record.get(field.getName().toUpperCase());  
                value = adjustType(field.getType(), value);  
   
                ReflectionUtil.invokeSetterMethod(field, result, value);  
           }  
   
           return (T) result;  
      }  
   
      private static Object adjustType(Class<?> type, Object value)  
                throws IllegalArgumentException, InstantiationException,  
                IllegalAccessException, InvocationTargetException {  
           Object result = value;  
   
           if (value == null) {  
                return result;  
           }  
   
           if (BigDecimal.class.equals(value.getClass())) {  
                BigDecimal new_name = (BigDecimal) value;  
                int helper = new_name.intValue();  
                for (Constructor<?> constructor : type.getDeclaredConstructors()) {  
                     Class<?>[] parameters = constructor.getParameterTypes();  
                     if (parameters.length == 1 && parameters[0].equals(int.class)) {  
                          result = constructor.newInstance(helper);  
                          break;  
                     }  
                }  
           }  
           return result;  
      }  
 }  
   

E o pojo com os dados do usuario, assim como o select do cursor você mesmo pode escrever. :D

Agora a main:

   
      public static void main(String[] args) throws Exception {  
           TipoRetorno retorno = null;  
           List<Usuario> usuarios = Collections.emptyList();  
           ListaUsuario listaUsuario = new ListaUsuario();  
           TipoEntrada entrada = new TipoEntrada();  
           entrada.setNmUsuario("USER1");  
           entrada.setIdSistema("1");  
           listaUsuario.call(entrada);  
           if (listaUsuario.isReady()) {  
                retorno = listaUsuario.getReturn();  
                usuarios = (List<Usuario>) listaUsuario.getResults();  
           }  
           // Displaying the results  
           System.out.format("Retono: Tipo:[%s] Mensagem:[%s]%n",  
                     retorno.getTipoMensagem(), retorno.getDescMensagem());  
           for (Usuario usuario : usuarios) {  
                System.out.format("Usuario [%s]%n", usuario);  
           }  
   
      }  
   

Bom, está resolvido, nem foi tão difícil. :P

Tentei resolver o problema com o que já existia no EclipseLink, sem sorte, mas, com um pouco de paciência pude entender melhor como ele funciona, implementar a interface DatabaseType e preparar essas classes que podem resolver o problema de forma mais interessante.