Showing posts with label spring. Show all posts
Showing posts with label spring. Show all posts

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.


Friday, May 18, 2012

Minuteproject reverse-engineering short stories

Latvian JUG Riga presentation abstract

I have been contacted by the Latvian JUG to make a demonstration-oriented presentation over Minuteproject. This page covers the agenda of what I intend to show at Riga.
All the demos will start from scratch i.e. a DB model that is evolving across the presentation.
Main demos will target technologies: JPA2, REST, Spring, CXF, Openxava for reverse-engineering.
A new feature of Minuteproject is introduced: Statement Driven Development with a demostration for REST track.
Eventually a last demo will show how to get instantly a web site with Openxava.

Agenda

  • Minuteproject overview
  • Demo 1
    • Model sample (3 tables)
    •  JPA2 track generation
    • code review
    • write unit test
    • alter and customize generated code (ex: with validation annotations)
  • Enrichment facilities
    • Customisation
    • Declaritive conventions
  • Demo 2
    • Model has changed! (10+ tables)
    • generation & code review (convensions, what happened to your modified code?)
  • Generated-code integration technics
  • RESTIFY your backend
  • Demo 3
    • Add REST track on top of JPA2
    • Generate for REST-CXF/SpringMVC
    • Deploy on tomcat and test
  • SDD - Statement Driven Development
  • Demo 4
    • Add custom statement
    • Generate for REST
    • Deploy on tomcat and test
  • Goodies...
  • Demo 5 
    • Get instantly an Openxava web app
    • Deploy on tomcat and test
  • Extend Minuteproject
    • Add your own templates and tracks
  • Conclusion
  • FAQ

Wednesday, May 16, 2012

Statement driven development WYSIWYG for REST

In my previous article, I mentioned that minuteproject will have statement driven development features.
This page I disclose the potential of SDD while developing REST application.
Regarding the technologies I take:
  • REST with CXF
  • Spring Bean for DAO
  • JPA2 as persistence layer
Original WYSIWYG is revisited into What You STATE Is What You Get.

As fundamentals you need a Statement with is surrounding I/O.
The model against which the statement is applied is important but secundary.

As example for SQL, I take 3 queries:
  • select * from address where latitude between ? and ? and longitude between ? and ? and lcase(city) like ?
  • select * from address where addressid between ? and ?
  • select * from address where lcase(city) like ?
Those queries must be given 
  • a name
  • an input (replace question marks by sample value and provide name)
  • an output (deduce from the metadata recieved after executing the query)
And basically that's it.

Thursday, February 16, 2012

Adding Spring-Security to Openxava

Introduction
The purpose of this article is to see how to integrate Spring Security on top of Openxava standalone application.
Openxava build portlets as well as standalone applications.
When working with portlets, those are deployed on a portal such as Liferay which handles secured access by configuration. Meanwhile while working as standalone application you have to handle this functionality yourself.
This page will illustrate how to add spring security (authentication/authorisation) functionalities. The focus will be put the authorisations aspects since authorisation is often enterprise-environment specific.
To demonstrate the integration, this article will use the minuteproject Lazuly showcase application generated for Openxava.
The first part identifies and explains the actions to undertake.
The second part explains what minuteproject can do to fasten your development by generated a customed spring-security integration for you Openxava application.
Eventually a set of tests will ensure that the resulting application is correctly protected for URL direct access as well as content display.
Furthermore, the integration is technologically non-intruisive. You do not have to change Openxava code for it to work.

Spring-Security Openxava integration
Technical Access
URL access
The url pattern is the following
http://servername:port/applicationcontext/xava/module.jsp?application=appName&module=moduleName
given like that it is hard to protect.
The module and application are passed as parameters.

The URL has to be revisited with
http://servername:port/applicationcontext/applicationPath/module
And the 'parameter' access are banned.

Enabling new URL access
Add a servlet
package net.sf.minuteproject.openxava.web.servlet;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class ModuleHomeServlet extends HttpServlet {
 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  RequestDispatcher dispatcher;
  String [] uri = request.getRequestURI().split("/");
  if (uri.length < 4) {
   dispatcher = request.getRequestDispatcher("/xava/homeMenu.jsp");
  } else {
   dispatcher = request.getRequestDispatcher(
   "/xava/home.jsp?application=" + uri[1] + "&module=" + uri[3]);
  }
  dispatcher.forward(request, response);
 }
 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  doGet(request, response);
 }
}
homeMenu.jsp is a page including a header with menu (to protect and whose menu link URL are correspond to the secured format) and a footer.
Add a servlet configuration
Servlet configuration snippet done in Openxava servlets.xml.
 
 <servlet>
  <servlet-name>moduleHome</servlet-name>
  <servlet-class>net.sf.minuteproject.openxava.web.servlet.ModuleHomeServlet</servlet-class>
 </servlet>
 
 <servlet-mapping>
  <servlet-name>moduleHome</servlet-name>
  <url-pattern>/MenuModules/*</url-pattern>
 </servlet-mapping>
 
This snippet will be package in war web.xml at build time by OpenXava ant script.


Jsp access
Prohibit any Openxava jsp access except the one of the menu

To do that add an spring applicationContext-security.xml in you classpath (ex: Openxava src folder).
<b:beans xmlns="http://www.springframework.org/schema/security"
...
    <http realm="conference Realm">
        <!-- default url -->
  <intercept-url pattern="/xava/homeMenu.jsp" access="ROLE_APPLICATION_USER"/>        
        <intercept-url pattern="/xava/**/*.jsp" access="ROLE_NOT_PRESENT"/>
...
This means that all path after xava will be accessible (ex: css...) safe jsp expect one homeMenu.jsp is available to all registered user (ie having role ROLE_APPLICATION_USER cf attribution at authorisation part further).
Of course ensure that the role ROLE_NOT_PRESENT is really not present in your app.

Business Access
The idea is to give CRUD access on a entity base on role.
Define roles and UC
To be more explicit, I define 3 roles with their scope.
Administrator can administrate ROLE and COUNTRY entities
Application_user can manage all the other conference related tables safe the master data table mentionned above
Reviewer can access to the statistic views but not the administration.
Both reviewer and Administrator can do what Application_user can do.

In applicationContext-security.xml the role can be mapped to specific URLs
<b:beans xmlns="http://www.springframework.org/schema/security"
....
    <http realm="conference Realm">
   <!-- secured country -->
        <intercept-url pattern="/MenuModules/Country" access="ROLE_ADMINISTRATOR"/>
  <!-- secured role -->
        <intercept-url pattern="/MenuModules/Role" access="ROLE_ADMINISTRATOR"/>
 <!-- secured stat_mb_by_role -->
        <intercept-url pattern="/MenuModules/MemberPerRoleCountryAndConference" access="ROLE_REVIEWER"/>
 <!-- secured stat_mb_per_ctry_conf -->
        <intercept-url pattern="/MenuModules/MemberPerCountryAndConference" access="ROLE_REVIEWER"/>
  <intercept-url pattern="/MenuModules/**" access="ROLE_APPLICATION_USER"/>

Impact of the roles access on your model modal navigation
Be coherent
As said before 'the CRUD access on a entity is role based' but the affectation mechanism has to reflect that.
OpenXava has annotation to create an entity from another one. It is then logical that we cannot create entity B from entity A, if we do not have CRUD rights on entity B.
The mechanism will consist in this case of affectation only with search functionalities.
In our scenario it means that a user with 'application_user' only can select a country but can not create any (no create or update icons available).

It is also true at the menu level, a user is entitled to see only its menu items corresponding to its profile.
Here the menu is done in jsp.
To secure the access you can wrap to code to secure with taglib code coming with spring security or add a little taglib such as the following isUserInRole.tag located in web/WEB-INF/tags/common
<%@ attribute name="role" required="true" %>

<%!

    public boolean hasRole(javax.servlet.http.HttpServletRequest request, String role) {
        return request.isUserInRole(role) || 
            request.isUserInRole(role.toUpperCase()) || 
            request.isUserInRole("ROLE_"+role.toUpperCase());
    }
%>

<%
     String [] roles = role.split(",");
     int length = roles.length;
     boolean isInRole = false;
     for (int i = 0; i < length;i++) {
      String role = (roles[i]);
         if(hasRole(request, role)) {
             isInRole = true;
             break;
         }   
     }
    if(isInRole) {
%>
        <jsp:doBody/>
<%        
    }
%>

Wrap the code to protect here the administrator menu and each menu item
<mp:isUserInRole role="administrator">
    <li class="topitem">
      <a href="#" onclick="return false;">
      Administration
      </a>
   <ul class="submenu">
<mp:isUserInRole role="administrator">
        <li><a href="/conference/MenuModules/Country" >Country</a></li>
</mp:isUserInRole>
<mp:isUserInRole role="administrator">
        <li><a href="/conference/MenuModules/Role" >Role</a></li>
</mp:isUserInRole>
   </ul>
 </li>
</mp:isUserInRole> 

Authentication/Authorisation
For the user to operate, he must be authenticated and authorised (moment where his role profile is loaded granting him with business access rights). I use an simple authentication and authorisation based a DB information.
Of course you are not supposed to use that in production ;)
In applicationContext-security.xml add the following snippet.
    <authentication-manager>
      <authentication-provider>
        <jdbc-user-service 
          data-source-ref="dataSource" 
          users-by-username-query="SELECT username,password,active FROM user_authentication WHERE username = ?"
          authorities-by-username-query="SELECT username,role FROM user_authorisation WHERE username = ?" 
          />
      </authentication-provider> 
    </authentication-manager>  
    <b:bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
        <b:property name="jndiName"><b:value>java:comp/env/jdbc/conferenceDS</b:value></b:property>
    </b:bean>  

Both authorisation and authentication queries have to be valid.
Here, they are done on top of views, which means that you have to implement 2 views: user_authentication and user_authorisation.
The datasource is the same as the one of the Openxava application
View gives you flexibility because if you have indirection level of granularity such as (user-role-permission), your view can associate user to role

Authentication flow
Eventually you need to handle an authentication flow composed of
  • welcome page
  • login page
  • access denied page
  • logout link
The flow is handled by applicationContext-security.xml
Add the following snippet.
    <authentication-manager>
    <http realm="conference Realm">

        <intercept-url pattern="/" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
        <intercept-url pattern="/index.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
        <intercept-url pattern="/hello.htm" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
        <intercept-url pattern="/login.jsp*" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
        <form-login login-page="/login.jsp" authentication-failure-url="/login.jsp?login_error=1"/>
        <http-basic/>
        <logout logout-success-url="/index.jsp"/>
        <remember-me />
        <access-denied-handler error-page="/accessDenied.jsp"/> 

Login.jsp is strongly inspired by spring petclinic sample
<%@ taglib uri="http://java.sun.com/jstl/core" prefix="c" %>
<%@ page pageEncoding="UTF-8" %>

<html>
  <head>
    <title>Login</title>
  </head>

  <body onload="document.f.j_username.focus();">
    <h1>Login test</h1>

    <p>Locale is: <%= request.getLocale() %></p>
    <%-- this form-login-page form is also used as the
         form-error-page to ask for a login again.
         --%>
    <c:if test="${ not empty param.login_error}">
      <font color="red">
        Your login attempt was not successful, try again.<br/><br/>
        Reason: <c:out value="${SPRING_SECURITY_LAST_EXCEPTION.message}"/>.
      </font>
    </c:if>   

    <form name="f" action="<c:url value='j_spring_security_check'/>" method="POST">
      <table>
        <tr><td>User:</td><td><input type='text' name='j_username' value='<c:if test="${ not empty param.login_error }"><c:out value="${SPRING_SECURITY_LAST_USERNAME}"/></c:if>'/></td></tr>
        <tr><td>Password:</td><td><input type='password' name='j_password'></td></tr>
        <tr><td><input type="checkbox" name="_spring_security_remember_me"></td><td>Don't ask for my password for two weeks</td></tr>

        <tr><td colspan='2'><input name="submit" type="submit"></td></tr>
        <tr><td colspan='2'><input name="reset" type="reset"></td></tr>
      </table>

    </form>

  </body>
</html>
index.jsp
<html>
  <head>
    <title>Welcome to Conference</title>
  </head>

  <body>
    <h1>Welcome to Conference</h1>

<a href="/conference/xava/homeMenu.jsp">login</a>

  </body>
</html>

accessDenied.jsp
Access denied!

Not to forget a logout functionality here added on the menu

     <span id="logout"><a href="../j_spring_security_logout">Logoff</a></span>    

Spring security dependencies
Add spring security jars into web/WEB-INF/lib

spring-aop-3.0.4.RELEASE.jar
spring-asm-3.0.4.RELEASE.jar
spring-beans-3.0.4.RELEASE.jar
spring-context-3.0.4.RELEASE.jar
spring-core-3.0.4.RELEASE.jar
spring-expression-3.0.4.RELEASE.jar
spring-jdbc-3.0.4.RELEASE.jar
spring-security-acl-2.0.3.jar
spring-security-config-3.1.0.M1.jar
spring-security-core-2.0.3.jar
spring-security-core-3.1.0.M1.jar
spring-security-core-tiger-2.0.3.jar
spring-security-taglibs-2.0.3.jar
spring-security-web-3.1.0.M1.jar
spring-tx-3.0.4.RELEASE.jar
spring-web-3.0.4.RELEASE.jar

Spring security context
Spring security context had been mentioned at different level, here is the complete version
<?xml version="1.0" encoding="UTF-8"?> 

<b:beans xmlns="http://www.springframework.org/schema/security" 
    xmlns:b="http://www.springframework.org/schema/beans" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd">

    <authentication-manager> 
      <authentication-provider> 
        <jdbc-user-service 
          data-source-ref="dataSource" 
          users-by-username-query="SELECT username,password,active FROM user_authentication WHERE username = ?" 
          authorities-by-username-query="SELECT username,role FROM user_authorisation WHERE username = ?" 
          /> 
      </authentication-provider> 
    </authentication-manager>  
    
    <http realm="conference Realm">

        <intercept-url pattern="/" access="IS_AUTHENTICATED_ANONYMOUSLY"/> 
        <intercept-url pattern="/index.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY"/> 
        <intercept-url pattern="/hello.htm" access="IS_AUTHENTICATED_ANONYMOUSLY"/> 
        <intercept-url pattern="/login.jsp*" access="IS_AUTHENTICATED_ANONYMOUSLY"/>

        <!-- default url --> 

                <intercept-url pattern="/xava/homeMenu.jsp" access="ROLE_APPLICATION_USER"/>        
        <intercept-url pattern="/xava/**/*.jsp" access="ROLE_NOT_PRESENT"/>  
                
                <!-- secured country --> 
        <intercept-url pattern="/MenuModules/Country" access="ROLE_ADMINISTRATOR"/> 
                <!-- secured role --> 
        <intercept-url pattern="/MenuModules/Role" access="ROLE_ADMINISTRATOR"/> 
        <!-- secured stat_mb_by_role --> 
        <intercept-url pattern="/MenuModules/MemberPerRoleCountryAndConference" access="ROLE_REVIEWER"/> 
        <!-- secured stat_mb_per_ctry_conf --> 
        <intercept-url pattern="/MenuModules/MemberPerCountryAndConference" access="ROLE_REVIEWER"/>

        <intercept-url pattern="/MenuModules/**" access="ROLE_APPLICATION_USER"/> 
                
        <form-login login-page="/login.jsp" authentication-failure-url="/login.jsp?login_error=1"/> 
        <http-basic/> 
        <logout logout-success-url="/index.jsp"/> 
        <remember-me /> 
        <access-denied-handler error-page="/accessDenied.jsp"/> 
    </http>

    <b:bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean"> 
        <b:property name="jndiName"><b:value>java:comp/env/jdbc/conferenceDS</b:value></b:property>

    </b:bean>    
         
</b:beans>

Reference the context
Openxava listeners.xml is the place where you can set web.xml-snippets to be package in web.xml at Openxava build time
Add the following snippet
 <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
     
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath:applicationContext-security.xml
        </param-value>
    </context-param> 
 
 <filter>
   <filter-name>springSecurityFilterChain</filter-name>
   <filter-class>
     org.springframework.web.filter.DelegatingFilterProxy
   </filter-class>
 </filter>
 
 <filter-mapping>
   <filter-name>springSecurityFilterChain</filter-name>
   <url-pattern>/*</url-pattern>
 </filter-mapping>

The minuteproject way 
Doing the integration can be time consuming. As you can notice there is some effort to have the code compliant for a webapp here Openxava to be bodyguard by Spring-Security.
Meanwhile when dealing with data centric application, this knowledge can be crystalized to be instantly available.
Because...there is an underlying concept that guides our choice and lead to best pratices.
It is one thing to execute them, it is another to state it.
The question is how do we specify which entity to access and to which role. The idea is to express with simplicity the relationship between role or permission and action.
In our case the actions are :
  • a full CRUD
  • an affectation mechanism
The full CRUD is associated to a specific role.
The affection (linkage of an entity from another by search) is when to entities are linked but not all the role of the main entities are the same as the roles of the target. Otherwise affection goes with creation and update.

And the roles are:
  • Administrator
  • Application_user
  • Reviewer
Now it is time for a primary school exercice
If you represent an entity-relationship diagram, you should see boxes and links. Boxes for entities and links for relationships.
Give each role/permission a color.
Paint all the boxes that are full CRUD with the corresponding role color... Yes, you may paint the same box twice (resulting is color combination).
The result gives you the Color access spectrum of your DB.
Of course, we can further decline the gradient with other function (read-only, controller specific...)
But the underlying idea is evident.

What Minuteproject allows you to do it by enriching your model with this color spectrum at the entity level or at the package level. This enables you to work with concept only closed to UC agnostic of technology implementations.

Minuteproject configuration snippet
<package name="admin" alias="Administration">
 <security-color roles="administrator" />
</package>
<package name="statistics">
 <security-color roles="reviewer" />
</package>

Generation
Minuteproject configuration full
The configuration is similar to lazuly show case enhanced with security aspects
<!DOCTYPE root>
<generator-config>
 <configuration>
  <model name="conference" 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/conference</url>
     <username>root</username>
     <password>mysql</password>
    </dataSource>
    <!-- for Oracle and DB2 please set the schema <schema> </schema> -->
    <primaryKeyPolicy oneGlobal="true">
     <primaryKeyPolicyPattern name="autoincrementPattern"></primaryKeyPolicyPattern>
    </primaryKeyPolicy>
   </data-model>
   <business-model>
    <generation-condition>
     <condition type="exclude" startsWith="user_"></condition>
    </generation-condition>
    <business-package default="conference">
     <condition type="package" startsWith="STAT" result="statistics"></condition>
     <condition type="package" startsWith="COUNTRY" result="admin"></condition>
     <condition type="package" startsWith="ROLE" result="admin"></condition>
    </business-package>
    <enrichment>
     <conventions>
      <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>
     <package name="admin" alias="Administration">
      <security-color roles="administrator" />
     </package>
     <package name="statistics">
      <security-color roles="reviewer" />
     </package>
     <entity name="COUNTRY" content-type="reference-data">
      <semantic-reference>
       <sql-path path="NAME" />
      </semantic-reference>
     </entity>
     <entity name="CONFERENCE_MEMBER">
      <semantic-reference>
       <sql-path path="FIRST_NAME" />
       <sql-path path="LAST_NAME" />
      </semantic-reference>
      <field name="STATUS">
       <property tag="checkconstraint" alias="conference_member_status">
        <property name="PENDING" value="PENDING" />
        <property name="ACTIVE" value="ACTIVE" />
       </property>
      </field>
      <field name="EMAIL">
       <stereotype stereotype="EMAIL" />
      </field>
     </entity>
     <entity name="SPEAKER">
      <field name="BIO">
       <stereotype stereotype="HTML_TEXT" />
      </field>
      <field name="PHOTO">
       <stereotype stereotype="PHOTO" />
      </field>
      <field name="WEB_SITE_URL">
       <stereotype stereotype="WEBURL" />
      </field>
     </entity>
     <entity name="PRESENTATION">
      <field name="STATUS">
       <property tag="checkconstraint" alias="presentation_status">
        <property name="PROPOSAL" value="PROPOSAL" />
        <property name="ACTIVE" value="ACTIVE" />
       </property>
      </field>
     </entity>
     <entity name="SPONSOR">
      <field name="STATUS">
       <property tag="checkconstraint" alias="sponsor_status">
        <property name="PENDING" value="PENDING" />
        <property name="ACTIVE" value="ACTIVE" />
       </property>
      </field>
      <field name="PRIVILEGE_TYPE">
       <property tag="checkconstraint" alias="sponsor_privilege">
        <property name="GOLDEN" value="Golden" />
        <property name="SILVER" value="Silver" />
        <property name="BRONZE" value="Bronze" />
       </property>
      </field>
     </entity>
     <!-- views -->
     <entity name="stat_mb_per_ctry_conf" alias="MEMBER_PER_COUNTRY_AND_CONFERENCE">
      <virtual-primary-key isRealPrimaryKey="true">
       <property name="virtualPrimaryKey" value="ID" />
      </virtual-primary-key>
     </entity>
     <entity name="stat_mb_by_role" alias="MEMBER_PER_ROLE_COUNTRY_AND_CONFERENCE">
      <virtual-primary-key isRealPrimaryKey="true">
       <property name="virtualPrimaryKey" value="id" />
      </virtual-primary-key>
      <field name="stat_mb_per_ctry_conf_ID" linkToTargetEntity="stat_mb_per_ctry_conf"
       linkToTargetField="id"></field>
     </entity>
    </enrichment>
   </business-model>
  </model>
  <targets>
   <!-- openxava -->
   <target refname="OpenXava" name="OpenXava"
    fileName="mp-template-config-openxava-last-features.xml"
    outputdir-root="../../DEV/output/openxava-springsecurity/conference"
    templatedir-root="../../template/framework/openxava">
    <property name="add-spring-security" value="true" />
   </target>

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

   <target refname="springsecurity" name="springsecurity"
    fileName="mp-template-config-spring-security.xml" 
                                outputdir-root="../../DEV/output/openxava-springsecurity/conference"
    templatedir-root="../../template/framework/security/spring">
   </target>

   <target refname="JPA2-LIB" fileName="mp-template-config-JPA2-LIB.xml"
    templatedir-root="../../template/framework/jpa">
   </target>

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

  </targets>
 </configuration>
</generator-config>
The main points are
  • exclude entities starting with user_ (i.e. the security entity used by spring configuration)
  • add security access on package level
    • package admin is accessible by role administrator only
    • package statistics is accessible by role reviewer only
    • default package (conference) is accessible by any application_user 
  • add spring-security track in the target
  • add reference in openxava to spring-security
The track springsecurity holding the configuration is not yet bundled in minuteproject release 0.8 but will be present for 0.8.1+.

Set up Database
Implement the views
Here a very dummy implementation.
create view user_authentication as
select 
email as username,
first_name as password,
'1' as active
from 
conference_member
;
create view user_authorisation as
select cm.email as username, r.name as role 
from conference_member cm, role r, member_role mr
where mr.role_id = r.id
and mr.conference_member_id = cm.id
union
select cm.email as username, concat('ROLE_',r.name) as role 
from conference_member cm, role r, member_role mr
where mr.role_id = r.id
and mr.conference_member_id = cm.id
;
As you can not there is a little redundancy in the user_authentication view, since sometimes the role administrator is refered sometimes role_administrator. This will be homogenized in next release.
Add some default value
Here a very dummy implementation.
INSERT INTO country (id, name, iso_name) VALUES (-1, 'France', 'FR');
INSERT INTO address (id, street1, street2, country_id) VALUES(-1, 'rue 1', 'rue 2', -1);
INSERT INTO  conference_member (id, conference_id, first_name, last_name, email, address_id, status )
    VALUES  (-1, -1, 'f', 'a', 'fa@test.com', -1, 'ACTIVE' );
INSERT INTO role (id, name) VALUES (-1, 'ADMINSTRATOR' );  
INSERT INTO role (id, name) VALUES (-2, 'ROLE_APPLICATION_USER' );
INSERT INTO member_role (conference_member_id, role_id) VALUES (-1, -1);  
INSERT INTO member_role (conference_member_id, role_id) VALUES (-1, -2);
So when user fa@test.com connects he will get the role Administrator which allows him to access the administrator menu and create a new role called 'REVIEWER'. He can also create a new conference member and associate with the role 'REVIEWER'.

Set up Application
Download the lazuly-openxava-springsecurity minuteproject configuration from google code minuteproject.
Copy file into /mywork/config

Execute
In /mywork/config: model-generation.cmd mp-config-LAZULY-Openxava-with-spring-security.xml
The generated code goes to /DEV/output/openxava-springsecurity/conference

Packaging
Here the packaging/deployment is a 2 steps exercices (unfortunately):
  • there is no more the start-tomcat/stop-tomcat command in OX distribution
  • spring dependencies are not included
Steps
  • Check that Openxava 4.3 is available, and OX_HOME is set to Openxava 4.3
  • from /DEV/output/openxava-springsecurity/conference run build-conference(.cmd/sh). This will trigger the build that is successful but not the deployment due to information before.
  • Open the project generated by the build in Openxava workspace
  • Add Spring security dependencies
  • Start tomcat server (remark: The Datasource for the application is present in tomcat/config/context.xml)
  • Deploy
  • Enjoy
Testing 

Welcome page
Default URL at context root of the application.


Login page













Any other direct called where the user is not authenticated will be intercepted and routed to this page

Contextual Menu
The user have access to the admin and conference part not the statistics.












The URLs have been modified. When the user tries to access the standard OX style URL he recieves an
access denied (ex: module.jsp)








Add role reviewer


















Add user
Affect user with role reviewer and default (application_user)
Logoff 
(click logoff)

Login as Reviewer
On login page enter username=bc@test.com and password=b
In the contextual menu you do see the 'admin' package'


And you get an access deny when manipulating directly the URL



 Now the application is secured.

Conclusion
This article showed the configuration and manipulation to integrate spring security with openxava in a non-intrusive manner.
It stressed a new concept 'DB color access spectrum' and how to densify the security information in minuteproject configuration.
DB color access spectrum is a concept which ask only to be extended:
  • Ad-hoc functions, controllers
  • Store procedures
It is simple to express and analyst friendly.
It is not bound to a technology.
It is a step in easily defining fine grain access, its combination with profile based access and state based access (to do manually... for the moment ;)) could pave the way to intuitive and implicit workflows instead of heavy BPM solutions.