Saturday, February 25, 2012

Productivity by example: Liferay with JOOQ

This article shows how to use jOOQ for the Liferay data model.
Minuteproject generates jOOQ artifacts for Liferay.
The main interest of using minuteproject to generate jOOQ artefacts are 
  • Liferay database does not have any foreign key, but minuteproject is able to detect relationship based on patterns.
  •  Some DB naming convention might need to be adapted at Java level
    • DB tables ending with '_' will have underscore stripped
    • Column name ending with '_id' corresponding to PK or FK will have this particule stripped when not coliding with other variable
  • Code is updatable, meaning that you can change part of it or entirely and consecutive generation will keep your alterations
For more information about the enrichment made on top of Liferay by minuteproject check another post targeting Liferay with JPA2.
Here the reverse-engineering targets jOOQ framework and the configuration mentions jOOQ track reference

Configuration
mp-config-LIFERAY-JOOQ.xml
<!DOCTYPE root>
<generator-config>
 <configuration>
  <conventions>
   <target-convention type="enable-updatable-code-feature" />
  </conventions>
  <model name="liferay" 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/lportal</url>
     <username>root</username>
     <password>mysql</password>
    </dataSource>
    <schema>lportal</schema>
    <primaryKeyPolicy oneGlobal="true">
     <primaryKeyPolicyPattern name="none"></primaryKeyPolicyPattern>
    </primaryKeyPolicy>
   </data-model>
   <business-model>
    <generation-condition>
     <condition type="exclude" startsWith="QUARTZ"></condition>
    </generation-condition>
    <enrichment>
     <conventions>
      <entity-naming-convention type="apply-strip-table-name-suffix"
       pattern-to-strip="_" />
      <!-- manipulate the structure and entities BEFORE manipulating the 
       entities -->
      <foreign-key-convention
       type="autodetect-foreign-key-based-on-similarity-and-map"
       column-ending="id" column-starting="" />
      <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="JOOQ" name="JOOQ" fileName="mp-template-config-JOOQ.xml"
    outputdir-root="../../dev/JOOQ/liferay" templatedir-root="../../template/framework/jooq">
    <property name="jooq-version" value="2.0.4"></property>
   </target>
   <target refname="LIB" fileName="mp-template-config-bsla-LIB-features.xml"
    templatedir-root="../../template/framework/bsla">
   </target>
  </targets>
 </configuration>
</generator-config>
Execution
Install Liferay mysql 6.0.6 running
Download minuteproject
Drop this config in /mywork/config
And execute model-generation.(sh/cmd) mp-config-LIFERAY-JOOQ.xml

Result 
Artifacts
The resulting artefacts are
  • A maven project with following dependencies
    • jOOQ
    • jUnit
    •  mysql drive
  • jOOQ artifacts
    • factory, keys, tables, records...
  • unit test
The artifacts are generated in /dev/JOOQ/liferay
If you build there will be a compilation error in jOOQ artifacts:
Table 'expandotable' has a field call 'table' that collides with a Jooq method. 
But just by altering the code to remove the compilation error and flaging to take into account this modification:
In ExpandotableRecord 
  • Change getTable into getTable_ 
  • Exclude code from been overwritten: line 24 //MP-MANAGED-UPDATABLE-BEGINNING-ENABLE
 //MP-MANAGED-UPDATABLE-BEGINNING-ENABLE @jooq-record-pk-liferay@
 /**
  * An uncommented item
  * 
  * PRIMARY KEY
  */
    public void setTable(java.lang.Long value) {
        setValue(net.sf.mp.demo.liferay.tables.Expandotable.__EXPANDOTABLE.TABLE, value);
    }
 /**
  * An uncommented item
  * 
  * PRIMARY KEY
  */
    public java.lang.Long getTable_() {
        return getValue(net.sf.mp.demo.liferay.tables.Expandotable.__EXPANDOTABLE.TABLE);
    }
 //MP-MANAGED-UPDATABLE-ENDING

Remark: Future version will handle this issue but it is interesting to see how the code can be customized without losing the productivity of consecutive generations.

Eventually run: mvn clean package
  • A set of unittest are executed:
    • The default unit test consist to retrieve the first row of each entity.
  • A JOOQ package is released
Code
The code can be downloaded from minuteproject googlecode here.

Sample
Active record:
Providing the Foreign key gives the ability to provide jOOQ records with some methods
One to many
//todo
Testing

/**
 * This class is generated by minuteproject 4 jOOQ
 */
package net.sf.mp.demo.liferay.tables;

import net.sf.mp.demo.liferay.tables.records.UserRecord;
import net.sf.mp.demo.liferay.Liferay;
import net.sf.mp.demo.liferay.Keys;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

import net.sf.mp.demo.liferay.LiferayFactory;
import static net.sf.mp.demo.liferay.tables.User.__USER;

import org.jooq.Record;
import org.jooq.Result;
import org.junit.Test;

//MP-MANAGED-ADDED-AREA-BEGINNING @import@
import static net.sf.mp.demo.liferay.tables.Company.__COMPANY;
import static net.sf.mp.demo.liferay.tables.Account.__ACCOUNT;
//MP-MANAGED-ADDED-AREA-ENDING @import@

//MP-MANAGED-ADDED-AREA-BEGINNING @class-annotation@
//MP-MANAGED-ADDED-AREA-ENDING @class-annotation@
@javax.annotation.Generated(value = { "http://www.jooq.org", "2.0.4" }, comments = "This class is generated by minuteproject 4 jOOQ")
public class TestUser {

 @Test
 public void testUser() {
  Connection conn = null;
  String userName = "root";
  String password = "mysql";
  String url = "jdbc:mysql://127.0.0.1:3306/lportal";

  try {
   Class.forName("org.gjt.mm.mysql.Driver").newInstance();
   conn = DriverManager.getConnection(url, userName, password);
   LiferayFactory create = new LiferayFactory(conn);

   // MP-MANAGED-UPDATABLE-BEGINNING-ENABLE
   // @jooq-unittest-testUser-liferay@
   // write your own tests, just set DISABLE to ENABLE in the comment
   // above
   // future generation will not erase your code ;)
   Result<Record> result = create
     .select(__USER.FIRSTNAME, __USER.LASTNAME, __COMPANY.WEB, __ACCOUNT.NAME)
     .from(__USER).join(__COMPANY)
     .on(__USER.COMPANY.equal(__COMPANY.COMPANY))
     .join(__ACCOUNT)
     .on(__COMPANY.ACCOUNT.equal(__ACCOUNT.ACCOUNT))
     .where(__COMPANY.WEB.like("%ray.com"))
     .orderBy(__USER.LASTNAME.asc().nullsFirst()).limit(10)
     .fetch();
   for (Record r : result) {
    String firstname = r.getValue(__USER.FIRSTNAME);
    String lastname = r.getValue(__USER.LASTNAME);
    String web = r.getValue(__COMPANY.WEB);
    String accountname = r.getValue(__ACCOUNT.NAME);

    System.out.println(" firstname : " + firstname + " lastname : "
      + lastname + " web : " + web + " accountname : "
      + accountname);
   }
   // MP-MANAGED-UPDATABLE-ENDING

  } catch (Exception e) {
   e.printStackTrace();
  } finally {
   if (conn != null) {
    try {
     conn.close();
    } catch (SQLException e) {
     // TODO Auto-generated catch block
     e.printStackTrace();
    }
   }
  }
 }

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

}
Giving at execution
 firstname :  lastname :  web : liferay.com accountname : Liferay
 firstname : Joe lastname : Bloggs web : liferay.com accountname : Liferay
 firstname : Test lastname : DLC 1 web : liferay.com accountname : Liferay
 firstname : Test lastname : DLC 10 web : liferay.com accountname : Liferay
 firstname : Test lastname : DLC 2 web : liferay.com accountname : Liferay
 firstname : Test lastname : DLC 3 web : liferay.com accountname : Liferay
 firstname : Test lastname : DLC 4 web : liferay.com accountname : Liferay
 firstname : Test lastname : DLC 5 web : liferay.com accountname : Liferay
 firstname : Test lastname : DLC 6 web : liferay.com accountname : Liferay
 firstname : Test lastname : DLC 7 web : liferay.com accountname : Liferay
JOOQ quick review
//todo
  • Typesafe query speeds up the development process
  • No ORM 
  • Focus on CRUD and complexe query 
  • No configuration loading meaning fast to test



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.