Showing posts with label jpa2. Show all posts
Showing posts with label jpa2. Show all posts

Monday, January 6, 2014

how-to cloudbees minuteproject 4 openxava

Minuteproject releases a demo Openxava application to be hosted on cloudbees at http://petshopox.minuteproject.cloudbees.net/

Following those steps, you can recreate an Openxava application with minuteproject and host it on cloudbees.
All those steps are IDE-free (i.e. productivity oriented) all is done by command line!
This is a requirement for automation, since it is very easy to integrate with a CI tool such as Jenkins.
Meanwhile the code is compatible with Eclipse (for java), Mysql Workbench (for DB design + release) and Cloudbees website for manual release.

Principles

Generate an openxava application with minuteproject base on an DB structure.
The database use here is petshop; for the generation the database can be local or already hosted on cloudbees PaaS. At running the database is the one of the cloudbees PaaS.

The generated Openxava compliant artifacts are built into an Openxava application.
The application is to be deployed on a tomcat server.
The application relies on a JNDI Datasource on the tomcat server.

After setting the database on cloudbees and the connection pool cloudbees tomcat server, the application can be deployed.

Ingredients

Infrastructure

JDK 1.6+
Minuteproject last version
Openxava 4.9
Mysql DB
Tomcat
Cloudbees

  • sdk
  • account

Set up Cloudbees

Database

Create a mysql db schema on cloudbees
bees db:create petshopDB
   
Retrieve the DB info (connection info: server, port, user...) all you need to create you connection pool

bees app:info -db petshopDB 


Application

Create application

bees app:create -petshopox
     

Datasource

This is use get the alias use in the persistence.xml datasource (java:comp/env/jdbc/petshopDS)

bees app:bind -db petshopDB -a petshopox -as petshopDS 


Database setup

Run you database script from the information receive via $bees db:info -db petshopDB

Sample
mysql --host=ec2-50-19-213-178.compute-1.amazonaws.com --user=xxxx --password=xxxx --port=3306 xxx < petshop.sql
petshop-mysql.sql can be found in MP_HOME/sample/schema/

Generation

Full details at http://minuteproject.wikispaces.com/OpenXava
The generation is done by Minuteproject.
Minuteproject generation by command line comes by passing a configuration file name to model-generation.

Sample
model-generation petshop.xml

Sample petshop.xml configuration
<!DOCTYPE root>
<generator-config xmlns="http://minuteproject.sf.net/xsd/mp-config"
	xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
	xs:noNamespaceSchemaLocation="mp-config.xsd">
	<!-- adapted for cloudbees petshop model -->
	<configuration>
		<conventions>
			<target-convention type="enable-updatable-code-feature" />
		</conventions>
		<model name="petshop" 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://localhost:3306/petshop</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="DUAL"></condition>
					<condition type="exclude" startsWith="ID_GEN"></condition>
					<condition type="exclude" startsWith="SEQUENCE"></condition>
				</generation-condition>
				<business-package default="pet">
					<condition type="package" startsWith="PRODUCT" result="product"></condition>
					<condition type="package" startsWith="ITEM" result="product"></condition>
				</business-package>
				<enrichment>
					<conventions>
						<entity-naming-convention type="apply-strip-table-name-prefix"
							pattern-to-strip="SYS,FIN" />
						<reference-naming-convention
							type="apply-referenced-alias-when-no-ambiguity" is-to-plurialize="true"></reference-naming-convention>
					</conventions>
					<package name="product">
						<entity-group entities="PRODUCT"></entity-group>
						<entity-group entities="ITEM"></entity-group>
					</package>
					<entity name="PRODUCT" alias="MY_GOOD_PRODUCT">
					</entity>
					<entity name="ITEM" alias="MY_GOOD_ITEM" comment="my item table">
						<field name="PRODUCTID" alias="THIS_IS_MY_PRODUCT" comment="my product field reference"></field>
					</entity>
					<entity name="CATEGORY" content-type="reference-data">
						<field name="DESCRIPTION" ordering="asc" label="my description"
							is-searchable="true"></field>
						<field name="NAME" ordering="asc"></field>
					</entity>
					<entity name="TAG" content-type="reference-data">
						<field name="TAG" ordering="asc">
							<semantic-reference>
								<sql-path path="NAME" />
							</semantic-reference>
						</field>
					</entity>
				</enrichment>
			</business-model>
			<statement-model>
				<enrichment>
					<conventions>
						<sdd-dummy-primarykey-convention add="true" />
					</conventions>
				</enrichment>
				<queries>
					<query name="get address abstract" id="dashAddress" type="dashboard"
						category="pie-chart">
						<query-body> <!-- dimensions column first -->
							<value>
<![CDATA[select city, count(*) as nb from address group by city order by count(*) desc limit ?]]>
                            </value>
						</query-body>
						<query-params>
							<query-param name="top city" is-mandatory="false"
								type="INT" sample="37" default="10"></query-param>
						</query-params>
					</query>
					<query name="get address summary" id="dashCity" type="dashboard"
						category="bar-chart">
						<query-body> <!-- dimensions column first -->
							<value>
<![CDATA[select city, count(*) as nb, count(*) as nb2 from address group by city order by count(*) desc]]>
                            </value>
						</query-body>
					</query>
					<query name="get addresses by criteria" id="c">
						<query-body>
							<value>
<![CDATA[select * from address where lcase(city) like ?]]>
                            </value>
						</query-body>
						<query-params>
							<query-param name="city" type="STRING" sample="'S'"
								convert="lowercase,append%" default="%">
							</query-param>
						</query-params>
					</query>
				</queries>
			</statement-model>
		</model>
		<targets catalog-entry="OpenXava" >
			<property name="environment" value="remote"></property>
			<property name="cloud-platform" value="cloudbees"></property>
			<property name="embed-driver" value="true"></property>
		</targets>
	</configuration>
</generator-config>

Appart from the 'classical' enrichment facilities, the interesting point are the properties under the targets node:
name="environment" value="remote" 
Implies to use a connection pool (for persistence.xml)
name="cloud-platform" value="cloudbees" 
Performs additional build facilities for Openxava on cloudbees
The OX war has to hold jta.jar, ejb.jar, mysql driver jar.
  • jta.jar and ejb.jar are embedded in tomcat/lib on the OX delivery.
name="embed-driver" value="true" 

  • mysql driver jar is shipped on MP delivery

  • Remark: the last 2 options are available on Minuteproject 0.8.6

    Build

    On the generated directory
    set/export OX_HOME
    set/export MP_HOME
    Run

    build-petshop.cmd/sh

    The application petshop.war goes into OX_HOME/workspace/petshop.dist

    The application is ready to be deployed on Cloudbees.

    Deploy

    bees app:deploy -a petshopox -t tomcat7 -Rjava_version=1.7 petshop.war

    petshopox is the name of the application on cloudbees

    Enjoy

    Monday, August 19, 2013

    Northwind DB revisited with MP 4 OX

    Minuteproject (0.8.4) now supports ms-sqlserver.
    It has been tested with MS Northwind DB on sqlserver 2014.
    Here are the steps to follow to get an OpenXava application from the northwind DB.

    Goal:
    Get a working Northwind Openxava application in couple of seconds.
    For hungry minds please find the resulting code on googlecode.

    Northwind DB

    Northwind database is a sample DB provided by Microsoft.
    It can be found at http://northwinddatabase.codeplex.com/releases/view/71634

    The sql provided to create the schema has DB objects (tables/views) containing space.
    This sql has been revisited to remove those spaces a version can be found here.

    • Install SQLServer
    • Create an account
    • Run the script.

    MinuteProject with SQLSERVER

    Minuteproject
    • uses com.microsoft.sqlserver.jdbc.SQLServerDriver jdbc driver
    • associates 'identity' as a primary key strategy by default on the console
    • associates org.hibernate.dialect.SQLServer2008Dialect for hibernate dialect
    • proposes maven artifact-id=sqljdbc4; group-id=com.microsoft.sqlserver; version=4.0 for pom configuration
    Furthermore when retrieving the metadata of your model, set 'dbo' of the schema node.

    Minuteproject configuration



    <!DOCTYPE root>
    <generator-config>
     <configuration>
      <model name="nortewind" version="1.0" package-root="net.sf.mp.demo">
       <data-model>
        <driver name="sqlserver" version="4.0" groupId="com.microsoft.sqlserver"
         artifactId="sqljdbc4"></driver>
        <dataSource>
         <driverClassName>com.microsoft.sqlserver.jdbc.SQLServerDriver</driverClassName>
         <url>jdbc:sqlserver://localhost:1433;databaseName=northwind</url>
         <username>sqlserver</username>
         <password>xxxxxxxx</password>
        </dataSource>
        <!-- for Oracle and DB2 please set the schema <schema> </schema> -->
        <schema>dbo</schema>
        <primaryKeyPolicy oneGlobal="true">
         <primaryKeyPolicyPattern name="identityPattern"></primaryKeyPolicyPattern>
        </primaryKeyPolicy>
       </data-model>
       <business-model>
        <business-package default="business">
            <condition type="package" database-object-type="VIEW" result="review"></condition>   
        </business-package>
        <enrichment>
         <conventions>
              <view-primary-key-convention 
                type="apply-default-primary-key-otherwise-first-one" 
                default-primary-key-names="ID" /> 
             <column-naming-convention
           type="apply-fix-primary-key-column-name-when-no-ambiguity-and-not-natural"
           default-value="ID" />
             <entity-naming-convention type="apply-field-alias-based-on-camelcase"/>
             <column-naming-convention type="apply-field-alias-based-on-camelcase"/>
    
          <reference-naming-convention
           type="apply-referenced-alias-when-no-ambiguity" is-to-plurialize="false" />
          <reference-naming-convention type="apply-many-to-many-aliasing" is-to-plurialize="false"/>
         </conventions>
        </enrichment>
       </business-model>
    
      </model>
    <!-- -->    <targets catalog-entry="OpenXava">
      </targets>  
    <!--    <targets catalog-entry="JPA2" >
      </targets>   -->
     </configuration>
    </generator-config>
    


    This configuration will allow you to

    • retrieve sqlserver information for the model northwind (do not forget to specify 'dbo' in schema)
    • assign identity as primary key strategy
    • separate table package from view package. (tables go in package business, views in package review)
    • apply convention
      • use camel case for field and entity (table/view)
      • associate a primary key if not present. The primary key would then be attribute to the field ID if present otherwise the first found.
      • have clean name (here not plurialized because northwind already use plural in DB object name) when possible (i.e. there is no variable name collisition - this occurs when you have more than one relationship between two objects).
    • use the track OpenXava from the catalog.

    Steps

    Copy this configuration to /mywork/config as northwind-OX.xml 
    From a command line run model-generation.cmd/sh northwind-OX.xml 
    The result will go in /mywork/output/nortewind/OpenXava

    Open a prompt in /mywork/output/nortewind/OpenXava
    set OX_HOME and MP_HOME (where you install Openxava and Minuteproject)
    • set OX_HOME= path-to-Openxava // export OX_HOME in linux
    • set MP_HOME= path-to-Minuteproject // export MP_HOME in linux

    Run build-nortewind.cmd/sh

    This should be enough to get a

    Enjoy!

    Screenshots

    You have Openxava CRUD on all tables and selection on views

    Menu entries


    Select Order

     Region details
    List of products (view)

    MinuteProject with console

    The code can also be generated with Minuteproject console, it is faster since you do not have to write any configuration. Meanwhile it is more limited since not all the convention are available on the console.

    click /bin/start-console.sh/cmd
    apply the following parameters



    Click on generate and the output will go to /output/northwind/Openxava

    You can them process with the same steps as with the configuration.


    Conclusion

    Minuteproject 0.8.4 offers sqlserver generation and simplifies the configuration of the tracks.
    Feel free to pick up others by picking them from the drop down list or by changing the attribute catalog-entry of the node targets by one of the following value:

    JPA2
    JPA2-ABL
    BSLA-JPA2
    REST-JEE
    REST-SpringMVC
    REST-CXF-Spring
    REST-CXF-JEE
    WS-JEE
    JOOQ
    Primefaces-Spring
    Primefaces-JEE
    FitNesse
    Solr
    OpenXava
    Grails
    Play
    Vaadin
    Roo
    Maven Spring Hibernate
    Maven Spring JPA/Hibernate
    SpringService

    Remark some tracks are under construction.



    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




    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.







    RigaJUG - Demo 1 - JPA2

    First Demo of RigaJUG agenda.
    This demo presents the track JPA2 of minuteproject. 

    Starting from a simple model that holds translation information.
    Minuteproject will generate a JPA2 layer.

    This demo will illustrate:
    • Generation by console or via config file
    • Altering generated code to add JSR 303 (validation) annotation.
    • Writing a unit test
    • Enrichment facilities and customisation
    Prerequisits
    • Download Minuteproject last version
    • Java 6 in path
    • Maven in path
    • Install model on myql
    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` ;
    
    -- -----------------------------------------------------
    -- Table `tranxy`.`translation_key`
    -- -----------------------------------------------------
    DROP TABLE IF EXISTS `tranxy`.`translation_key` ;
    
    CREATE  TABLE IF NOT EXISTS `tranxy`.`translation_key` (
      `id` BIGINT(20) NOT NULL AUTO_INCREMENT ,
      `key_name` VARCHAR(200) NULL DEFAULT NULL ,
      `description` VARCHAR(400) NULL DEFAULT NULL ,
      PRIMARY KEY (`id`) )
    ENGINE = InnoDB
    DEFAULT CHARACTER SET = latin1;
    
    
    -- -----------------------------------------------------
    -- Table `tranxy`.`language`
    -- -----------------------------------------------------
    DROP TABLE IF EXISTS `tranxy`.`language` ;
    
    CREATE  TABLE IF NOT EXISTS `tranxy`.`language` (
      `idlanguage` INT(11) NOT NULL AUTO_INCREMENT ,
      `code` VARCHAR(45) NOT NULL ,
      PRIMARY KEY (`idlanguage`) ,
      UNIQUE INDEX `code_UNIQUE` (`code` ASC) )
    ENGINE = InnoDB
    DEFAULT CHARACTER SET = latin1;
    
    
    -- -----------------------------------------------------
    -- Table `tranxy`.`traduction`
    -- -----------------------------------------------------
    DROP TABLE IF EXISTS `tranxy`.`traduction` ;
    
    CREATE  TABLE IF NOT EXISTS `tranxy`.`traduction` (
      `id` BIGINT(20) NOT NULL AUTO_INCREMENT ,
      `translation` VARCHAR(800) NULL DEFAULT NULL ,
      `language_id` INT(11) NOT NULL ,
      `Key_id` BIGINT(20) NOT NULL ,
      PRIMARY KEY (`id`) ,
      INDEX `fk_traduction_language` (`language_id` ASC) ,
      INDEX `fk_traduction_Key1` (`Key_id` ASC) ,
      CONSTRAINT `fk_traduction_Key1`
        FOREIGN KEY (`Key_id` )
        REFERENCES `tranxy`.`translation_key` (`id` )
        ON DELETE NO ACTION
        ON UPDATE NO ACTION,
      CONSTRAINT `fk_traduction_language`
        FOREIGN KEY (`language_id` )
        REFERENCES `tranxy`.`language` (`idlanguage` )
        ON DELETE NO ACTION
        ON UPDATE NO ACTION)
    ENGINE = InnoDB
    DEFAULT CHARACTER SET = latin1;
    
    
    
    SET SQL_MODE=@OLD_SQL_MODE;
    SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;
    SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;
    
    

    Console generation

    By console

    In /application run
    >start-console.cmd/sh
    Fill the 'Data model reverse-engineering' tab
    And click on generate

    The output goes in /output/trans/JPA2
    To build the resulting package execute
    >mvn clean package

    By command line

    Add configuration in /mywork/config
    TRANXY-JPA2-1.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-strip-column-name-suffix"
           pattern-to-strip="ID" />
          <reference-naming-convention
           type="apply-referenced-alias-when-no-ambiguity" is-to-plurialize="true" />
         </conventions>
        </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="environment" value="remote"></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>
    

    Execute by running:
    > model-generation.cmd/sh TRANXY-JPA2-1.xml
    The ouput goes in /dev/latvianjug/output/JPA2

    Make a build:
    >mvn clean package

    Console vs. Command line generation

    The generation made by the command line has more enrichment facilities than just working via the console.
    Example: here the configuration add querydsl integration.
    The rest of the demo will focus on the configuration enrichment facilities.
    Remark:
    Ideally the console should move into a IDE plugin manipulating the configuration.

    Resulting artefacts

    Summary
    • A maven pom project
    • 3 JPA2 entities (one for each table)
    Pom artefact
    • provide a jar with name and version number given in configuration
    • has hibernate, querydsl, mysql driver, junit dependencies
    JPA2 entities
    • packaged logically entity starting with 'trans' goes to package translation other goes to 'tranxy' package
    • convention 'apply-strip-column-name-suffix' when ending with 'ID'
      • DB naming convention used (the foreign key name is composed of the name of the link entity + '_id' is converted into java by provided name stripped from the 'Id' particule.
    • primary key strategy:
      • Based on auto increment pk when not natural.
    JPA2 metamodel
    • each entity is associated to a metamodel java file to build type safe criteria queries.
    persistence.xml: 2 are generated.
    MinuteProject artefacts are designed to be tested and ready to be deployed!
    • in src/main/resources/META-INF
      • with reference to a JTA datasource
    • in src/test/resources/META-INF
      • with reference to an embedded Connection pool
    Global convention
    All artefacts have an updatable nature, meaning that you can change the code or add new code and consecutive generation will keep your modifications.

    Code

    Translation key
    /**
     * 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 : DomainEntityJPA2Annotation
     * - file name : DomainEntityJPA2Annotation.vm
    */
    package net.sf.mp.demo.tranxy.domain.translation;
    
    //MP-MANAGED-ADDED-AREA-BEGINNING @import@
    //MP-MANAGED-ADDED-AREA-ENDING @import@
    import java.sql.*;
    import java.util.Date;
    import java.util.List;
    import java.util.ArrayList;
    import java.util.Set;
    import java.util.HashSet;
    
    import java.io.Serializable;
    import javax.persistence.*;
    import net.sf.mp.demo.tranxy.domain.tranxy.Traduction;
    
    /**
     *
     * <p>Title: TranslationKey</p>
     *
     * <p>Description: Domain Object describing a TranslationKey entity</p>
     *
     */
    @Entity (name="TranslationKey")
    @Table (name="translation_key")
    @NamedQueries({
      @NamedQuery(name="TranslationKey.findAll", query="SELECT translationKey FROM TranslationKey translationKey")
     ,@NamedQuery(name="TranslationKey.findByKeyName", query="SELECT translationKey FROM TranslationKey translationKey WHERE translationKey.keyName = :keyName")
     ,@NamedQuery(name="TranslationKey.findByKeyNameContaining", query="SELECT translationKey FROM TranslationKey translationKey WHERE translationKey.keyName like :keyName")
     ,@NamedQuery(name="TranslationKey.findByDescription", query="SELECT translationKey FROM TranslationKey translationKey WHERE translationKey.description = :description")
     ,@NamedQuery(name="TranslationKey.findByDescriptionContaining", query="SELECT translationKey FROM TranslationKey translationKey WHERE translationKey.description like :description")
    })
    public class TranslationKey implements Serializable {
        private static final long serialVersionUID = 1L;
     
        public static final String FIND_ALL = "TranslationKey.findAll";
        public static final String FIND_BY_KEYNAME = "TranslationKey.findByKeyName";
        public static final String FIND_BY_KEYNAME_CONTAINING ="TranslationKey.findByKeyNameContaining";
        public static final String FIND_BY_DESCRIPTION = "TranslationKey.findByDescription";
        public static final String FIND_BY_DESCRIPTION_CONTAINING ="TranslationKey.findByDescriptionContaining";
     
        @Id @Column(name="id" )
        @GeneratedValue(strategy = GenerationType.AUTO)
        private Long id;
    
    //MP-MANAGED-ADDED-AREA-BEGINNING @key_name-field-annotation@
    //MP-MANAGED-ADDED-AREA-ENDING @key_name-field-annotation@
    //MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @ATTRIBUTE-key_name@
        @Column(name="key_name",  length=200,  nullable=true,  unique=false)
        private String keyName; 
    //MP-MANAGED-UPDATABLE-ENDING
    
    //MP-MANAGED-ADDED-AREA-BEGINNING @description-field-annotation@
    //MP-MANAGED-ADDED-AREA-ENDING @description-field-annotation@
    //MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @ATTRIBUTE-description@
        @Column(name="description",  length=400,  nullable=true,  unique=false)
        private String description; 
    //MP-MANAGED-UPDATABLE-ENDING
    
    //MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @traductions-field-translation_key@
        @OneToMany (targetEntity=net.sf.mp.demo.tranxy.domain.tranxy.Traduction.class, fetch=FetchType.LAZY, mappedBy="key", cascade=CascadeType.REMOVE)//, cascade=CascadeType.ALL)
        private Set <Traduction> traductions = new HashSet<Traduction>(); 
    
    //MP-MANAGED-UPDATABLE-ENDING
        /**
        * Default constructor
        */
        public TranslationKey() {
        }
    
     /**
     * All field constructor 
     */
        public TranslationKey(
           Long id,
           String keyName,
           String description) {
           //primary keys
           setId (id);
           //attributes
           setKeyName (keyName);
           setDescription (description);
           //parents
        }
    
     public TranslationKey flat() {
        return new TranslationKey(
              getId(),
              getKeyName(),
              getDescription()
        );
     }
    
        public Long getId() {
            return id;
        }
     
        public void setId (Long id) {
            this.id =  id;
        }
        
    //MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @GETTER-SETTER-key_name@
        public String getKeyName() {
            return keyName;
        }
     
        public void setKeyName (String keyName) {
            this.keyName =  keyName;
        }    
    //MP-MANAGED-UPDATABLE-ENDING
    
    //MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @GETTER-SETTER-description@
        public String getDescription() {
            return description;
        }
     
        public void setDescription (String description) {
            this.description =  description;
        }    
    //MP-MANAGED-UPDATABLE-ENDING
    
    
    
    //MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @traductions-getter-translation_key@
        public Set<Traduction> getTraductions() {
            if (traductions == null){
                traductions = new HashSet<Traduction>();
            }
            return traductions;
        }
    
        public void setTraductions (Set<Traduction> traductions) {
            this.traductions = traductions;
        } 
        
        public void addTraductions (Traduction traduction) {
             getTraductions().add(traduction);
        }
        
    //MP-MANAGED-UPDATABLE-ENDING
    
    
    //MP-MANAGED-ADDED-AREA-BEGINNING @implementation@
    //MP-MANAGED-ADDED-AREA-ENDING @implementation@
    
    }
    
    Language
    /**
     * 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 : DomainEntityJPA2Annotation
     * - file name : DomainEntityJPA2Annotation.vm
    */
    package net.sf.mp.demo.tranxy.domain.tranxy;
    
    //MP-MANAGED-ADDED-AREA-BEGINNING @import@
    //MP-MANAGED-ADDED-AREA-ENDING @import@
    import java.sql.*;
    import java.util.Date;
    import java.util.List;
    import java.util.ArrayList;
    import java.util.Set;
    import java.util.HashSet;
    
    import java.io.Serializable;
    import javax.persistence.*;
    import net.sf.mp.demo.tranxy.domain.tranxy.Traduction;
    
    /**
     *
     * <p>Title: Language</p>
     *
     * <p>Description: Domain Object describing a Language entity</p>
     *
     */
    @Entity (name="Language")
    @Table (name="language")
    @NamedQueries({
      @NamedQuery(name="Language.findAll", query="SELECT language FROM Language language")
     ,@NamedQuery(name="Language.findByCode", query="SELECT language FROM Language language WHERE language.code = :code")
     ,@NamedQuery(name="Language.findByCodeContaining", query="SELECT language FROM Language language WHERE language.code like :code")
    })
    public class Language implements Serializable {
        private static final long serialVersionUID = 1L;
     
        public static final String FIND_ALL = "Language.findAll";
        public static final String FIND_BY_CODE = "Language.findByCode";
        public static final String FIND_BY_CODE_CONTAINING ="Language.findByCodeContaining";
     
        @Id @Column(name="idlanguage" )
        @GeneratedValue(strategy = GenerationType.AUTO)
        private Integer idlanguage;
    
    //MP-MANAGED-ADDED-AREA-BEGINNING @code-field-annotation@
    //MP-MANAGED-ADDED-AREA-ENDING @code-field-annotation@
    //MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @ATTRIBUTE-code@
        @Column(name="code",  length=45, nullable=false,  unique=false)
        private String code; 
    //MP-MANAGED-UPDATABLE-ENDING
    
    //MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @traductions-field-language@
        @OneToMany (targetEntity=net.sf.mp.demo.tranxy.domain.tranxy.Traduction.class, fetch=FetchType.LAZY, mappedBy="language", cascade=CascadeType.REMOVE)//, cascade=CascadeType.ALL)
        private Set <Traduction> traductions = new HashSet<Traduction>(); 
    
    //MP-MANAGED-UPDATABLE-ENDING
        /**
        * Default constructor
        */
        public Language() {
        }
    
     /**
     * All field constructor 
     */
        public Language(
           Integer idlanguage,
           String code) {
           //primary keys
           setIdlanguage (idlanguage);
           //attributes
           setCode (code);
           //parents
        }
    
     public Language flat() {
        return new Language(
              getIdlanguage(),
              getCode()
        );
     }
    
        public Integer getIdlanguage() {
            return idlanguage;
        }
     
        public void setIdlanguage (Integer idlanguage) {
            this.idlanguage =  idlanguage;
        }
        
    //MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @GETTER-SETTER-code@
        public String getCode() {
            return code;
        }
     
        public void setCode (String code) {
            this.code =  code;
        }    
    //MP-MANAGED-UPDATABLE-ENDING
    
    
    
    //MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @traductions-getter-language@
        public Set<Traduction> getTraductions() {
            if (traductions == null){
                traductions = new HashSet<Traduction>();
            }
            return traductions;
        }
    
        public void setTraductions (Set<Traduction> traductions) {
            this.traductions = traductions;
        } 
        
        public void addTraductions (Traduction traduction) {
             getTraductions().add(traduction);
        }
        
    //MP-MANAGED-UPDATABLE-ENDING
    
    
    //MP-MANAGED-ADDED-AREA-BEGINNING @implementation@
    //MP-MANAGED-ADDED-AREA-ENDING @implementation@
    
    }
    
    Tranduction
    /**
     * 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 : DomainEntityJPA2Annotation
     * - file name : DomainEntityJPA2Annotation.vm
    */
    package net.sf.mp.demo.tranxy.domain.tranxy;
    
    //MP-MANAGED-ADDED-AREA-BEGINNING @import@
    //MP-MANAGED-ADDED-AREA-ENDING @import@
    import java.sql.*;
    import java.util.Date;
    import java.util.List;
    import java.util.ArrayList;
    import java.util.Set;
    import java.util.HashSet;
    
    import java.io.Serializable;
    import javax.persistence.*;
    import net.sf.mp.demo.tranxy.domain.translation.TranslationKey;
    import net.sf.mp.demo.tranxy.domain.tranxy.Language;
    
    /**
     *
     * <p>Title: Traduction</p>
     *
     * <p>Description: Domain Object describing a Traduction entity</p>
     *
     */
    @Entity (name="Traduction")
    @Table (name="traduction")
    @NamedQueries({
      @NamedQuery(name="Traduction.findAll", query="SELECT traduction FROM Traduction traduction")
     ,@NamedQuery(name="Traduction.findByTranslation", query="SELECT traduction FROM Traduction traduction WHERE traduction.translation = :translation")
     ,@NamedQuery(name="Traduction.findByTranslationContaining", query="SELECT traduction FROM Traduction traduction WHERE traduction.translation like :translation")
    })
    public class Traduction implements Serializable {
        private static final long serialVersionUID = 1L;
     
        public static final String FIND_ALL = "Traduction.findAll";
        public static final String FIND_BY_TRANSLATION = "Traduction.findByTranslation";
        public static final String FIND_BY_TRANSLATION_CONTAINING ="Traduction.findByTranslationContaining";
     
        @Id @Column(name="id" )
        @GeneratedValue(strategy = GenerationType.AUTO)
        private Long id;
    
    //MP-MANAGED-ADDED-AREA-BEGINNING @translation-field-annotation@
    //MP-MANAGED-ADDED-AREA-ENDING @translation-field-annotation@
    //MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @ATTRIBUTE-translation@
        @Column(name="translation",  length=800,  nullable=true,  unique=false)
        private String translation; 
    //MP-MANAGED-UPDATABLE-ENDING
    
        @ManyToOne (fetch=FetchType.LAZY , optional=false)
        @JoinColumn(name="Key_id", referencedColumnName = "id", nullable=false,  unique=false ) 
        private TranslationKey key;  
    
        @Column(name="Key_id",  nullable=false,  unique=false, insertable=false, updatable=false)
        private Long key_;
    
        @ManyToOne (fetch=FetchType.LAZY , optional=false)
        @JoinColumn(name="language_id", referencedColumnName = "idlanguage", nullable=false,  unique=false ) 
        private Language language;  
    
        @Column(name="language_id",  nullable=false,  unique=false, insertable=false, updatable=false)
        private Integer language_;
    
        /**
        * Default constructor
        */
        public Traduction() {
        }
    
     /**
     * All field constructor 
     */
        public Traduction(
           Long id,
           String translation,
           Integer language,
           Long key) {
           //primary keys
           setId (id);
           //attributes
           setTranslation (translation);
           //parents
           this.key = new TranslationKey();
           this.key.setId(key); //ID
           this.language = new Language();
           this.language.setIdlanguage(language); //IDLANGUAGE
        }
    
     public Traduction flat() {
        return new Traduction(
              getId(),
              getTranslation(),
              getLanguage_(),
              getKey_()
        );
     }
    
        public Long getId() {
            return id;
        }
     
        public void setId (Long id) {
            this.id =  id;
        }
        
    //MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @GETTER-SETTER-translation@
        public String getTranslation() {
            return translation;
        }
     
        public void setTranslation (String translation) {
            this.translation =  translation;
        }    
    //MP-MANAGED-UPDATABLE-ENDING
    
    
        public TranslationKey getKey () {
         return key;
        }
     
        public void setKey (TranslationKey key) {
         this.key = key;
        }
    
        public Long getKey_() {
            return key_;
        }
     
        public void setKey_ (Long key) {
            this.key_ =  key;
        }
     
        public Language getLanguage () {
         return language;
        }
     
        public void setLanguage (Language language) {
         this.language = language;
        }
    
        public Integer getLanguage_() {
            return language_;
        }
     
        public void setLanguage_ (Integer language) {
            this.language_ =  language;
        }
    
    //MP-MANAGED-ADDED-AREA-BEGINNING @implementation@
    //MP-MANAGED-ADDED-AREA-ENDING @implementation@
    
    }
    
    persistence.xml in test directory
    <?xml version="1.0"?>
    <persistence xmlns="http://java.sun.com/xml/ns/persistence"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
                 version="1.0">
    <!--MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @PERSISTENCE-UNIT-tranxy@-->
        <persistence-unit name="tranxy" transaction-type="RESOURCE_LOCAL">
    <!--MP-MANAGED-UPDATABLE-ENDING-->
            <provider>org.hibernate.ejb.HibernatePersistence</provider>
            <!-- tranxy --> 
            <class>net.sf.mp.demo.tranxy.domain.tranxy.Language</class>
            <class>net.sf.mp.demo.tranxy.domain.tranxy.Traduction</class>
            <!-- translation --> 
            <class>net.sf.mp.demo.tranxy.domain.translation.TranslationKey</class>
            <properties>
                <property name="hibernate.show_sql" value="true" />
                <property name="hibernate.format_sql" value="true" />
                <property name="hibernate.connection.driver_class" value="org.gjt.mm.mysql.Driver" />
                <property name="hibernate.connection.url" value="jdbc:mysql://127.0.0.1:3306/tranxy" />
                <property name="hibernate.connection.username" value="root" />
                <property name="hibernate.connection.password" value="mysql" />
                <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>
            </properties> 
        </persistence-unit>
    </persistence>

    Persistence.xml in main directory

    The persistence.xml provides a configuration with embedded connection pool.
    If the user want to use a remote connection pool accessed by JNDI, the user has to put property

    lt;property name="environment" value="remote" />
    Under the target JPA2 node.

    Alter Generated Code and Unit Test

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

    Add a validation annotation

    In Language class
    //MP-MANAGED-ADDED-AREA-BEGINNING @import@
    import javax.validation.constraints.*;
    //MP-MANAGED-ADDED-AREA-ENDING @import@
    
    ...
    //MP-MANAGED-ADDED-AREA-BEGINNING @code-field-annotation@
        @Size(min = 2)
    //MP-MANAGED-ADDED-AREA-ENDING @code-field-annotation@
    //MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @ATTRIBUTE-code@
        @Column(name="code",  length=45, nullable=false,  unique=false)
        private String code; 
    

    Add the imports between the MP-MANAGED-ADDED-AREA-BEGINNING @import@ and MP-MANAGED-ADDED-AREA-ENDING @import@ comments
    Add the annotation between MP-MANAGED-ADDED-AREA-BEGINNING @xxxx-field-annotation@ and MP-MANAGED-ADDED-AREA-ENDING @xxxx-field-annotation@ comments

    Now further generation will keep your added code.

    Unit test

    In src/test/java add the TranxyTest class in package mytest
    The validation scenario has been inspired by this link.

    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");
      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");
      q.executeUpdate();
      tx.commit();
     }
    }
    
    

    Execute with
    >mvn clean package
    The 2 tests pass.

    Download


    The result can be downloaded on google code minuteproject.

    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

    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.