Wednesday, May 30, 2012

RigaJUG demo - REST - SDD

On the model
You want to extract some info via sql such as

select k.key_name, t.translation, t.date_finalization, l.code, l.description, tl.first_name, tl.last_name, tl.email 
from translation t, language l, user tl, translation_key k
where
t.language_id = l.idlanguage
and tl.idUser = t.translator_id
and k.id = t.key_id
order by key_name

And you want to parametarize the extraction by passing some filtering input
select k.key_name, t.translation, t.date_finalization, l.code, l.description, tl.first_name, tl.last_name, tl.email 
from translation t, language l, user tl, translation_key k
where
t.language_id = l.idlanguage
and tl.idUser = t.translator_id
and k.id = t.key_id
and k.key_name like ?
and l.code like ?
order by key_name
limit ?

And you want to have a REST access!
And you want it NOW!

Good news SDD Statement Driven Development is there for you!

From the above statement you can extract 3 info:
  • an input (in java a DTO bean) with keyName, code, limit as params
  • an output  (in java a DTO bean) with keyName, translation, dateFinalization, code, description, firstName, lastName, email
  • a functionality to name
This is enough to get a REST-CXF application instantly!
This page will show you how to reach it!

Intro

This page correspond to a demo to belonging to a more global presentation.
To be able to demostrate it there are couple of prerequisits:
  • download minuteproject 0.8.1+
  • install maven
  • install tomcat
  • install mysql and create the schema (see demo JPA2)
This page will show you how to:
  • configure
  • generate
  • build
  • deploy
  • test

Configuration

Minuteproject works with a configuration file that indicates:

  • where is the model
  • how to enrich it
    • enable Statement Driven Development declartion
  • against which technologies to generate (targets)

Here is the configuration TRANXY-JPA2-Spring-REST-CXF-SDD.xml
<!DOCTYPE root>
<generator-config xmlns="http://minuteproject.sf.net/xsd/mp-config" 
xmlns:xs="http://www.w3.org/2001/XMLSchema-instance" 
xs:noNamespaceSchemaLocation="../config/mp-config.xsd">
 <configuration>
  <conventions>
   <target-convention type="enable-updatable-code-feature" />
  </conventions>
  <model name="tranxy" version="1.0" package-root="net.sf.mp.demo">
   <data-model>
    <driver name="mysql" version="5.1.16" groupId="mysql"
     artifactId="mysql-connector-java"></driver>
    <dataSource>
     <driverClassName>org.gjt.mm.mysql.Driver</driverClassName>
     <url>jdbc:mysql://127.0.0.1:3306/tranxy</url>
     <username>root</username>
     <password>mysql</password>
    </dataSource>
    <primaryKeyPolicy oneGlobal="false" >
     <primaryKeyPolicyPattern name="autoincrementPattern"></primaryKeyPolicyPattern>
    </primaryKeyPolicy>
   </data-model>
   <business-model>
    <generation-condition>
     <condition type="exclude" startsWith="QUARTZ"></condition>
    </generation-condition>
    <business-package default="tranxy">
     <condition type="package" startsWith="trans" result="translation"></condition>
    </business-package>
    <enrichment>
     <conventions>
      <!-- manipulate the structure and entities BEFORE manipulating the 
       entities -->
      <column-naming-convention type="apply-strip-column-name-suffix"
       pattern-to-strip="ID" />
      <reference-naming-convention
       type="apply-referenced-alias-when-no-ambiguity" is-to-plurialize="true" />
     </conventions>
          <entity name="language_x_translator">
              <field name="language_id" linkReferenceAlias="translating_language" />
              <field name="user_id" linkReferenceAlias="translator" />
          </entity>
          <entity name="LANGUAGE_X_SPEAKER">
              <field name="LANGUAGE_ID" linkToTargetEntity="LANGUAGE"
                  linkToTargetField="IDLANGUAGE" linkReferenceAlias="spoken_language" />
              <field name="user_id" linkReferenceAlias="speaker" />
          </entity>
          <entity name="APPLICATION">
              <field name="TYPE">
                  <property tag="checkconstraint" alias="application_type">
                      <property name="OPENSOURCE"/>
                      <property name="COPYRIGHT" />
                  </property>
              </field>
          </entity>
    </enrichment>
   </business-model>
      <statement-model>
         <queries>
             <query name="get translation info">
                 <query-body><value>
<![CDATA[select k.key_name, t.translation, t.date_finalization, l.code, l.description, tl.first_name, tl.last_name, tl.email from translation t, language l, user tl, translation_key k where t.language_id = l.idlanguage and tl.idUser = t.translator_id and k.id = t.key_id and k.key_name like ? and l.code like ? order by key_name limit ?]]>
                    </value></query-body>
                 <query-params>
                     <query-param name="key" is-mandatory="false" type="STRING" sample="'test'"></query-param>
                     <query-param name="code" is-mandatory="false" type="STRING" sample="'FR'"></query-param>
                     <query-param name="max" is-mandatory="false" type="INT" sample="10"></query-param>
                 </query-params>
             </query>
          </queries>
      </statement-model>
  </model>
  <targets>
            
      <target refname="REST-CXF-BSLA" 
         name="default" 
         fileName="mp-template-config-REST-CXF-Spring.xml" 
         outputdir-root="../../DEV/latvianjug/tranxy/rest"
         templatedir-root="../../template/framework/cxf">
      </target>

      <target refname="BackendOnBsla" 
         name="default" 
         fileName="mp-template-config-JPA2-bsla.xml" 
         outputdir-root="../../DEV/latvianjug/tranxy/bsla"
         templatedir-root="../../template/framework/bsla">
          <property name="add-cache-implementation" value="ehcache"></property>
      </target> 
      
   <target refname="JPA2" fileName="mp-template-config-JPA2.xml"
    outputdir-root="../../DEV/latvianjug/tranxy/jpa" 
        templatedir-root="../../template/framework/jpa">
    <property name="add-querydsl" value="2.1.2"></property>
    <property name="add-jpa2-implementation" value="hibernate"></property>
        <property name="add-cache-implementation" value="ehcache"></property>
        <property name="add-domain-specific-method" value="true"></property>
        <property name="add-xmlbinding" value="true"></property> 
        <property name="add-xml-format" value="lowercase-hyphen"></property> 
   </target>
   
      <target refname="SDD-beans" 
          outputdir-root="../../DEV/latvianjug/tranxy/jpa"
         fileName="mp-template-config-SDD-beans.xml" 
         templatedir-root="../../template/framework/bean">
         <property name="add-xmlbinding" value="true"></property> 
         <property name="add-xml-format" value="lowercase-hyphen"></property>
      </target>

      <target refname="COMMON-LIB" 
         fileName="mp-template-config-COMMON-LIB.xml" 
         templatedir-root="../../template/framework/common">
      </target>   
                        
      <target refname="MavenMaster" 
         name="maven" 
         fileName="mp-template-config-maven.xml" 
         outputdir-root="../../DEV/latvianjug/tranxy"
         templatedir-root="../../template/framework/maven">
      </target>

      <target refname="CACHE-LIB" fileName="mp-template-config-CACHE-LIB.xml"
          templatedir-root="../../template/framework/cache">
      </target>
      
   <target refname="LIB" fileName="mp-template-config-bsla-LIB-features.xml"
    templatedir-root="../../template/framework/bsla">
   </target>

      <target refname="REST-LIB" 
         fileName="mp-template-config-REST-LIB.xml" 
         templatedir-root="../../template/framework/rest">
      </target>
      <target refname="SPRING-LIB" 
         fileName="mp-template-config-SPRING-LIB.xml" 
         templatedir-root="../../template/framework/spring">
      </target>

  </targets>
 </configuration>
</generator-config>
Aside of the 'standard' CXF CRUD generation (see on demo). Here a set of new artifacts are generated to enable the enriched 'get translation infos' function.
The part of the configuration may seem a bit long, I agree. It describes what templates and metadata file to use.
I plan to have simplify version à la minuteproject console where you just reference a track name belonging to a catalog and pass some properties.
The set of target currently displayed can be copied from the sample provided in /demo/config.

Generation

Drop TRANXY-JPA2-Spring-REST-CXF-SDD.xml in /mywork/config and run: model-generation.cmd/sh TRANXY-JPA2-Spring-REST-CXF-SDD.xml
Generated artifacts go to /DEV/latvianjug/tranxy.

Generated artifacts

Here are presented on the new artifacts of the CXF-JPA2 track.
The artifacts are presented from a top down perspective (starting from REST-Frontend down to Persistence layer).
The name of the artifacts are deeply linked to the query name 'get translation info'.

REST resources

2 resources file are generated, one for json rendering the other for xml.

GetTranslationInfoJsonResource.java

/**
 * Copyright (c) minuteproject, minuteproject@gmail.com
 * All rights reserved.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * More information on minuteproject:
 * twitter @minuteproject
 * wiki http://minuteproject.wikispaces.com 
 * blog http://minuteproject.blogspot.net
 * 
*/
/**
 * template reference : 
 * - name : CXFSpringSDDJsonResource
 * - file name : CXFSpringSDDResource.vm
*/
package net.sf.mp.demo.tranxy.rest.statement;

//MP-MANAGED-ADDED-AREA-BEGINNING @import@
//MP-MANAGED-ADDED-AREA-ENDING @import@
import java.util.Date;
import java.util.List;
import java.util.ArrayList;
import java.io.*;
import java.sql.*;

import javax.servlet.http.*;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.FormParam;
import javax.ws.rs.QueryParam;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import javax.xml.bind.JAXBElement;

import net.sf.mp.demo.tranxy.sdd.out.statement.GetTranslationInfoOutList;
import net.sf.mp.demo.tranxy.sdd.in.statement.GetTranslationInfoIn;
import net.sf.mp.demo.tranxy.dao.sdd.face.statement.GetTranslationInfoDaoFace;
/**
 *
 * <p>Title: GetTranslationInfoJsonResource</p>
 *
 * <p>Description: remote interface for GetTranslationInfoJsonResource service </p>
 *
 */
@Produces ({MediaType.APPLICATION_JSON})
@Consumes ({MediaType.APPLICATION_JSON})
@Service ("getTranslationInfoJsonResource")
@Transactional
@Path ("/rest/json/gettranslationinfos")
public class GetTranslationInfoJsonResource {

    @Autowired
    @Qualifier("getTranslationInfoDaoFace")
    GetTranslationInfoDaoFace getTranslationInfoDaoFace;

//MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @SDD_EXECUTE_GET-get translation info@
    @GET
    @Produces (MediaType.APPLICATION_JSON) 

    public GetTranslationInfoOutList executeAndFormatXml (
        @QueryParam ("key") String key ,
        @QueryParam ("code") String code ,
        @QueryParam ("max") Integer max ) {
        return execute(
           key ,
           code ,
           max   
  );
    }
//MP-MANAGED-UPDATABLE-ENDING

//MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @SDD_EXECUTE_GET-get translation info@
    public GetTranslationInfoOutList execute (
        String key ,
        String code ,
        Integer max ) {
  GetTranslationInfoIn getTranslationInfoIn = new GetTranslationInfoIn ();
  getTranslationInfoIn.setKey (key);
  getTranslationInfoIn.setCode (code);
  getTranslationInfoIn.setMax (max);
        return getTranslationInfoDaoFace.execute(getTranslationInfoIn);
    }
//MP-MANAGED-UPDATABLE-ENDING

//MP-MANAGED-ADDED-AREA-BEGINNING @implementation@
//MP-MANAGED-ADDED-AREA-ENDING @implementation@

}

GetTranslationInfoXmlResource.java

/**
 * Copyright (c) minuteproject, minuteproject@gmail.com
 * All rights reserved.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * More information on minuteproject:
 * twitter @minuteproject
 * wiki http://minuteproject.wikispaces.com 
 * blog http://minuteproject.blogspot.net
 * 
*/
/**
 * template reference : 
 * - name : CXFSpringSDDXmlResource
 * - file name : CXFSpringSDDResource.vm
*/
package net.sf.mp.demo.tranxy.rest.statement;

//MP-MANAGED-ADDED-AREA-BEGINNING @import@
//MP-MANAGED-ADDED-AREA-ENDING @import@
import java.util.Date;
import java.util.List;
import java.util.ArrayList;
import java.io.*;
import java.sql.*;

import javax.servlet.http.*;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.FormParam;
import javax.ws.rs.QueryParam;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import javax.xml.bind.JAXBElement;

import net.sf.mp.demo.tranxy.sdd.out.statement.GetTranslationInfoOutList;
import net.sf.mp.demo.tranxy.sdd.in.statement.GetTranslationInfoIn;
import net.sf.mp.demo.tranxy.dao.sdd.face.statement.GetTranslationInfoDaoFace;
/**
 *
 * <p>Title: GetTranslationInfoXmlResource</p>
 *
 * <p>Description: remote interface for GetTranslationInfoXmlResource service </p>
 *
 */
@Produces ({MediaType.APPLICATION_XML})
@Consumes ({MediaType.APPLICATION_XML})
@Service ("getTranslationInfoXmlResource")
@Transactional
@Path ("/rest/xml/gettranslationinfos")
public class GetTranslationInfoXmlResource {

    @Autowired
    @Qualifier("getTranslationInfoDaoFace")
    GetTranslationInfoDaoFace getTranslationInfoDaoFace;

//MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @SDD_EXECUTE_GET-get translation info@
    @GET
    @Produces (MediaType.APPLICATION_XML) 

    public GetTranslationInfoOutList executeAndFormatXml (
        @QueryParam ("key") String key ,
        @QueryParam ("code") String code ,
        @QueryParam ("max") Integer max ) {
        return execute(
           key ,
           code ,
           max   
  );
    }
//MP-MANAGED-UPDATABLE-ENDING

//MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @SDD_EXECUTE_GET-get translation info@
    public GetTranslationInfoOutList execute (
        String key ,
        String code ,
        Integer max ) {
  GetTranslationInfoIn getTranslationInfoIn = new GetTranslationInfoIn ();
  getTranslationInfoIn.setKey (key);
  getTranslationInfoIn.setCode (code);
  getTranslationInfoIn.setMax (max);
        return getTranslationInfoDaoFace.execute(getTranslationInfoIn);
    }
//MP-MANAGED-UPDATABLE-ENDING

//MP-MANAGED-ADDED-AREA-BEGINNING @implementation@
//MP-MANAGED-ADDED-AREA-ENDING @implementation@

}

Spring configuration

application-context.xml

It is adapted to reflect the new beans
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
 xmlns:context="http://www.springframework.org/schema/context"
 xmlns:tx="http://www.springframework.org/schema/tx"
 xmlns:jaxrs="http://cxf.apache.org/jaxrs"
 xsi:schemaLocation="
  http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  http://www.springframework.org/schema/context 
  http://www.springframework.org/schema/context/spring-context-3.0.xsd
  http://www.springframework.org/schema/tx
  http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
  http://cxf.apache.org/jaxrs
  http://cxf.apache.org/schemas/jaxrs.xsd">

    <import resource="classpath:META-INF/cxf/cxf.xml" />
    <import resource="classpath:META-INF/cxf/cxf-extension-jaxrs-binding.xml" />
    <import resource="classpath:META-INF/cxf/cxf-servlet.xml" />
    
    <context:component-scan base-package="net.sf.mp.demo.tranxy.rest"/>
    <context:component-scan base-package="net.sf.mp.demo.tranxy.dao.sdd.impl"/>

    <import resource="classpath:net/sf/mp/demo/tranxy/factory/spring/spring-config-Tranxy-BE-main.xml"/>    
 
    <jaxrs:server id="restContainer" address="/">
        <jaxrs:serviceBeans>
   <!-- tranxy --> 
   <ref bean="applicationResource"/>
   <ref bean="languageResource"/>
   <ref bean="userResource"/>
   <!-- translation --> 
   <ref bean="translationResource"/>
   <ref bean="translationKeyResource"/>
   <ref bean="translationRequestResource"/>
 
   <!-- statements -->
   <ref bean="getTranslationInfoXmlResource"/>
   <ref bean="getTranslationInfoJsonResource"/>
        </jaxrs:serviceBeans>
    </jaxrs:server> 

</beans> 

DAO layer

Interface GetTranslationInfoDaoFace.java

/**
 * Copyright (c) minuteproject, minuteproject@gmail.com
 * All rights reserved.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * More information on minuteproject:
 * twitter @minuteproject
 * wiki http://minuteproject.wikispaces.com 
 * blog http://minuteproject.blogspot.net
 * 
*/
/**
 * template reference : 
 * - name : SDDDaoInterface
 * - file name : SDDDaoInterface.vm
*/
package net.sf.mp.demo.tranxy.dao.sdd.face.statement;

//MP-MANAGED-ADDED-AREA-BEGINNING @import@
//MP-MANAGED-ADDED-AREA-ENDING @import@

import net.sf.mp.demo.tranxy.sdd.out.statement.GetTranslationInfoOutList;
import net.sf.mp.demo.tranxy.sdd.in.statement.GetTranslationInfoIn;

/**
 *
 * <p>Title: GetTranslationInfoDaoFace</p>
 *
 * <p>Description: remote interface for GetTranslationInfoDaoFace service </p>
 *
 */
public interface GetTranslationInfoDaoFace {

//MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @SDD_EXECUTE_GET-get translation info@
    public GetTranslationInfoOutList execute (GetTranslationInfoIn getTranslationInfoIn) ;
//MP-MANAGED-UPDATABLE-ENDING

//MP-MANAGED-ADDED-AREA-BEGINNING @implementation@
//MP-MANAGED-ADDED-AREA-ENDING @implementation@


} 

Implementation GetTranslationInfoRepository.java

/**
 * Copyright (c) minuteproject, minuteproject@gmail.com
 * All rights reserved.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * More information on minuteproject:
 * twitter @minuteproject
 * wiki http://minuteproject.wikispaces.com 
 * blog http://minuteproject.blogspot.net
 * 
*/
/**
 * template reference : 
 * - name : SDDSpringJPADao
 * - file name : SDDSpringJPADao.vm
*/
package net.sf.mp.demo.tranxy.dao.sdd.impl.statement;

//MP-MANAGED-ADDED-AREA-BEGINNING @import@
//MP-MANAGED-ADDED-AREA-ENDING @import@

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import org.hibernate.HibernateException;
import org.hibernate.Session;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import net.sf.mp.demo.tranxy.sdd.out.statement.GetTranslationInfoOutList;
import net.sf.mp.demo.tranxy.sdd.out.statement.GetTranslationInfoOut;
import net.sf.mp.demo.tranxy.sdd.in.statement.GetTranslationInfoIn;
import net.sf.mp.demo.tranxy.dao.sdd.face.statement.GetTranslationInfoDaoFace;

/**
 *
 * <p>Title: GetTranslationInfoRepository</p>
 *
 * <p>Description: SDD DAO Spring JPA implementation </p>
 *
 */
@Repository ("getTranslationInfoDaoFace")
@Transactional(propagation = Propagation.REQUIRED) 
public class GetTranslationInfoRepository implements GetTranslationInfoDaoFace{

 public static final String QUERY_NATIVE = "select k.key_name, t.translation, t.date_finalization, l.code, l.description, tl.first_name, tl.last_name, tl.email from translation t, language l, user tl, translation_key k where t.language_id = l.idlanguage and tl.idUser = t.translator_id and k.id = t.key_id and k.key_name like ? and l.code like ? order by key_name limit ?";

 @PersistenceContext(unitName = "tranxy")  
    EntityManager entityManager;  
//MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @SDD_EXECUTE_GET-get translation info@
    public GetTranslationInfoOutList execute (GetTranslationInfoIn getTranslationInfoIn) {
  GetTranslationInfoOutList getTranslationInfoOutList = new GetTranslationInfoOutList();
  List<GetTranslationInfoOut> list = executeJDBC (getTranslationInfoIn);
  getTranslationInfoOutList.setGetTranslationInfoOuts (list);
        return getTranslationInfoOutList;
    }
//MP-MANAGED-UPDATABLE-ENDING

//MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @SDD_EXECUTE_JDBC-get translation info@
 public List<GetTranslationInfoOut> executeJDBC(GetTranslationInfoIn getTranslationInfoIn) {
  List<GetTranslationInfoOut> list = new ArrayList<GetTranslationInfoOut>();
  PreparedStatement pstmt = null;
  ResultSet rs = null;
  Connection conn = null;
  try {
   conn = getConnection();
   pstmt = conn.prepareStatement(QUERY_NATIVE);
   pstmt.setString(1, getTranslationInfoIn.getKey()); 
   pstmt.setString(2, getTranslationInfoIn.getCode()); 
   pstmt.setInt(3, getTranslationInfoIn.getMax()); 
   rs = pstmt.executeQuery();
   while (rs.next()) {
    GetTranslationInfoOut getTranslationInfoOut = new GetTranslationInfoOut();
    getTranslationInfoOut.setKeyName(rs.getString(1)); 
    getTranslationInfoOut.setTranslation(rs.getString(2)); 
    getTranslationInfoOut.setDateFinalization(rs.getString(3)); 
    getTranslationInfoOut.setCode(rs.getString(4)); 
    getTranslationInfoOut.setDescription(rs.getString(5)); 
    getTranslationInfoOut.setFirstName(rs.getString(6)); 
    getTranslationInfoOut.setLastName(rs.getString(7)); 
    getTranslationInfoOut.setEmail(rs.getString(8)); 
    list.add(getTranslationInfoOut);
         }
  } catch (Exception e) {
        e.printStackTrace();
     } finally {
       try {
         rs.close();
         pstmt.close();
         conn.close();
       } catch (Exception e) {
         e.printStackTrace();
       }
     }
  return list;
 }
//MP-MANAGED-UPDATABLE-ENDING

//if JPA2 implementation is hibernate
 @SuppressWarnings("deprecation")   
    public Connection getConnection() throws HibernateException {  
  Session session = getSession();  
  Connection connection = session.connection();  
  return connection;  
    } 
 
    private Session getSession() {  
     Session session = (Session) entityManager.getDelegate();  
     return session;  
    }
 
//MP-MANAGED-ADDED-AREA-BEGINNING @implementation@
//MP-MANAGED-ADDED-AREA-ENDING @implementation@

}

Beans!!

Welcome back DTO! You do not need to re-wrap your sql against ORM!
Go straight: your I/O = your DTOs

Input bean

GetTranslationInfoIn.java


/**
 * Copyright (c) minuteproject, minuteproject@gmail.com
 * All rights reserved.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * More information on minuteproject:
 * twitter @minuteproject
 * wiki http://minuteproject.wikispaces.com 
 * blog http://minuteproject.blogspot.net
 * 
*/
/**
 * template reference : 
 * - name : SDDInputBean
 * - file name : JavaBean.vm
*/
package net.sf.mp.demo.tranxy.sdd.in.statement;

import java.util.List;
import java.util.ArrayList;

//MP-MANAGED-ADDED-AREA-BEGINNING @import@
//MP-MANAGED-ADDED-AREA-ENDING @import@
/**
 *
 * <p>Title: GetTranslationInfoIn</p>
 *
 * <p>Description: Java Bean containing a collection of GetTranslationInfo </p>
 *
 */
public class GetTranslationInfoIn {

    private String key;
    private String code;
    private Integer max;

    /**
    * Default constructor
    */
    public GetTranslationInfoIn() {
    }
 
    public String getKey() {
        return key;
    }
 
    public void setKey (String key) {
        this.key =  key;
    }

    public String getCode() {
        return code;
    }
 
    public void setCode (String code) {
        this.code =  code;
    }

    public Integer getMax() {
        return max;
    }
 
    public void setMax (Integer max) {
        this.max =  max;
    }


//MP-MANAGED-ADDED-AREA-BEGINNING @implementation@
//MP-MANAGED-ADDED-AREA-ENDING @implementation@
}

Output beans

GetTranslationInfoOut


/**
 * Copyright (c) minuteproject, minuteproject@gmail.com
 * All rights reserved.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * More information on minuteproject:
 * twitter @minuteproject
 * wiki http://minuteproject.wikispaces.com 
 * blog http://minuteproject.blogspot.net
 * 
*/
/**
 * template reference : 
 * - name : SDDOutputBean
 * - file name : JavaBean.vm
*/
package net.sf.mp.demo.tranxy.sdd.out.statement;

import java.util.List;
import java.util.ArrayList;

//MP-MANAGED-ADDED-AREA-BEGINNING @import@
//MP-MANAGED-ADDED-AREA-ENDING @import@
/**
 *
 * <p>Title: GetTranslationInfoOut</p>
 *
 * <p>Description: Java Bean containing a collection of GetTranslationInfo </p>
 *
 */
public class GetTranslationInfoOut {

    private String keyName;
    private String translation;
    private String dateFinalization;
    private String code;
    private String description;
    private String firstName;
    private String lastName;
    private String email;

    /**
    * Default constructor
    */
    public GetTranslationInfoOut() {
    }
 
    public String getKeyName() {
        return keyName;
    }
 
    public void setKeyName (String keyName) {
        this.keyName =  keyName;
    }

    public String getTranslation() {
        return translation;
    }
 
    public void setTranslation (String translation) {
        this.translation =  translation;
    }

    public String getDateFinalization() {
        return dateFinalization;
    }
 
    public void setDateFinalization (String dateFinalization) {
        this.dateFinalization =  dateFinalization;
    }

    public String getCode() {
        return code;
    }
 
    public void setCode (String code) {
        this.code =  code;
    }

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

    public String getFirstName() {
        return firstName;
    }
 
    public void setFirstName (String firstName) {
        this.firstName =  firstName;
    }

    public String getLastName() {
        return lastName;
    }
 
    public void setLastName (String lastName) {
        this.lastName =  lastName;
    }

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


//MP-MANAGED-ADDED-AREA-BEGINNING @implementation@
//MP-MANAGED-ADDED-AREA-ENDING @implementation@
}

GetTranslationInfoOutList


/**
 * Copyright (c) minuteproject, minuteproject@gmail.com
 * All rights reserved.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * More information on minuteproject:
 * twitter @minuteproject
 * wiki http://minuteproject.wikispaces.com 
 * blog http://minuteproject.blogspot.net
 * 
*/
/**
 * template reference : 
 * - name : SDDOutputBeanCollection
 * - file name : JavaBeanCollection.vm
*/
package net.sf.mp.demo.tranxy.sdd.out.statement;

import java.util.List;
import java.util.ArrayList;

import javax.xml.bind.annotation.*;

import net.sf.mp.demo.tranxy.sdd.out.statement.GetTranslationInfoOut;

//MP-MANAGED-ADDED-AREA-BEGINNING @import@
//MP-MANAGED-ADDED-AREA-ENDING @import@
/**
 *
 * <p>Title: GetTranslationInfoOutList</p>
 *
 * <p>Description: Java Bean GetTranslationInfoOutList </p>
 *
 */
@XmlRootElement (name="GetTranslationInfoOutList")
public class GetTranslationInfoOutList {

    @XmlElement (name="GetTranslationInfoOut") //(name="gettranslationinfoouts")
    private List<GetTranslationInfoOut> getTranslationInfoOuts;

    /**
    * Default constructor
    */
    public GetTranslationInfoOutList() {
    }
 
    public void setGetTranslationInfoOuts (List<GetTranslationInfoOut> getTranslationInfoOuts) {
        this.getTranslationInfoOuts = getTranslationInfoOuts;
    }

    @XmlTransient
    public List<GetTranslationInfoOut> getGetTranslationInfoOuts () {
        if (getTranslationInfoOuts==null)
            getTranslationInfoOuts = new ArrayList<GetTranslationInfoOut>();
        return getTranslationInfoOuts;
    }

    public void addGetTranslationInfoOut (GetTranslationInfoOut getTranslationInfoOut) {
        getGetTranslationInfoOuts ().add(getTranslationInfoOut);
    }


//MP-MANAGED-ADDED-AREA-BEGINNING @implementation@
//MP-MANAGED-ADDED-AREA-ENDING @implementation@
}

Improvements

SDD concept is brand new inside minuteproject, a number of improvement are foreseen:
  • error handling
  • validation (type, mandatory)
  • default value

Build deploy and test

Build

Run >mvn clean package from the root application

Deploy

Drop the resulting artefact tranxyRestCxfApp.war in tomcat/webapps
Start tomcat

Test

2 URLs - one for json format the other for xml format are available

REST URL with parameters returning json format
http://localhost:8080/tranxyRestCxfApp/rest/json/gettranslationinfos?key=%25&code=%25&max=2
Search for all key : % and all code : % and max number of return value is 2
REST URL with parameters returning xml format
Search for all key : % and all code : % and max number of return value is 1

Now you can enjoy and make cool front-end (ajax, jquery friendly and other js).

Conclusion

If you want to go fast, and when working with ORM abstraction takes to long, then have a look at Statement Driven Development.

The war between pro Domain Object vs. DTO is irrelevant for Minuteproject.
On this project both live aside.
Use the approach that best suits your needs.


Wednesday, May 23, 2012

RigaJUG - demo - instant OPENXAVA app

From the following model called Tranxy

we'll generate instantly an Openxava app.

The interesting part of this demo is the enrichment of the model, it is described Riga demo 2 (The main complexity comes by having 2 tables (language and user) linked by 2 many-to-many).

Actions

There are four simple actions make it happened:
  • Drop TRANXY-OPENXAVA.xml in /mywork/config and run: model-generation.cmd/sh TRANXY-OPENXAVA.xml
  • In /output/OPENXAVA set the 2 environment variables
    • set OX_HOME=../openxava-4.4.1 (export on unix)
    • set MP_HOME=../minuteProject-0.8.1 (export on unix)
  • execute >build-tranxy.cmd/sh
And that's it!
Last action will 
  • create an Openxava project
  • build it (portlet included ready for Liferay)
  • start Openxava embedded tomcat server
  • add connection datasource
  • deploy the build application on tomcat
  • start a browser connection at  http://localhost:8080/tranxy/xava/homeMenu.jsp where the menu is.

The application works on an empty database, you can use the Openxava-Minuteproject-generated-app to
populate it...
Enjoy!

Prerequisits

MinuteProject installed 
Openxava installed 
Ant installed
Model Tranxy (script) installed on an up-and-running Mysql server
Java 1.6

Configuration

TRANXY-OPENXAVA.xml
<!DOCTYPE root>
<generator-config xmlns="http://minuteproject.sf.net/xsd/mp-config" 
xmlns:xs="http://www.w3.org/2001/XMLSchema-instance" 
xs:noNamespaceSchemaLocation="../config/mp-config.xsd">
    <configuration>
        <conventions>
            <target-convention type="enable-updatable-code-feature" />
        </conventions>
        <model name="tranxy" version="1.0" package-root="net.sf.mp.demo">
            <data-model>
                <driver name="mysql" version="5.1.16" groupId="mysql"
                    artifactId="mysql-connector-java"></driver>
                <dataSource>
                    <driverClassName>org.gjt.mm.mysql.Driver</driverClassName>
                    <url>jdbc:mysql://127.0.0.1:3306/tranxy</url>
                    <username>root</username>
                    <password>mysql</password>
                </dataSource>
                <primaryKeyPolicy oneGlobal="false" >
                    <primaryKeyPolicyPattern name="autoincrementPattern"></primaryKeyPolicyPattern>
                </primaryKeyPolicy>
            </data-model>
            <business-model>
                <business-package default="tranxy">
                    <condition type="package" startsWith="trans" result="translation"></condition>
                </business-package>
            <enrichment>
             <conventions>
              <!-- manipulate the structure and entities BEFORE manipulating the 
               entities -->
              <column-naming-convention type="apply-fix-primary-key-column-name-when-no-ambiguity" 
                      default-value="ID"/>  
              <column-naming-convention type="apply-strip-column-name-suffix"
               pattern-to-strip="ID" />
              <reference-naming-convention
               type="apply-referenced-alias-when-no-ambiguity" is-to-plurialize="true" />
              <reference-naming-convention type="apply-many-to-many-aliasing" is-to-plurialize="true"/>
             </conventions>
                  <entity name="language_x_translator">
                      <field name="language_id" linkReferenceAlias="translating_language" linkToTargetEntity="LANGUAGE"/>
                      <field name="user_id" linkReferenceAlias="translator" linkToTargetEntity="USER"/>
                  </entity>
                  <entity name="LANGUAGE_X_SPEAKER">
                      <field name="LANGUAGE_ID" linkToTargetEntity="LANGUAGE"
                          linkToTargetField="IDLANGUAGE" linkReferenceAlias="spoken_language" />
                      <field name="user_id" linkReferenceAlias="speaker" linkToTargetEntity="USER"/>
                  </entity>
                  <entity name="APPLICATION" alias="registered application">
                      <field name="TYPE" alias="obedience">
                          <property tag="checkconstraint" alias="application_type">
                              <property name="OPENSOURCE"/>
                              <property name="COPYRIGHT" />
                          </property>
                      </field>
                  </entity>
                  <entity name="LANGUAGE" content-type="reference-data"/>
            </enrichment>
            </business-model>
        </model>
        <targets>
        
            <target refname="OpenXava" 
               name="OpenXava" 
               fileName="mp-template-config-openxava-last-features.xml" 
               outputdir-root="../../dev/latvianjug/output/OPENXAVA"
               templatedir-root="../../template/framework/openxava">
            </target> 
            <target refname="JPA2-LIB" 
               fileName="mp-template-config-JPA2-LIB.xml" 
               templatedir-root="../../template/framework/jpa">
            </target>
            <target refname="CACHE-LIB" 
               fileName="mp-template-config-CACHE-LIB.xml" 
               templatedir-root="../../template/framework/cache">
            </target>
            <target refname="LIB" 
               fileName="mp-template-config-bsla-LIB-features.xml" 
               templatedir-root="../../template/framework/bsla">
            </target>

        </targets>
    </configuration>
</generator-config>

Screenshots

Add an application

Customise Translation Column




RigaJUG - demo - REST CXF

In this demo, we will generate a REST/CXF application with spring/jpa2 backend.
This demo presents the track REST-CXF of minuteproject.

The model from demo 2 remains the same.

The enrichment stays the same.

But the tracks changes
What is added are 2 layers:
  • a DAO layer with spring integration on top of JPA2
  • a REST-CXF layer with JAX-RS annotation
The JPA2 entities are annotated by JAXB annotations.

All this is done to provide a CRUD interface on top of the model entities.

Configuration

Here is the configuration TRANXY-JPA2-Spring-REST-CXF.xml
<!DOCTYPE root>
<generator-config xmlns="http://minuteproject.sf.net/xsd/mp-config" 
xmlns:xs="http://www.w3.org/2001/XMLSchema-instance" 
xs:noNamespaceSchemaLocation="../config/mp-config.xsd">
 <configuration>
  <conventions>
   <target-convention type="enable-updatable-code-feature" />
  </conventions>
  <model name="tranxy" version="1.0" package-root="net.sf.mp.demo">
   <data-model>
    <driver name="mysql" version="5.1.16" groupId="mysql"
     artifactId="mysql-connector-java"></driver>
    <dataSource>
     <driverClassName>org.gjt.mm.mysql.Driver</driverClassName>
     <url>jdbc:mysql://127.0.0.1:3306/tranxy</url>
     <username>root</username>
     <password>mysql</password>
    </dataSource>
    <primaryKeyPolicy oneGlobal="false" >
     <primaryKeyPolicyPattern name="autoincrementPattern"></primaryKeyPolicyPattern>
    </primaryKeyPolicy>
   </data-model>
   <business-model>
    <generation-condition>
     <condition type="exclude" startsWith="QUARTZ"></condition>
    </generation-condition>
    <business-package default="tranxy">
     <condition type="package" startsWith="trans" result="translation"></condition>
    </business-package>
    <enrichment>
     <conventions>
      <!-- manipulate the structure and entities BEFORE manipulating the 
       entities -->
      <column-naming-convention type="apply-strip-column-name-suffix"
       pattern-to-strip="ID" />
      <reference-naming-convention
       type="apply-referenced-alias-when-no-ambiguity" is-to-plurialize="true" />
     </conventions>
                    <entity name="language_x_translator">
                        <field name="language_id" linkReferenceAlias="translating_language" />
                        <field name="user_id" linkReferenceAlias="translator" />
                    </entity>
                    <entity name="LANGUAGE_X_SPEAKER">
                        <field name="LANGUAGE_ID" linkToTargetEntity="LANGUAGE"
                            linkToTargetField="IDLANGUAGE" linkReferenceAlias="spoken_language" />
                        <field name="user_id" linkReferenceAlias="speaker" />
                    </entity>
                    <entity name="APPLICATION">
                        <field name="TYPE">
                            <property tag="checkconstraint" alias="application_type">
                                <property name="OPENSOURCE"/>
                                <property name="COPYRIGHT" />
                            </property>
                        </field>
                    </entity>
    </enrichment>
   </business-model>
  </model>
  <targets>
            
      <target refname="REST-CXF-BSLA" 
         name="default" 
         fileName="mp-template-config-REST-CXF-Spring.xml" 
         outputdir-root="../../DEV/latvianjug/tranxy/rest"
         templatedir-root="../../template/framework/cxf">
      </target>

      <target refname="BackendOnBsla" 
         name="default" 
         fileName="mp-template-config-JPA2-bsla.xml" 
         outputdir-root="../../DEV/latvianjug/tranxy/bsla"
         templatedir-root="../../template/framework/bsla">
          <property name="add-cache-implementation" value="ehcache"></property>
      </target> 
      
   <target refname="JPA2" fileName="mp-template-config-JPA2.xml"
    outputdir-root="../../DEV/latvianjug/tranxy/jpa" 
        templatedir-root="../../template/framework/jpa">
 <property name="add-querydsl" value="2.1.2"></property>
 <property name="add-jpa2-implementation" value="hibernate"></property>
        <property name="add-cache-implementation" value="ehcache"></property>
        <property name="add-domain-specific-method" value="true"></property>
        <property name="add-xmlbinding" value="true"></property> 
        <property name="add-xml-format" value="lowercase-hyphen"></property> 
   </target>
      <target refname="MavenMaster" 
         name="maven" 
         fileName="mp-template-config-maven.xml" 
         outputdir-root="../../DEV/latvianjug/tranxy"
         templatedir-root="../../template/framework/maven">
      </target>

      <target refname="CACHE-LIB" fileName="mp-template-config-CACHE-LIB.xml"
          templatedir-root="../../template/framework/cache">
      </target>
      
 <target refname="LIB" fileName="mp-template-config-bsla-LIB-features.xml"
 templatedir-root="../../template/framework/bsla">
</target>

      <target refname="REST-LIB" 
         fileName="mp-template-config-REST-LIB.xml" 
         templatedir-root="../../template/framework/rest">
      </target>
      <target refname="SPRING-LIB" 
         fileName="mp-template-config-SPRING-LIB.xml" 
         templatedir-root="../../template/framework/spring">
      </target>

  </targets>
 </configuration>
</generator-config>

It is possible to enrich each link of the many-to-many relationship table (ex: language_x_translator and language_x_speaker) to provide the other side relationship with an alias and thus avoid long composite variable name.

Generation

Set TRANXY-JPA2-Spring-REST-CXF.xml in /mywork/config
Run
>model-generation.cmd TRANXY-JPA2-Spring-REST-CXF.xml
The output goes in /dev/latvianjug/tranxy

Resulting artefacts

A maven project structure with 3 modules
  • JPA2 layer
  • Spring DAO layer
  • CXF layer
JPA2 layer has been visited in Demo 1 and Demo 2.

Spring DAO layer

It consists of transactional services one for each entity

CRUD DAO layer on top of JPA2: This layer is called BSLA (Basic Spring Layer Architecture).

Two interfaces and implementation are generated for each entity
Example for Translation entity

DAO Interfaces

/**
 * Copyright (c) minuteproject, minuteproject@gmail.com
 * All rights reserved.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * More information on minuteproject:
 * twitter @minuteproject
 * wiki http://minuteproject.wikispaces.com 
 * blog http://minuteproject.blogspot.net
 * 
*/
/**
 * template reference : 
 * - name : BslaDaoInterfaceUML
 * - file name : BslaDaoInterfaceUML.vm
*/
package net.sf.mp.demo.tranxy.dao.face.translation;

import net.sf.mp.demo.tranxy.domain.translation.Translation;
import java.util.List;
import net.sf.minuteProject.architecture.bsla.bean.criteria.PaginationCriteria;
import net.sf.minuteProject.architecture.bsla.dao.face.DataAccessObject;


/**
 *
 * <p>Title: TranslationDao</p>
 *
 * <p>Description: Interface of a Data access object dealing with TranslationDao
 * persistence. It offers a set of methods which allow for saving,
 * deleting and searching translation objects</p>
 *
 */
public interface TranslationDao extends DataAccessObject {

    /**
     * Inserts a Translation entity 
     * @param Translation translation
     */
    public void insertTranslation(Translation translation) ;
 
    /**
     * Inserts a list of Translation entity 
     * @param List<Translation> translations
     */
    public void insertTranslations(List<Translation> translations) ;
        
    /**
     * Updates a Translation entity 
     * @param Translation translation
     */
    public Translation updateTranslation(Translation translation) ;

  /**
     * Updates a Translation entity with only the attributes set into Translation.
  * The primary keys are to be set for this method to operate.
  * This is a performance friendly feature, which remove the udibiquous full load and full update when an
  * update is to be done
   * Remark: The primary keys cannot be update by this methods, nor are the attributes that must be set to null.
   * @param Translation translation
   */
    public int updateNotNullOnlyTranslation(Translation translation) ;
  
 public int updateNotNullOnlyPrototypeTranslation(Translation translation, Translation prototypeCriteria);
 
     /**
     * Saves a Translation entity 
     * @param Translation translation
     */
    public void saveTranslation(Translation translation);
    
    /**
     * Deletes a Translation entity 
     * @param Translation translation
     */
    public void deleteTranslation(Translation translation) ;
 
    /**
     * Loads the Translation entity which is related to an instance of
     * Translation
     * @param Long id
     * @return Translation The Translation entity
     
    public Translation loadTranslation(Long id);
*/
    /**
     * Loads the Translation entity which is related to an instance of
     * Translation
     * @param java.lang.Long Id
     * @return Translation The Translation entity
     */
    public Translation loadTranslation(java.lang.Long id);    

    /**
     * Loads a list of Translation entity 
     * @param List<java.lang.Long> ids
     * @return List<Translation> The Translation entity
     */
    public List<Translation> loadTranslationListByTranslation (List<Translation> translations);
    
    /**
     * Loads a list of Translation entity 
     * @param List<java.lang.Long> ids
     * @return List<Translation> The Translation entity
     */
    public List<Translation> loadTranslationListById(List<java.lang.Long> ids);
    
    /**
     * Loads the Translation entity which is related to an instance of
     * Translation and its dependent one to many objects
     * @param Long id
     * @return Translation The Translation entity
     */
    public Translation loadFullFirstLevelTranslation(java.lang.Long id);
    
    /**
     * Loads the Translation entity which is related to an instance of
     * Translation
     * @param Translation translation
     * @return Translation The Translation entity
     */
    public Translation loadFullFirstLevelTranslation(Translation translation);    
    
    
    /**
     * Loads the Translation entity which is related to an instance of
     * Translation and its dependent objects one to many
     * @param Long id
     * @return Translation The Translation entity
     */
    public Translation loadFullTranslation(Long id) ;

    /**
     * Searches a list of Translation entity based on a Translation containing Translation matching criteria
     * @param Translation translation
     * @return List<Translation>
     */
    public List<Translation> searchPrototypeTranslation(Translation translation) ;
    
    /**
     * Searches a list of Translation entity based on a list of Translation containing Translation matching criteria
     * @param List<Translation> translations
     * @return List<Translation>
     */
    public List<Translation> searchPrototypeTranslation(List<Translation> translations) ;    
     
 /**
     * Searches a list of Translation entity 
     * @param Translation translation
     * @return List
     */
    public List<Translation> searchPrototypeTranslation(Translation translationPositive, Translation translationNegative) ;
        
    /**
     * Load a paginated list of Translation entity dependent of pagination criteria
     * @param PaginationCriteria paginationCriteria
     * @return List
     */
    public List<Translation> loadPaginatedTranslation (Translation translation, PaginationCriteria paginationCriteria) ;
            
}

/**
 * Copyright (c) minuteproject, minuteproject@gmail.com
 * All rights reserved.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * More information on minuteproject:
 * twitter @minuteproject
 * wiki http://minuteproject.wikispaces.com 
 * blog http://minuteproject.blogspot.net
 * 
*/
/**
 * template reference : 
 * - name : BslaDaoInterfaceExtendedUML
 * - file name : BslaDaoInterfaceKFUML.vm
*/
package net.sf.mp.demo.tranxy.dao.face.translation;

import net.sf.mp.demo.tranxy.domain.translation.Translation;
import java.util.List;
import net.sf.minuteProject.architecture.filter.data.Criteria;
import net.sf.minuteProject.architecture.bsla.dao.face.DataAccessObject;

/**
 *
 * <p>Title: TranslationExtDao</p>
 *
 * <p>Description: Interface of a Data access object dealing with TranslationExtDao
 * persistence. It offers extended DAO functionalities</p>
 *
 */
public interface TranslationExtDao extends DataAccessObject {
     
     /**
     * Inserts a Translation entity with cascade of its children
     * @param Translation translation
     */
    public void insertTranslationWithCascade(Translation translation) ;
    
    /**
     * Inserts a list of Translation entity with cascade of its children
     * @param List<Translation> translations
     */
    public void insertTranslationsWithCascade(List<Translation> translations) ;        
   
    /**
     * lookup Translation entity Translation, criteria and max result number
     */
    public List<Translation> lookupTranslation(Translation translation, Criteria criteria, Integer numberOfResult);
 
 public Integer updateNotNullOnlyTranslation (Translation translation, Criteria criteria);

 /**
  * Affect the first translation retrieved corresponding to the translation criteria.
  * Blank criteria are mapped to null.
  * If no criteria is found, null is returned.
  */
    public Translation affectTranslation (Translation translation);
    
    public Translation affectTranslationUseCache (Translation translation);
     
 /**
  * Assign the first translation retrieved corresponding to the translation criteria.
  * Blank criteria are mapped to null.
  * If no criteria is found, null is returned.
  * If there is no translation corresponding in the database. Then translation is inserted and returned with its primary key(s). 
  */
    public Translation assignTranslation (Translation translation);

 /**
  * Assign the first translation retrieved corresponding to the mask criteria.
  * Blank criteria are mapped to null.
  * If no criteria is found, null is returned.
  * If there is no translation corresponding in the database. 
  * Then translation is inserted and returned with its primary key(s). 
  * Mask servers usually to set unique keys or the semantic reference
  */
    public Translation assignTranslation (Translation translation, Translation mask);
 
    public Translation assignTranslationUseCache (Translation translation);
         
    /**
    * return the first Translation entity found
    */           
    public Translation getFirstTranslation (Translation translation);
    
    /**
    * checks if the Translation entity exists
    */           
    public boolean existsTranslation (Translation translation);    
    
    public boolean existsTranslationWhereConditionsAre (Translation translation);

    /**
    * partial load enables to specify the fields you want to load explicitly
    */            
    public List<Translation> partialLoadTranslation(Translation translation, Translation positiveTranslation, Translation negativeTranslation);

    /**
    * partial load with parent entities
    * variation (list, first, distinct decorator)
    * variation2 (with cache)
    */
    public List<Translation> partialLoadWithParentTranslation(Translation translation, Translation positiveTranslation, Translation negativeTranslation);

    public List<Translation> partialLoadWithParentTranslationUseCache(Translation translation, Translation positiveTranslation, Translation negativeTranslation, Boolean useCache);

    public List<Translation> partialLoadWithParentTranslationUseCacheOnResult(Translation translation, Translation positiveTranslation, Translation negativeTranslation, Boolean useCache);

    /**
    * variation first
    */
    public Translation partialLoadWithParentFirstTranslation(Translation translationWhat, Translation positiveTranslation, Translation negativeTranslation);
    
    public Translation partialLoadWithParentFirstTranslationUseCache(Translation translationWhat, Translation positiveTranslation, Translation negativeTranslation, Boolean useCache);

    public Translation partialLoadWithParentFirstTranslationUseCacheOnResult(Translation translationWhat, Translation positiveTranslation, Translation negativeTranslation, Boolean useCache);

    /**
    * variation distinct
    */
    public List<Translation> getDistinctTranslation(Translation translationWhat, Translation positiveTranslation, Translation negativeTranslation);

    //
    public List partialLoadWithParentForBean(Object bean, Translation translation, Translation positiveTranslation, Translation negativeTranslation);

    /**
    * search on prototype with cache
    */
    public List<Translation> searchPrototypeWithCacheTranslation (Translation translation);
      
    
    /**
     * Searches a list of distinct Translation entity based on a Translation mask and a list of Translation containing Translation matching criteria
     * @param Translation translation
     * @param List<Translation> translations
     * @return List<Translation>
     */
    public List<Translation> searchDistinctPrototypeTranslation(Translation translationMask, List<Translation> translations) ;    

 public List<Translation> countDistinct (Translation whatMask, Translation whereEqCriteria);
 
 public Long count (Translation whereEqCriteria);
 
 public List<Translation> loadGraph(Translation graphMaskWhat, List<Translation> whereMask);  
 
 public List<Translation> loadGraphFromParentKey (Translation graphMaskWhat, List<Translation> parents); 
 
    /**
     * generic to move after in superclass
     */
    public List<Object[]> getSQLQueryResult(String query);     
 
}

DAO implementations

TranslationJPAImpl and TranslationJPAExtImpl (code not copied).

In the future Generic DAO will be used for cross-entity redundant aspects.
Adaptation to spring 3.x will be perform (i.e no more JPASupport extension by EntityManager injection)
Meanwhile the code here above works fine with spring 2.5+

Spring configurations

spring-config-Tranxy-BE-main.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>

    <!-- Dao JPA -->
    <import resource="classpath:net/sf/mp/demo/tranxy/factory/spring/spring-config-JPA-Tranxy-dao.xml"/>    

<!--MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @JPAtranxyFactory-tranxy@-->
    <!-- hibernate config to put in an appart config file-->
    <bean id="JPAtranxyFactory" autowire="byName"
        class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
  <!-- all connection information are retrieve from the persistence file-->
  <!--
   <property name="dataSource" ref="..."/>
   <property name="persistenceUnitName" value="..."/>
  -->
        <property name="persistenceXmlLocation" value="classpath:META-INF/persistence.xml" />
    </bean>
<!--MP-MANAGED-UPDATABLE-ENDING-->
    <!-- Database -->
    <import resource="classpath:net/sf/mp/demo/tranxy/factory/spring/spring-config-Tranxy-database.xml"/>    

</beans>

spring-config-Tranxy-database.xml
<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:jndi="http://www.springframework.org/schema/jee"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                           http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd
                           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">


    <bean id="placeHolderConfig"
        class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="location"><value>classpath:net/sf/mp/demo/tranxy/factory/spring/spring-config-Tranxy.properties</value></property>
    </bean>     
 
    <bean id="tranxyTransactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="JPAtranxyFactory"/>
    </bean>

    <!-- to get the entity manager -->
    <tx:annotation-driven transaction-manager="tranxyTransactionManager"/>
    
</beans>

spring-config-Tranxy-BE-main
jdbc.tranxy.driverClassName=org.gjt.mm.mysql.Driver
jdbc.tranxy.url=jdbc:mysql://127.0.0.1:3306/tranxy
jdbc.tranxy.username=root
jdbc.tranxy.password=mysql
jdbc.tranxy.jndi=jdbc/tranxy
hibernate.dialect=org.hibernate.dialect.MySQLDialect

spring-config-JPA-Tranxy-dao.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>

    <!-- Import Dao definitions for business components -->

   <!-- tranxy -->
   <import resource="classpath:net/sf/mp/demo/tranxy/factory/spring/tranxy/dao-JPA-Tranxy.xml"/>
   <!-- translation -->
   <import resource="classpath:net/sf/mp/demo/tranxy/factory/spring/translation/dao-JPA-Translation.xml"/>

    <!-- Import Ext Dao definitions for business components -->
   <!-- tranxy extended dao -->
   <import resource="classpath:net/sf/mp/demo/tranxy/factory/spring/tranxy/dao-ext-JPA-Tranxy.xml"/>
   <!-- translation extended dao -->
   <import resource="classpath:net/sf/mp/demo/tranxy/factory/spring/translation/dao-ext-JPA-Translation.xml"/>

</beans>

dao-JPA-Translation.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>

    <bean id="translationDao" class="net.sf.mp.demo.tranxy.dao.impl.jpa.translation.TranslationJPAImpl" singleton="false" >
        <property name="entityManagerFactory"><ref bean="JPAtranxyFactory"/></property>
    </bean>
    <bean id="translationKeyDao" class="net.sf.mp.demo.tranxy.dao.impl.jpa.translation.TranslationKeyJPAImpl" singleton="false" >
        <property name="entityManagerFactory"><ref bean="JPAtranxyFactory"/></property>
    </bean>
    <bean id="translationRequestDao" class="net.sf.mp.demo.tranxy.dao.impl.jpa.translation.TranslationRequestJPAImpl" singleton="false" >
        <property name="entityManagerFactory"><ref bean="JPAtranxyFactory"/></property>
    </bean>

</beans>
It is the same for the dao-ext-JPA-Translation.xml, dao-ext-JPA-Tranxy.xml, dao-JPA-Tranxy.xml files

But wait a minute... How can I unit test?
You need two other artifacts before writting your own test.
One is persistence.xml...
Again? Yes, with a embedded connection pool, because the shipped with the build of your JPA2 layer may refere a JNDI Datasource (in case the property environment is set to remote).
Since it is under /src/test/resources/META-INF it will override the one in the JPA2 package.

Two is an adapter that extends AbstractTransactionalJUnit4SpringContextTests: it is generated in /src/test/java

package net.sf.mp.demo.tranxy.dao.face;

import javax.sql.DataSource;

import org.apache.commons.lang.StringUtils;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.TransactionConfiguration;
import org.springframework.transaction.annotation.Transactional;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={
     "classpath:net/sf/mp/demo/tranxy/factory/spring/spring-config-Tranxy-BE-main.xml"
})
@TransactionConfiguration(transactionManager = "tranxyTransactionManager") 
@Transactional
public class AdapterTranxyTestDao extends AbstractTransactionalJUnit4SpringContextTests { 

 @Override
 @Autowired
 public void setDataSource(@Qualifier(value = "tranxyDataSource") DataSource dataSource) {
    this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);
 }
...

CXF layer

Each entity have a Rest Resource artifact with JAX-RS annotations to enable CRUD access.
Example with Translation

/**
 * Copyright (c) minuteproject, minuteproject@gmail.com
 * All rights reserved.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * More information on minuteproject:
 * twitter @minuteproject
 * wiki http://minuteproject.wikispaces.com 
 * blog http://minuteproject.blogspot.net
 * 
*/
/**
 * template reference : 
 * - name : CXFSpringEntityResource
 * - file name : CXFSpringEntityResource.vm
*/
package net.sf.mp.demo.tranxy.rest.translation;


import java.util.Date;
import java.util.List;
import java.util.ArrayList;
import java.io.*;
import java.sql.*;

import javax.servlet.http.*;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.FormParam;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import javax.xml.bind.JAXBElement;

import net.sf.mp.demo.tranxy.dao.face.translation.TranslationDao;
import net.sf.mp.demo.tranxy.dao.face.translation.TranslationExtDao;
import net.sf.mp.demo.tranxy.domain.translation.Translation;

/**
 *
 * <p>Title: TranslationResource</p>
 *
 * <p>Description: remote interface for TranslationResource service </p>
 *
 */
@Path ("/rest/xml/translations")
@Produces ({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
@Consumes ({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
@Service
@Transactional
public class TranslationResource  {
 

 @Autowired
 @Qualifier("translationDao")
 TranslationDao translationDao;
 
 @Autowired
 @Qualifier("translationExtDao")
 TranslationExtDao translationExtDao;

//MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @FIND_ALL-translation@
    @GET
    @Produces ({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) 
    public List<Translation> findAll () {
  List<Translation> r = new ArrayList<Translation>();
        List<Translation> l = translationDao.searchPrototypeTranslation(new Translation());
  for (Translation translation : l) {
   r.add(translation.flat());
  }
  return r;
    }
//MP-MANAGED-UPDATABLE-ENDING

//MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @FIND_BY_ID-translation@
    @GET
    @Path("{id}")
    @Produces ({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})  
    public Translation findById (@PathParam ("id") java.lang.Long id) {
        Translation _translation = new Translation ();
  _translation.setId(id);
  _translation = translationExtDao.getFirstTranslation(_translation);
  if (_translation!=null) return _translation.flat();
  return new Translation ();
    }
//MP-MANAGED-UPDATABLE-ENDING

    @DELETE
    @Path("{id}")
    public void delete (@PathParam ("id") Long id) {
        Translation translation = new Translation ();
        translation.setId(id);
        translationDao.deleteTranslation(translation);
    }

    @POST
    @Produces ({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) 
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    public Translation create (
        @FormParam("id") Long id,
        @FormParam("translation") String translation,
        @FormParam("language") Integer language,
        @FormParam("key") Long key,
        @FormParam("isFinal") Short isFinal,
        @FormParam("dateFinalization") Date dateFinalization,
        @FormParam("translator") Long translator,
        @Context HttpServletResponse servletResponse
        ) throws IOException {
        Translation _translation = new Translation (
           id,
           translation,
           language,
           key,
           isFinal,
           dateFinalization,
           translator);
        return save(_translation);
    }

    @PUT
    @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
    public Translation save(JAXBElement<Translation> jaxbTranslation) {
        Translation translation = jaxbTranslation.getValue();
        if (translation.getId()!=null)
            return translationDao.updateTranslation(translation);
        return save(translation);
    }

 public Translation save (Translation translation) {
  translationDao.saveTranslation(translation);
  return translation;
 }


}

And two files for the web application and spring in /src/main/resources/webapp/WEB-INF

Web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

    <display-name>tranxy CXF REST</display-name>
    <description>tranxy CXF REST access</description>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/application-context.xml</param-value>
    </context-param>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <servlet>
        <servlet-name>CXFServlet</servlet-name>
        <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>CXFServlet</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

</web-app>

application-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
 xmlns:context="http://www.springframework.org/schema/context"
 xmlns:tx="http://www.springframework.org/schema/tx"
 xmlns:jaxrs="http://cxf.apache.org/jaxrs"
 xsi:schemaLocation="
  http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  http://www.springframework.org/schema/context 
  http://www.springframework.org/schema/context/spring-context-3.0.xsd
  http://www.springframework.org/schema/tx
  http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
  http://cxf.apache.org/jaxrs
  http://cxf.apache.org/schemas/jaxrs.xsd">

    <import resource="classpath:META-INF/cxf/cxf.xml" />
    <import resource="classpath:META-INF/cxf/cxf-extension-jaxrs-binding.xml" />
    <import resource="classpath:META-INF/cxf/cxf-servlet.xml" />
    
    <context:component-scan base-package="net.sf.mp.demo.tranxy.rest"/>

    <import resource="classpath:net/sf/mp/demo/tranxy/factory/spring/spring-config-Tranxy-BE-main.xml"/>    
 
    <jaxrs:server id="restContainer" address="/">
        <jaxrs:serviceBeans>
   <!-- tranxy --> 
   <ref bean="applicationResource"/>
   <ref bean="languageResource"/>
   <ref bean="userResource"/>
   <!-- translation --> 
   <ref bean="translationResource"/>
   <ref bean="translationKeyResource"/>
   <ref bean="translationRequestResource"/>
 
   <!-- statements -->
        </jaxrs:serviceBeans>
    </jaxrs:server> 

</beans>

Package, deployment and test

Package

Before building the package there is a dependency shipped with minuteproject mp-bsla.x.y.jar to install. In /target/mp-bsla/ Run script: maven-install.cmd/sh
Build: >mvn clean package

The result is tranxyRestCxfApp.war in /rest/target

Deployment

Start tomcat
Drop tranxyRestCxfApp.war in /webapps
There is an embedded connection pool, so no configuration is needed on tomcat.

Test

USE `tranxy` ;
DELETE FROM application_x_key;
DELETE FROM translation;
DELETE FROM language_x_translator;
DELETE FROM language_x_speaker;
DELETE FROM request_key;

DELETE FROM application;
DELETE FROM translation_key;
DELETE FROM user;
DELETE FROM language;

INSERT INTO application (idapplication,  name, description, type) VALUES (-1,'Porphyry', 'OS application holding environment app', 'OPENSOURCE');
INSERT INTO application (idapplication,  name, description, type) VALUES (-2,'Minuteproject', 'Minuteproject app', 'OPENSOURCE');

INSERT INTO user (iduser, first_name, last_name, email) VALUES (-1,'test', 'lastName', 'test@test.me');
INSERT INTO user (iduser, first_name, last_name, email) VALUES (-2,'test2', 'lastName2', 'test2@test.me');

INSERT INTO language (idlanguage, code, description, locale) VALUES (-1, 'FR', 'France', 'fr');
INSERT INTO language (idlanguage, code, description, locale) VALUES (-2, 'ES', 'Spanish', 'es');                                                                                               
INSERT INTO language (idlanguage, code, description, locale) VALUES (-3, 'EN', 'English', 'en');          

INSERT INTO language_x_translator (language_id, user_id) VALUES (-1, -1);
INSERT INTO language_x_translator (language_id, user_id) VALUES (-2, -1);
INSERT INTO language_x_speaker (language_id, user_id) VALUES (-1, -1);
INSERT INTO language_x_speaker (language_id, user_id) VALUES (-2, -1);
   
INSERT INTO language_x_translator (language_id, user_id) VALUES (-1, -2);
INSERT INTO language_x_translator (language_id, user_id) VALUES (-2, -2);
INSERT INTO language_x_translator (language_id, user_id) VALUES (-3, -2);    

INSERT INTO translation_key (id, key_name, description) VALUES (-1, 'msg.user.name', 'user name');
INSERT INTO translation (id, translation, language_id, key_id, is_final, date_finalization, translator_id) 
VALUES (-1, 'nom', -1, -1, 1, '2012-04-04', -1);
INSERT INTO translation (id, translation, language_id, key_id, is_final, date_finalization, translator_id) 
VALUES (-2, 'apellido', -1, -2, 1, CURDATE(), -1);                                                                                                               
Now enter
http://localhost:8080/tranxyRestCxfApp/rest/xml/languages to get all the languages

This is the result

Now enter
http://localhost:8080/tranxyRestCxfApp/rest/xml/users/-1 to get the first user

This is the result

Conclusion

This article presented you how to get quickly a CRUD REST interface on top of your DB model.
Of course, you may not need CRUD for all entities and may you need more coarse grain functions to manipulate your model.
Next article will present you how with Statement Driven Development we can get closer to Use Case.

Sunday, May 20, 2012

RigaJUG - Demo 2 - JPA2

In the first demo the model was limited to 3 tables.
The second demo marks an evolution of our model. Now there are 10 tables.
This demo presents the track JPA2 of minuteproject.


Now we want to know:
  • Who translate what via translation request. 
  • What a user can speak and can translate.
In fact the model can be altered in multiple ways, here is just one possibility.
In the current diagram there are multiple many-to-many relationships
  • request_key
  • application_x_key
  • language_x_translator
  • language_x_speaker
And 2 (language_x_translator and language_x_speaker) link twice user to language...

This demo will illustrate:
  • Enrichment facilities and customisation
  • Generation for JPA2
  • Integration technics of resulting artefacts

Model source

SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0;
SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;
SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='TRADITIONAL';

DROP SCHEMA IF EXISTS `tranxy` ;
CREATE SCHEMA IF NOT EXISTS `tranxy` DEFAULT CHARACTER SET latin1;
USE `tranxy` ;

DROP TABLE IF EXISTS `tranxy`.`traduction` ;
DROP TABLE IF EXISTS `tranxy`.`translation_key` ;
DROP TABLE IF EXISTS `tranxy`.`language` ;
-- -----------------------------------------------------
-- Table `tranxy`.`translation_key`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `tranxy`.`translation_key` ;

CREATE  TABLE IF NOT EXISTS `tranxy`.`translation_key` (
  `id` BIGINT NOT NULL AUTO_INCREMENT ,
  `key_name` VARCHAR(200) NULL ,
  `description` VARCHAR(400) NULL ,
  PRIMARY KEY (`id`) )
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `tranxy`.`language`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `tranxy`.`language` ;

CREATE  TABLE IF NOT EXISTS `tranxy`.`language` (
  `idlanguage` INT NOT NULL AUTO_INCREMENT ,
  `code` VARCHAR(45) NOT NULL ,
  `locale` VARCHAR(10) NOT NULL ,
  `description` VARCHAR(45) NOT NULL ,
  PRIMARY KEY (`idlanguage`) ,
  UNIQUE INDEX `code_UNIQUE` (`code` ASC) )
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `tranxy`.`user`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `tranxy`.`user` ;

CREATE  TABLE IF NOT EXISTS `tranxy`.`user` (
  `iduser` BIGINT NOT NULL AUTO_INCREMENT ,
  `first_name` VARCHAR(45) NOT NULL ,
  `last_name` VARCHAR(45) NOT NULL ,
  `email` VARCHAR(200) NULL ,
  PRIMARY KEY (`iduser`) )
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `tranxy`.`translation`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `tranxy`.`translation` ;

CREATE  TABLE IF NOT EXISTS `tranxy`.`translation` (
  `id` BIGINT NOT NULL AUTO_INCREMENT ,
  `translation` VARCHAR(800) NULL ,
  `language_id` INT NOT NULL ,
  `Key_id` BIGINT NOT NULL ,
  `is_final` TINYINT NOT NULL ,
  `date_finalization` DATE NULL ,
  `translator_id` BIGINT NOT NULL ,
  PRIMARY KEY (`id`) ,
  INDEX `fk_traduction_language` (`language_id` ASC) ,
  INDEX `fk_traduction_Key1` (`Key_id` ASC) ,
  INDEX `fk_traduction_user1` (`translator_id` ASC) ,
  CONSTRAINT `fk_traduction_language`
    FOREIGN KEY (`language_id` )
    REFERENCES `tranxy`.`language` (`idlanguage` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_traduction_Key1`
    FOREIGN KEY (`Key_id` )
    REFERENCES `tranxy`.`translation_key` (`id` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_traduction_user1`
    FOREIGN KEY (`translator_id` )
    REFERENCES `tranxy`.`user` (`iduser` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `tranxy`.`application`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `tranxy`.`application` ;

CREATE  TABLE IF NOT EXISTS `tranxy`.`application` (
  `idapplication` BIGINT NOT NULL AUTO_INCREMENT ,
  `name` VARCHAR(100) NULL ,
  `description` VARCHAR(200) NULL ,
  `type` VARCHAR(45) NOT NULL ,
  PRIMARY KEY (`idapplication`) ,
  UNIQUE INDEX `name_UNIQUE` (`name` ASC) )
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `tranxy`.`application_x_key`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `tranxy`.`application_x_key` ;

CREATE  TABLE IF NOT EXISTS `tranxy`.`application_x_key` (
  `Key_id` BIGINT NOT NULL ,
  `application_id` BIGINT NOT NULL ,
  INDEX `fk_application_x_key_Key1` (`Key_id` ASC) ,
  INDEX `fk_application_x_key_application1` (`application_id` ASC) ,
  PRIMARY KEY (`Key_id`, `application_id`) ,
  CONSTRAINT `fk_application_x_key_Key1`
    FOREIGN KEY (`Key_id` )
    REFERENCES `tranxy`.`translation_key` (`id` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_application_x_key_application1`
    FOREIGN KEY (`application_id` )
    REFERENCES `tranxy`.`application` (`idapplication` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `tranxy`.`translation_request`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `tranxy`.`translation_request` ;

CREATE  TABLE IF NOT EXISTS `tranxy`.`translation_request` (
  `idtranslation_request` BIGINT NOT NULL AUTO_INCREMENT ,
  `name` VARCHAR(45) NULL ,
  `request_date` DATE NOT NULL ,
  `user_id` BIGINT NOT NULL ,
  `Key_id` BIGINT NOT NULL ,
  PRIMARY KEY (`idtranslation_request`) ,
  INDEX `fk_translation_request_user1` (`user_id` ASC) ,
  INDEX `fk_translation_request_Key1` (`Key_id` ASC) ,
  CONSTRAINT `fk_translation_request_user1`
    FOREIGN KEY (`user_id` )
    REFERENCES `tranxy`.`user` (`iduser` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_translation_request_Key1`
    FOREIGN KEY (`Key_id` )
    REFERENCES `tranxy`.`translation_key` (`id` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `tranxy`.`request_key`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `tranxy`.`request_key` ;

CREATE  TABLE IF NOT EXISTS `tranxy`.`request_key` (
  `translation_request_id` BIGINT NOT NULL ,
  `language_id` INT NOT NULL ,
  INDEX `fk_request_key_translation_request1` (`translation_request_id` ASC) ,
  PRIMARY KEY (`translation_request_id`, `language_id`) ,
  INDEX `fk_request_key_language1` (`language_id` ASC) ,
  CONSTRAINT `fk_request_key_translation_request1`
    FOREIGN KEY (`translation_request_id` )
    REFERENCES `tranxy`.`translation_request` (`idtranslation_request` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_request_key_language1`
    FOREIGN KEY (`language_id` )
    REFERENCES `tranxy`.`language` (`idlanguage` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `tranxy`.`language_x_translator`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `tranxy`.`language_x_translator` ;

CREATE  TABLE IF NOT EXISTS `tranxy`.`language_x_translator` (
  `language_id` INT NOT NULL ,
  `user_id` BIGINT NOT NULL ,
  PRIMARY KEY (`language_id`, `user_id`) ,
  INDEX `fk_request_key_language1` (`language_id` ASC) ,
  INDEX `fk_language_x_translator_user1` (`user_id` ASC) ,
  CONSTRAINT `fk_request_key_language10`
    FOREIGN KEY (`language_id` )
    REFERENCES `tranxy`.`language` (`idlanguage` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_language_x_translator_user1`
    FOREIGN KEY (`user_id` )
    REFERENCES `tranxy`.`user` (`iduser` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `tranxy`.`language_x_speaker`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `tranxy`.`language_x_speaker` ;

CREATE  TABLE IF NOT EXISTS `tranxy`.`language_x_speaker` (
  `language_id` INT NOT NULL ,
  `user_id` BIGINT NOT NULL ,
  PRIMARY KEY (`language_id`, `user_id`) ,
  INDEX `fk_request_key_language1` (`language_id` ASC) ,
  INDEX `fk_language_x_translator_user1` (`user_id` ASC) ,
  CONSTRAINT `fk_request_key_language100`
    FOREIGN KEY (`language_id` )
    REFERENCES `tranxy`.`language` (`idlanguage` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_language_x_translator_user10`
    FOREIGN KEY (`user_id` )
    REFERENCES `tranxy`.`user` (`iduser` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;

SET SQL_MODE=@OLD_SQL_MODE;
SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;
SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;

Enrichment facilities and customisation

Minuteproject configuration allows to define naming conventions working globally and allow specific enrichment whose granularity is limited to a table or a field.

New configuration

TRANXY-JPA2-2.xml
<!DOCTYPE root>
<generator-config xmlns="http://minuteproject.sf.net/xsd/mp-config" 
xmlns:xs="http://www.w3.org/2001/XMLSchema-instance" 
xs:noNamespaceSchemaLocation="../config/mp-config.xsd">
 <configuration>
  <conventions>
   <target-convention type="enable-updatable-code-feature" />
  </conventions>
  <model name="tranxy" version="1.0" package-root="net.sf.mp.demo">
   <data-model>
    <driver name="mysql" version="5.1.16" groupId="mysql"
     artifactId="mysql-connector-java"></driver>
    <dataSource>
     <driverClassName>org.gjt.mm.mysql.Driver</driverClassName>
     <url>jdbc:mysql://127.0.0.1:3306/tranxy</url>
     <username>root</username>
     <password>mysql</password>
    </dataSource>
    <primaryKeyPolicy oneGlobal="false" >
     <primaryKeyPolicyPattern name="autoincrementPattern"></primaryKeyPolicyPattern>
    </primaryKeyPolicy>
   </data-model>
   <business-model>
    <business-package default="tranxy">
     <condition type="package" startsWith="trans" result="translation"></condition>
    </business-package>
    <enrichment>
     <conventions>
      <!-- manipulate the structure and entities BEFORE manipulating the 
       entities -->
      <column-naming-convention type="apply-fix-primary-key-column-name-when-no-ambiguity" 
              default-value="ID"/>  
      <column-naming-convention type="apply-strip-column-name-suffix"
       pattern-to-strip="ID" />
      <reference-naming-convention
       type="apply-referenced-alias-when-no-ambiguity" is-to-plurialize="true" />
      <reference-naming-convention type="apply-many-to-many-aliasing" is-to-plurialize="true"/>
     </conventions>
          <entity name="language_x_translator">
              <field name="language_id" linkReferenceAlias="translating_language" linkToTargetEntity="LANGUAGE"/>
              <field name="user_id" linkReferenceAlias="translator" linkToTargetEntity="USER"/>
          </entity>
          <entity name="LANGUAGE_X_SPEAKER">
              <field name="LANGUAGE_ID" linkToTargetEntity="LANGUAGE"
                  linkToTargetField="IDLANGUAGE" linkReferenceAlias="spoken_language" />
              <field name="user_id" linkReferenceAlias="speaker" linkToTargetEntity="USER"/>
          </entity>
          <entity name="APPLICATION" alias="registered application">
              <field name="TYPE" alias="obedience">
                  <property tag="checkconstraint" alias="application_type">
                      <property name="OPENSOURCE"/>
                      <property name="COPYRIGHT" />
                  </property>
              </field>
          </entity>
          <entity name="LANGUAGE" content-type="reference-data"/>
    </enrichment>
   </business-model>
  </model>
  <targets>
   <target refname="JPA2" fileName="mp-template-config-JPA2.xml"
    outputdir-root="../../dev/latvianjug/output/JPA2" templatedir-root="../../template/framework/jpa">
    <property name="add-querydsl" value="2.1.2"></property>
    <property name="add-jpa2-implementation" value="hibernate"></property>
    <property name="add-cache-implementation" value="ehcache"></property>
   </target>
            <target refname="CACHE-LIB" fileName="mp-template-config-CACHE-LIB.xml"
                templatedir-root="../../template/framework/cache">
            </target>
   <target refname="LIB" fileName="mp-template-config-bsla-LIB-features.xml"
    templatedir-root="../../template/framework/bsla">
   </target>
  </targets>
 </configuration>
</generator-config>

Global conventions

Make database convention java-friendly.

Fix primary key variable

<column-naming-convention type="apply-fix-primary-key-column-name-when-no-ambiguity" 
    default-value="ID"/>
In Language class the variable + getter/setter are related to 'id' althought mapped to 'idlanguage'.
    @Id @Column(name="idlanguage" )
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;

Give readable collection name

When there is no ambiguity (only one foreign key between 2 entities), there is the possibility to have the collection variable made of the name of the linked table plurialized.
<reference-naming-convention 
    type="apply-referenced-alias-when-no-ambiguity" is-to-plurialize="true" />
In TranslationKey
private Set <translation> translations = new HashSet<translation>(); 

Simplify many-to-many relationship

Simplify many-to-many relationship variable when there is an ambiguity. In fact language_x_translator and language_x_speaker both user and language. So it is by default not possible to add 'users' variable to language since there should be 2 'users' variable. And vice-versa for user entity where 'languages' collection variable would be duplicate. This means that by default the variable is ambiguous and its name is a combination of link table foreign key and the other-end table, which gives quite a complexe name to read. The convention below in combination to some enrichment offers the possibility to get simple names declaratively.
<reference-naming-convention type="apply-many-to-many-aliasing" is-to-plurialize="true"/>
Combined with many-to-many tables enrichment
                    <entity name="language_x_translator">
                        <field name="language_id" linkReferenceAlias="translating_language" linkToTargetEntity="LANGUAGE"/>
                        <field name="user_id" linkReferenceAlias="translator" linkToTargetEntity="USER"/>
                    </entity>
                    <entity name="LANGUAGE_X_SPEAKER">
                        <field name="LANGUAGE_ID" linkToTargetEntity="LANGUAGE"
                            linkToTargetField="IDLANGUAGE" linkReferenceAlias="spoken_language" />
                        <field name="user_id" linkReferenceAlias="speaker" linkToTargetEntity="USER"/>
                    </entity>
Gives for User entity
    @ManyToMany
    @JoinTable(name="LANGUAGE_X_SPEAKER", 
        joinColumns=@JoinColumn(name="user_id"), 
        inverseJoinColumns=@JoinColumn(name="language_id") 
    )
    private Set <language> spokenLanguages = new HashSet <Language> ();

    @ManyToMany
    @JoinTable(name="LANGUAGE_X_TRANSLATOR", 
        joinColumns=@JoinColumn(name="user_id"), 
        inverseJoinColumns=@JoinColumn(name="language_id") 
    )
    private Set <language> translatingLanguages = new HashSet <Language> ();
Gives for Language entity
    @ManyToMany
    @JoinTable(name="LANGUAGE_X_SPEAKER", 
        joinColumns=@JoinColumn(name="language_id"), 
        inverseJoinColumns=@JoinColumn(name="user_id") 
    )
    private Set <user> speakers = new HashSet <User> ();

    @ManyToMany
    @JoinTable(name="LANGUAGE_X_TRANSLATOR", 
        joinColumns=@JoinColumn(name="language_id"), 
        inverseJoinColumns=@JoinColumn(name="user_id") 
    )
    private Set <user> translators = new HashSet <User> ();

Local enrichment

Fine grain tuning: Granularity at the level of the entity or attribute.

Modify the name of the entity with alias

<entity name="APPLICATION" alias="registered application" >
Gives
@Entity (name="RegisteredApplication")
@Table (name="application")
public class RegisteredApplication ...

Modify the name of the field with alias

<field name="TYPE" alias="obedience" >
Gives
    @Column(name="type")
    private ApplicationType obedience;

Add enumeration

                        <field name="TYPE" alias="obedience" >
                            <property tag="checkconstraint" alias="application_type">
                                <property name="OPENSOURCE"/>
                                <property name="COPYRIGHT" />
                            </property>
                        </field>
Gives
    @Enumerated (EnumType.STRING)
    @Column(name="type")
    private ApplicationType obedience; 
And a Enum artefact
public enum ApplicationType {

    OPENSOURCE("OPENSOURCE"),
    COPYRIGHT("COPYRIGHT");

    private final String value;
...

Providing content type of an entity

<entity name="LANGUAGE" content-type="reference-data"/>
Gives an entry in ehcache.xml configuration
   <cache
    name="net.sf.mp.demo.tranxy.domain.tranxy.Language"
        maxElementsInMemory="5000"
        eternal="false"
        timeToIdleSeconds="300"
        timeToLiveSeconds="600"
        overflowToDisk="false"
   />

Generation

Same as for demo one:
Put TRANXY-JPA2-2.xml in /mywork/config
Run
>model-generation.cmd TRANXY-JPA2-2.xml

But what happened to my altered code?

It is kept. Your validation annotation are not erased!

With Minuteproject one shot-generation as 'too-much-often' seen is over!
Be ready for continuous-refactoring of your backend!

Build and test

The model has changed and 2 new fields are mandatory to create a Language. The unit test is modified.
package mytest;

import javax.persistence.*;
import javax.validation.*;
import javax.validation.constraints.*;

import static junit.framework.Assert.*;

import org.junit.*;

import net.sf.mp.demo.tranxy.domain.tranxy.*;

public class TranxyTest {

 EntityManagerFactory emf = Persistence.createEntityManagerFactory("tranxy");
 EntityManager em = emf.createEntityManager();
 
 @Test
 public void testLanguage() {
  EntityTransaction tx = em.getTransaction(); 
  tx.begin();
  Language language = new Language();
  language.setCode("FR");
  
  //demo2
  language.setDescription("France");
  language.setLocale("fr");
  
  em.persist(language);
  tx.commit();
 }
 
    @Test
    public void testTooSmallLanguage() {

        try {
      EntityTransaction tx = em.getTransaction(); 
      tx.begin();
      Language language = new Language();
      language.setCode("F");
      em.persist(language);
            fail("Expected ConstraintViolationException wasn't thrown.");
            tx.commit();
        } 
        catch (ConstraintViolationException e) {
            assertEquals(1, e.getConstraintViolations().size());
            ConstraintViolation violation = 
                e.getConstraintViolations().iterator().next();

            assertEquals("code", violation.getPropertyPath().toString());
            assertEquals(
                Size.class, 
                violation.getConstraintDescriptor().getAnnotation().annotationType());
        }
    } 
 
 @Before
 public void clean () {
  EntityTransaction tx = em.getTransaction(); 
  tx.begin();
  Query q = em.createQuery("delete Language");
  int i = q.executeUpdate();
  tx.commit();
 }
}

To build run
>mvn clean package

Integration technics

Two are standard
  • integration by extension
  • integration by overriding
Minuteproject adds a new one
  • Integration by alteration/mutation
  • 3 types of alteration
    • artifact level (exclude for next generation)
    • snippet level
      • added part
      • updatable part

Download

The result can be downloaded on google code minuteproject.