Friday, August 23, 2013

SDD as a productivity weapon 4 openxava

Under construction (need 0.8.5 for generation) but can be used in the meantime as a OX tutorial to work with any jdbc sql statement (here stored-procedure)

Productivity challenge

With SDD (Statement driven development) what is important is I/O and functionality.
The model is secondary.
To experience it, let's have a store procedure that perform a required operation and get a web application out of it with Minuteproject 4 Openxava.

Mission statement

UC general statement

You have information to pass to a DB via a web application to perform a function.
You would like the input to be validated base on type, presence, stereotype, membership.

UC specific

You need a functionality to ask for role in the application.
As input you pass 3 params:
  • username
  • email
  • requested role
 UserName is:
  • mandatory
  • string
Email is:
  • mandatory
  • stereotype (format)
 Request role is:
  • mandatory
  • should be one of the application specific role

The function shall also:
  • register the time of creation
  • status=TO_TREAT
The function is provided in format of a stored procedure.

Dev environment constraints

A schema containing
  • a table SEC_ROLE containing roles
  • a table BUS_USER_ROLE_REQUEST
  • a store procedure ASK_FOR_ROLE
  • CREATE PROCEDURE ask_for_role(
      IN username VARCHAR(255),
      IN email VARCHAR(255),
       IN role VARCHAR(255)
    )
    BEGIN
    Insert into BUS_USER_ROLE_REQUEST (USERNAME, EMAIL, ROLE_REQUESTED, STATUS, REQUEST_DATE)
    values (username, email, role, 'TO_TREAT', NOW());
    
    END
    
  • SEC_ROLE and  BUS_USER_ROLE_REQUEST are not linked by any relationships (no FK nor m2m).

Configuration

This configuration focus on the SDD part
<!DOCTYPE root>
<generator-config>
 <configuration>
        <conventions>
            <target-convention type="enable-updatable-code-feature" />
        </conventions>

<!-- other configuration, data-model where sec_role and bus_user_role_request are present... -->

  <model name="porphyry" version="1.0" package-root="net.sf.mp.demo">
   <statement-model>
    <queries>
            <query name="ask_for_role" id="ask_for_role" >
                         <query-body>
                         <value>
<![CDATA[call ask_for_role (?,?,?)]]>
                            </value>
                         </query-body>
                         <query-params>
                             <query-param name="username" is-mandatory="true" type="string" sample="'a'" is-id="true"></query-param>
                             <query-param name="email" is-mandatory="true" type="string" sample="'b'">
                              <stereotype stereotype="EMAIL" />
                             </query-param>
                             <query-param name="role" is-mandatory="true" type="string" sample="'c'">
                                <query-param-link entity-name="sec_role" field-name="role"/>
                             </query-param>
                         </query-params>
                     </query>
                </queries>
            </statement-model>
         </model>
  <targets catalog-entry="OpenXava" >
  </targets>
 </configuration>
</generator-config> 
The main points are:
  • call ask_for_role with 3 parameters
  • description of the parameters (name, type, presence, stereotype)
  • restrict to a set of value coming from a table and field (query-param-link)

Openxava design flow

The input is in format of an Openxava/JPA2 entity to enable binding and link to other entity.
But this entity will never be persisted and never lookup.
The input screen will be accessed directly, and button match the action. After the action is performed a message is display.
To enable this flow OX controllers.xml and application.xml nodes are generated.
The action binding the input data from the form; validating and calling the store procedure call are also generated.

Generated code

Input/Output bean generated


import javax.persistence.*;
import org.openxava.annotations.*;

import net.sf.mp.demo.porphyry.domain.security.Role;

@Entity (name="AskForRoleIn")
@Table (name="ask_for_role")
@Views({
//MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @view-base-ask_for_role@
 @View(
  name="base",
  members=
        ""  
        + "username  ; "
        + "email  ; "
        + "role  ; "
  ),
//MP-MANAGED-UPDATABLE-ENDING
 @View(
  name="Create", 
  extendsView="base"
 ),
 @View(
  name="Update", 
  extendsView="base",
        members=
          ""  
 ),
 @View(extendsView="base",
        members=
          ""  
 ),
    @View(name="askForRoleDEFAULT_VIEW", 
    members=
          " username ;"  
        + "email  ; "
        + "roleTransient  ; "
 ),
//MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @view-reference-ask_for_role@
    @View(name="reference", 
       extendsView="askForRoleDEFAULT_VIEW"
//MP-MANAGED-UPDATABLE-ENDING
    )
})

//MP-MANAGED-ADDED-AREA-BEGINNING @class-annotation@
//MP-MANAGED-ADDED-AREA-ENDING @class-annotation@
public class AskForRoleIn {

     @Id @Column(name="username" ,length=255)
    private String username; 

//MP-MANAGED-ADDED-AREA-BEGINNING @email-field-annotation@
//MP-MANAGED-ADDED-AREA-ENDING @email-field-annotation@

//MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @ATTRIBUTE-email@
    @Column(name="email",  length=255, nullable=false,  unique=false)
    @Required
    @Stereotype ("EMAIL")
    private String email;
//MP-MANAGED-UPDATABLE-ENDING

//MP-MANAGED-ADDED-AREA-BEGINNING @role_TRANSIENT-field-annotation@
//MP-MANAGED-ADDED-AREA-ENDING @role_TRANSIENT-field-annotation@

//MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @ATTRIBUTE-role_TRANSIENT@
 @Transient
 @ReadOnly
    private String roleTransient;
//MP-MANAGED-UPDATABLE-ENDING


//MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @parent-Role-ask_for_role@
    @ManyToOne (fetch=FetchType.LAZY ,optional=false) 
    @JoinColumn(name="role", referencedColumnName = "ID", nullable=false,  unique=false  )
    @ReferenceView ("reference") 
    private Role role;

...
} 
Although not persisted and never looked up AskForRoleIn can be used by Openxava:
  •  to pass by information as a DTO
  • perform validation
  • perform assignment (it is linked to table Role)
  • it contains a transient field roleTransient that will be used by the Openxava action to copy the 'role' field of the 'role' table (not the pk)
  • offers a view with
    • simple input field
    • associated entities
  • @Id is associated to one field (otherwise JPA/Hibernate complains)

Action


/**
 * template reference : 
 * - name      : ActionOX.SDD.query
 * - file name : ActionOX.SDD.query.vm
 * - time      : 2013/08/22 AD at 12:29:41 CEST
*/
package net.sf.mp.demo.porphyry.sdd.action.statement;

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

import org.openxava.jpa.*;
import org.openxava.model.*;
import org.openxava.util.*;
import org.openxava.validators.*;
import org.openxava.actions.*;
import java.util.*;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

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

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

import net.sf.mp.demo.porphyry.sdd.out.statement.AskForRoleOutList;
import net.sf.mp.demo.porphyry.sdd.out.statement.AskForRoleOut;
import net.sf.mp.demo.porphyry.sdd.in.statement.AskForRoleIn;

public class AskForRoleAction extends ViewBaseAction {

    public static final String QUERY_NATIVE = "call ask_for_role (?,?,?)";

//MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @SDD_EXECUTE_GET-ask_for_role@
    public AskForRoleOutList execute (AskForRoleIn askForRoleIn) {
        AskForRoleOutList askForRoleOutList = new AskForRoleOutList();
        List list = executeJDBC (askForRoleIn);
        askForRoleOutList.setAskForRoleOuts (list);
        return askForRoleOutList;
    }
//MP-MANAGED-UPDATABLE-ENDING

//MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @SDD_EXECUTE_JDBC-ask_for_role@
 public List<askforroleout> executeJDBC(AskForRoleIn askForRoleIn) {
  if (askForRoleIn==null)
   askForRoleIn = new AskForRoleIn();
  List<askforroleout> list = new ArrayList<askforroleout>();
  PreparedStatement pstmt = null;
  ResultSet rs = null;
  Connection conn = null;
  try {
   conn = getConnection();
   pstmt = conn.prepareStatement(QUERY_NATIVE);
            if (askForRoleIn.getUsername()==null) {
               pstmt.setNull(1, java.sql.Types.VARCHAR);
            } else {
               pstmt.setString(1, askForRoleIn.getUsername()); 
            }
            if (askForRoleIn.getEmail()==null) {
               pstmt.setNull(2, java.sql.Types.VARCHAR);
            } else {
               pstmt.setString(2, askForRoleIn.getEmail()); 
            }
            if (askForRoleIn.getRoleTransient()==null) {
               pstmt.setNull(3, java.sql.Types.VARCHAR);
            } else {
               pstmt.setString(3, askForRoleIn.getRoleTransient()); 
            }
   rs = pstmt.executeQuery();
  } catch (Exception e) {
        e.printStackTrace();
     } finally {
       try {
         rs.close();
         pstmt.close();
         conn.close();
       } catch (Exception e) {
         e.printStackTrace();
       }
     }
  return list;
 }
//MP-MANAGED-UPDATABLE-ENDING

//if JPA2 implementation is hibernate
    @SuppressWarnings("deprecation")   
    public Connection getConnection() throws HibernateException {  
        Session session = getSession();  
        Connection connection = session.connection();  
        return connection;  
    } 
    
    private Session getSession() {  
        Session session = (Session) XPersistence.getManager().getDelegate();  
        return session;  
    }

 public void execute() throws Exception {
//MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @execute-porphyry@
        //super.execute();
        //TODO
        Messages errors = 
            MapFacade.validate("AskForRoleIn", getView().getValues());
        if (errors.contains()) throw new ValidationException(errors);
        AskForRoleIn e = new AskForRoleIn();
 e.setUsername((String)getView().getValue("username"));
 e.setEmail((String)getView().getValue("email"));

 // parent to copy to transient field
        Map roleMap = (Map)getView().getValue("role");
        if (roleMap!=null) {
  e.setRoleTransient ((String)roleMap.get("role"));
        }
  
        try {
            execute(e);
        } catch (Exception ex) {
            errors = new Messages();
            errors.add(ex.getMessage());
            throw new ValidationException(errors);
        }
        //TODO return list
        addInfo("call AskForRoleAction done!");
//MP-MANAGED-UPDATABLE-ENDING

 }

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

} 
  • perform validation
  • retrieve simple type as well as complex (Role object type)
  • copy values a input of the store proc call
    • here the store proc does not return anything so there is no parsing of the resultset.
Here one can argue that we do not need the transient field 'roleTransient' in AskForRoleIn.
This is true. It is present because it was easier from a generation point of view to keep the parameter order of the stored procedure call.

Controller.xml


<controllers> 
    <!-- statement driven development SDD -->
  <!-- $table.name -->
 <controller name="AskForRoleController">
  <action name="askForRole" mode="detail" class="net.sf.mp.demo.porphyry.sdd.action.statement.AskForRoleAction" >
   <use-object name="xava_view" />
  </action>     
 </controller>
</controllers> 
Provides controller and action

Application.xml


<application name="porphyry"> 

    <!-- statement driven development SDD -->
 <module name="AskForRoleIn" >
     <model name="AskForRoleIn"/>
     <view name="base"/> 
     <controller name="AskForRoleController"/>
     <mode-controller name="DetailOnly"/>
 </module>
 
</application>

Wiring between Model/View/Controller and mode
Detail mode selected (ie no lookup)

Screens

Input screen 

Available at ${yourcontext}/xava/home.jsp?application=porphyry&module=AskForRoleIn
Is also available as a menu entry under statement

 Sub select Use case


 

 

Performing the action

Viewing the result


Since it is store in table USER_ROLE_REQUEST
Minuteproject generates also a CRUD access on this table.
By filtering we check that the input of the store proc+ additional business field are stored correctly.



 

Conclusion

Statement Driven Development - SDD
  • provides tooling for analyst
  • RAD for developer 
    • any sql statement could now be an advance business UC (sub affection, validation)
    • necessary OX gearing is generated.
  • is a pillar of development productivity
Minuteproject is also generating portlet.xml in order to deploy 'Transient-definition portlet' on portal such as Liferay.