venerdì 19 luglio 2013

Castor data binding framework mapping XMLGregorianCalendar

Castor data binding framework mapping XMLGregorianCalendar

The goal of this article is to show how to carry out complex castor mapping and to share a knowledge that I've just gained on how to map javax.xml.datatype.XMLGregorianCalendar within castor.
Castor supports lots of system types except javax.xml.datatype.XMLGregorianCalendar.

So, how can I tackle this issue?
I've started thinking on how I can provide a mecchanism to make this javax.xml.datatype.XMLGregorianCalendar date field work in castor context mapping?

The answer was YES

In effect, this is not what I consider a proper article, but a sort of hand to those who might go through this kind of issue.
Well, to give a hand to those who might face this issue, I have built a simple eclipse project to make this solution easier to comprehend.

Components that make up this small but working solution

@XmlType

org.sample.castor.Customer
org.sample.castor.Order
org.sample.castor.OrderResponse
org.sample.castor.Orders

#Manipolatore (Handler) del campo data javax.xml.datatype.XMLGregorianCalendar
org.sample.castor.handler.OrderDateHandler

org.sample.castor.util.DateUtil
#Main class nel package di default

default/OrderManager
# Mapping file

mapping.xml

#Main class
import java.io.InputStream;
import java.io.StringReader;
import java.util.List;
import org.exolab.castor.mapping.Mapping;
import org.exolab.castor.mapping.MappingException;
import org.exolab.castor.xml.MarshalException;
import org.exolab.castor.xml.Unmarshaller;
import org.exolab.castor.xml.ValidationException;
import org.exolab.castor.xml.XMLContext;
import org.sample.castor.Order;
import org.sample.castor.OrderResponse;
import org.sample.castor.Orders;
import org.xml.sax.InputSource;

public class OrderManager {

    private static StringBuffer message;

    static{
        message = new StringBuffer();
        message.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
        message.append("<ns2:orderResponse xmlns:ns2=\"http://org.sample.castor\">\n");
        message.append("   <customer>\n");
        message.append("       <id>101</id>\n");
        message.append("       <name>Paolo</name>\n");
        message.append("       <surname>Rossi</surname>\n");
        message.append("       <address>Via della Giustiniana, 71</address>\n");
        message.append("       <email>p.rossi@gmail.com</email>\n");
        message.append("   </customer>\n");
        message.append("   <orders>\n");
        message.append("         <order>\n");
        message.append("          <id>701</id>\n");
        message.append("          <description>Order 701</description>\n");
        message.append("          <orderDate>2013-03-23T00:00:00+02:00</orderDate>\n");
        message.append("         </order>\n");     
        message.append("         <order>\n");
        message.append("          <id>702</id>\n");
        message.append("          <description>Order 702</description>\n");
        message.append("          <orderDate>2013-07-23T00:00:00+02:00</orderDate>\n");
        message.append("         </order>\n");
        message.append("   </orders>\n");
        message.append("</ns2:orderResponse>\n");

    } 

 

    public static void main(String[] args) throws MappingException, MarshalException, ValidationException {     

        InputStream istream = OrderManager.class.getResourceAsStream("mapping.xml");
        System.out.println(message.toString());
        InputSource is = new InputSource(istream);

        // Load Mapping
        Mapping mapping = new Mapping();
        mapping.loadMapping(is);

        // initialize and configure XMLContext
        XMLContext context = new XMLContext();
        context.addMapping(mapping);

        // Create a new Unmarshaller
        Unmarshaller unmarshaller = context.createUnmarshaller();
        unmarshaller.setClass(OrderResponse.class);     

        is.setCharacterStream(new StringReader(message.toString()));     
        OrderResponse orderResponse = (OrderResponse)unmarshaller.unmarshal(is); 

        System.out.println(orderResponse);
        System.out.println(orderResponse.getCustomer().getId());
        System.out.println(orderResponse.getCustomer().getName());
        System.out.println(orderResponse.getCustomer().getSurname());
        System.out.println(orderResponse.getCustomer().getAddress());
        System.out.println(orderResponse.getCustomer().getEmail());

     
        Orders orders = orderResponse.getOrders();
        for(Order order : orders.getOrders()){
            System.out.println(order.getId());
            System.out.println(order.getOrderDate());
            System.out.println(order.getDescription());
        }
    }
}

#Simple dto/xsd

package org.sample.castor;
import java.io.Serializable;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlType;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "customer", namespace="http://org.sample.castor", propOrder = {
    "id",
    "name",
    "surname",
    "address",
    "email"
})
public class Customer implements Serializable{

    private long id;
    private String name;
    private String surname;
    private String address;
    private String email;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSurname() {
        return surname;
    }

    public void setSurname(String surname) {
        this.surname = surname;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

}

#Simple dto/xsd

package org.sample.castor;

import java.io.Serializable;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlType;
import javax.xml.datatype.XMLGregorianCalendar;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "order", namespace="http://org.sample.castor", propOrder = {
    "id",
    "orderDate",
    "description"
})

public class Order implements Serializable{

    private long id;
    private XMLGregorianCalendar orderDate;
    private String description;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public XMLGregorianCalendar getOrderDate() {
        return orderDate;
    }

    public void setOrderDate(XMLGregorianCalendar orderDate) {
       this.orderDate = orderDate;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

}

#Simple dto/xsd

package org.sample.castor;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;

@XmlAccessorType(XmlAccessType.FIELD)

@XmlType(name = "orderResponse", namespace="http://org.sample.castor", propOrder = {
    "customer", 
    "orders"
})

public class OrderResponse implements Serializable{

    private Customer customer;
    private Orders orders;

    public Orders getOrders() {
        return orders;
    }

    public void setOrders(Orders orders) {
        this.orders = orders;
    }

    public Customer getCustomer() {
        return customer;
    }

    public void setCustomer(Customer customer) {
        this.customer = customer;
    }
}

#Simple dto/xsd

package org.sample.castor;

import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlType;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "orders", namespace="http://org.sample.castor", propOrder = {
    "orders"
})

public class Orders {

    private List<Order> orders; 

    public List<Order> getOrders() {
        if (orders == null) {
            orders = new ArrayList<Order>();
        }
        return this.orders;
    } 
}

#Handler

package org.sample.castor.handler;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.xml.datatype.DatatypeConfigurationException;
import org.exolab.castor.mapping.FieldHandler;
import org.exolab.castor.mapping.ValidityException;
import org.sample.castor.Order;
import org.sample.castor.util.DateUtil;

public class OrderDateHandler implements FieldHandler{

    private static final String FORMAT = "yyyy-MM-dd";

    /**
     * Creates a new OrderDateHandler instance
     */

    public OrderDateHandler() {
        super();
    }

    public Object getValue(final Object object) throws IllegalStateException {

        Order root = (Order)object;
        Date value = new Date(); //root.getDate();

        if (value == null) return null;

        SimpleDateFormat formatter = new SimpleDateFormat(FORMAT);
        Date date = (Date)value;
        return formatter.format(date);
    }

    public void setValue(Object object, Object value)

       throws IllegalStateException, IllegalArgumentException {
    
        Order root = (Order)object;
        SimpleDateFormat formatter = new SimpleDateFormat(FORMAT);
        Date date = null;

        try {
            date = formatter.parse((String)value);
        }

        catch(ParseException px) {
            throw new IllegalArgumentException(px.getMessage());
        }

        try {
            root.setOrderDate(DateUtil.toXMLGregorianCalendar(date));
        } catch (DatatypeConfigurationException e) {
       e.printStackTrace();  
    }
    }

    public Object newInstance(Object parent) throws IllegalStateException {

        //-- Since it's marked as a string...just return null,
        //-- it's not needed.
        return null;
    }



    public void resetValue(Object object) throws IllegalStateException, IllegalArgumentException {
        ((Order)object).setOrderDate(null);
    }

    @Override
    public void checkValidity(Object arg0) throws ValidityException,
            IllegalStateException {
        // TODO Auto-generated method stub
    }

}

#utility class

package org.sample.castor.util;

import java.util.Date;
import java.util.GregorianCalendar;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;

public class DateUtil {

        public static XMLGregorianCalendar toXMLGregorianCalendar(Date date) throws DatatypeConfigurationException {
            GregorianCalendar c = new GregorianCalendar();
            c.setTime(date);
            XMLGregorianCalendar xmlGreCal = DatatypeFactory.newInstance().newXMLGregorianCalendar(c); 
            return xmlGreCal;
        }

        public static XMLGregorianCalendar toXMLGregorianCalendar(String date)
            throws DatatypeConfigurationException {        

            XMLGregorianCalendar xmlGCal = null;
            try {
                   xmlGCal = DatatypeFactory.newInstance().newXMLGregorianCalendar(date);
            } catch (DatatypeConfigurationException ex) {
                ex.printStackTrace();
            }

            return xmlGCal;
        }
}

#castor file mapping.xml

<?xml version="1.0"?>

<mapping>

  <class name="org.sample.castor.OrderResponse" type="org.sample.castor.OrderResponse">
     <map-to xml="orderResponse"/>
     <field name="customer"
           type="org.sample.castor.Customer">
          <bind-xml name="customer" node="element"/>
     </field>
     <field name="orders"
           type="org.sample.castor.Orders">
          <bind-xml name="orders" node="element"/>
     </field>
  </class>

  <class name="org.sample.castor.Orders" type="org.sample.castor.Orders">
     <map-to xml="orders"/>
     <field name="orders"
           type="org.sample.castor.Order" collection="arraylist">
          <bind-xml name="orders" node="element"/>
     </field>  
  </class>

  <class name="org.sample.castor.Order" type="org.sample.castor.Order">
     <map-to xml="order"/>
     <field name="id"
           type="java.lang.Long">
          <bind-xml name="id" node="element"/>
     </field>
     <field name="orderDate"
           type="java.lang.String" handler="org.sample.castor.handler.OrderDateHandler">
          <bind-xml name="orderDate" node="element"/>
     </field>
     <field name="description"
           type="java.lang.String">
          <bind-xml name="description" node="element"/>
     </field>
  </class>

  <class name="org.sample.castor.Customer" type="org.sample.castor.Customer">
     <map-to xml="customer"/>
     <field name="id"
           type="java.lang.Long">
          <bind-xml name="id" node="element"/>
     </field>
     <field name="name"
           type="java.lang.String">
          <bind-xml name="name" node="element"/>
     </field>    
     <field name="surname"
           type="java.lang.String">
          <bind-xml name="surname" node="element"/>
     </field>
     <field name="address"
           type="java.lang.String">
          <bind-xml name="address" node="element"/>
     </field>
     <field name="email"
           type="java.lang.String">
          <bind-xml name="email" node="element"/>
     </field>
  </class> 


</mapping>

libraries:

castor-1.3.1-xml.jar
castor-core-1.3.2.jar

In case the blogging system might not support attachments, you could email me at doviche@gmail.com and I will reply with a rar or zip file, containing both jar files.


Thanks for reading



Douglas Oviche

lunedì 5 marzo 2012

Un mix tecnologico tra JAVA-J2EE/JPA Hibernate e Spring

L'articolo che state per consultare è uno studio di specifiche e framework basati su tecnologia Java.

Lo scopo di questa ricerca è stato realizzare un applicativo distribuito utilizzando le cutting edge technologies o tecnologie all'avanguardia.
Quando si parla di tecnologie all'avanguardia nell'ambiente java, il pensiero ci porta immediatamente alle specifiche J2EE, ma soprattutto alle specifiche principali che ne fanno parte come EJB e JPA. Oltre a queste specifiche, ci sono delle piattaforme e framework che implementano meccanismi di iniezione DI e IOC con il supporto di sistemi e provider di persistenza che inizialmente possono risultare difficili da configurare per le nostre particolari esigenze, ma, nel corso dello sviluppo di un progetto di complessità media o alta, possono portare dei vantaggi significativi accelerando il rilascio dei diversi moduli architetturali che costituiscono il sistema.

Personalmente mi sono sempre domandato come abbinare i framework spring, hibernate, jpa e gli enterprise java beans, affinché potessi ottenere il massimo da ognuna di queste tecnologie all'avanguardia.
Ad esempio, mi sono chiesto come potevo fare per riuscire a fornire una totale configurazione dei miei componenti di servizio, persistenza (ecc...) tramite il framework spring e soprascrivere il meccanismo di iniezione svolto dagli intercettori forniti nella specifica EJB, permettendo a quelli di spring di subentrare in questo processo.

Un'osservazione importante che riporto in questo articolo sono gli aspetti relativi alla normalizzazione delle banca dati: spesso ci imbattiamo nella progettazione di entità di persistenza in JPA, senza mai prendere in considerazione la normalizzazione presente nel sistema relazionale. Non bisogna mai dimenticare che tale normalizzazione deve essere estesa nel contesto applicativo, altrimenti commettiamo un gravissimo errore di progettazione.

Dunque, l'intenzione dell'articolo è spiegare come avviene la progettazione di questo mix tecnologico tramite un pratico esempio .
Attualmente la soluzione è stata da me collaudata su glassfish, ma nulla impedisce la possibilità di schierare l'applicazione su JBoss od altri AS con supporto EJB container.

Pre requisiti:
Glassfish 3v
Jdk 1.6 o +
Hibernate 3 o +
Springframework qualsiasi versione
Specifiche J2EE (EJB 3.0, JPA 1.0/2.0)



Struttura del progetto:



Intefaccia DAO

package com.soluzionijava.sample.dao;

public interface ClienteDAO {
public Cliente findCliente(BigInteger idCliente);
public List<Pagamento> findPagamentiByIdCliente(BigInteger idCliente);
}
L'implementazione dell'interfaccia ClienteDAO

package com.soluzionijava.sample.dao;

public class ClienteDAOImpl implements ClienteDAO {
@PersistenceContext
private EntityManager em;
@Override
public Cliente findCliente(BigInteger idCliente) {
Cliente cliente = null;
String namedQuery = "findClienteById";
try {
Query query = em.createNamedQuery(namedQuery);
query.setParameter(1, idCliente);
cliente = (Cliente)query.getSingleResult();
} catch (NoResultException e) {
cliente = null;
}
return cliente;
}
…...........
}

Ci sono degli elementi che spiccano all'interno di questa implementazione, uno di questi è l'utilizzo dell'annotazione @PersistenceContext: tale annotazione mette a disposizione nella nostra implementazione DAO l'elemento JPA EntityManager. Tramite l'EntityManager vengono effettuate tutte le operazioni d'accesso al dato come ad esempio:

javax.persistence.Query query = em.createNamedQuery(namedQuery);

Viene trovato il Cliente per id tramite JPA EntityManger che a sua volta attraverso il metodo createNamedQuery ci permette di effettuare l'esecuzione di EJB QL.
In questo particolare esempio viene utilizzato quello per le invocazioni delle query nominate ossia namedQuery fornite nell'entità coinvolta nell'operazione, ma, questo prototipo applicativo, non si sofferma sui particolari di JPA, ma bensì sulla progettazione di quest'insieme di tecnologie.

Osserviamo un po' più da vincino l'entità Cliente, entità principale nel nostro articolo:

package com.soluzionijava.sample.entity;

@Entity(name="Cliente")
@Table(name="clienti")
@NamedQuery(name="findClienteById",
query="select o from Cliente o where o.id = ?")
public class Cliente implements java.io.Serializable {

@Id
private BigInteger id;

@Column(name="COGNOME", nullable=false)
private String cognome;

@Column(name="NOME", nullable=false)
private String nome;

@OneToMany(fetch=FetchType.EAGER, mappedBy="cliente")
private Set<RClientiPagamenti> clientiPagamenti;

[…]
}


Il frammento di codice di sopra riportato ci mostra l'entità Cliente con i suoi diversi campi(Field), i setters e getters sono stati omessi, ma devono essere obbligatoriamente presenti.
  • Prima osservazione: @Entity(name="Cliente"). Questa dichiarazione annotata determina che ovunque verrà utilizzata questa entità dovrà essere chiamata con questo nome.
  • Seconda osservazione: @Table(name="clienti"). Correla l'entità ad una tabella relazionale.
  • Terza osservazione: @NamedQuery(name="findClienteById",
    query="select o from Cliente o where o.id = ?"). Definisce ed associa all'entità una NAMED_QUERY.
  • Quarta osservazione: ogni singolo campo viene associato ad un campo di una tabella relazionale.

File di configurazione ORM:

<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings version="2.0" xmlns="http://java.sun.com/xml/ns/persistence/orm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_2_0.xsd">
<persistence-unit-metadata>
<persistence-unit-defaults>
<access>FIELD</access>
</persistence-unit-defaults>
</persistence-unit-metadata>
</entity-mappings>


Andando avanti nel nostro mix tecnologico, arriviamo finalmente al servizio e cioè l'enterprise java bean che viene iniettato da spring con l'implementazione del nostro DAO.

package com.soluzionijava.sample.service;

@Stateless(mappedName="ClienteService", name="ClienteService")
@Interceptors(SpringBeanAutowiringInterceptor.class)
public class ClienteService implements ClienteServiceRemote, ClienteServiceLocal {
@Autowired
private ClienteDAO clienteDAO;

[…]
@Override
public Cliente findCliente(BigInteger idCliente) {
return clienteDAO.findCliente(idCliente);
}

@Override
public List<Pagamento> findPagamentiByIdCliente(BigInteger idCliente){
return clienteDAO.findPagamentiByIdCliente(idCliente);
}

[…]
}

In questa classe ci sono due elementi di fondamentale importanza, ma discutiamoli uno alla volta affinché tale importanza possa essere capita esaustivamente.
In questo componente avviene la vera manifestazione di mischia tecnologica promessa in questo articolo: se guardiamo con attenzione possiamo osservare che gli intercettori vengono sovrascritti con quelli del framework spring @Interceptors(SpringBeanAutowiringInterceptor.class).

Una volta che spring è subentrato nel meccanismo di iniezione, lungo il corpo della classe possono essere osservate le sue manifestazioni attraverso l'utilizzo dell'annotazione @Autowired.

Per completare è rendere finalmente possibile questo mix, bisogna però fornire le configurazioni contestuali che mettono a disposizione l'intero meccanismo di intercezione ed iniezione(DI/IOC).
Una volta che i nostri componenti java sono pronti per l'uso, bisogna portare nel contesto applicativo una serie di file.
Di seguito vi elenco i file mancanti che chiudono il puzzle:

Il file beanRefContext.xml

<beans>
<bean class="org.springframework.context.support.ClassPathXmlApplicationContext">
<constructor-arg value="classpath*:soluzionijava-enterprise.xml" />
</bean>
</beans>

Questo file definisce le factory nel contesto business, in effetti viene usato dagli ejb per usufruire dei beans e pojo definiti nel contesto business.

Il file soluzionijava-enterprise.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans>
<import resource="classpath*:soluzionijava-dao.xml"/>
<context:annotation-config/>
<tx:annotation-driven />
</beans>

Il file soluzionijava-dao.xml

<beans>
<bean id="clienteDAO" class="com.soluzionijava.sample.dao.ClienteDAOImpl" autowire="byName"/>
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
<property name="persistenceUnitName" value="soluzioniJava"/>
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<context:component-scan base-package="com.soluzionijava.sample.entity" />
<tx:annotation-driven />
</beans>





Per l'ultimo il file di persistenza:
persistence.xml
<persistence>
<persistence-unit name="soluzioniJava"
transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<class>com.soluzionijava.sample.entity.Cliente</class>
<class>com.soluzionijava.sample.entity.RClientiPagamenti</class>
<class>com.soluzionijava.sample.entity.Pagamento</class>
<class>com.soluzionijava.sample.entity.RClientiPagamentiPK</class>
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect" />
<property name="hibernate.connection.url" value="jdbc:mysql://localhost:3306/clienti_db"/>
<property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/>
<property name="hibernate.connection.username" value="root"/>
<property name="hibernate.connection.password" value="password"/>
</properties>
</persistence-unit>
</persistence>

Promemoria:
Ricordate di includere nelle intestazioni dei file di configurazione i diversi schemi che occorrono per portare nel contesto applicativo le capacità dichiarate all'interno dei file.

Prima di salutarvi volevo ricordarvi che questo mix di tecnologie è stato da me collaudato e ed attualmente esistono dei progetti nei quali è stato già applicato.

Buon lavoro e al prossimo articolo

Douglas Oviche