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
- requested role
- mandatory
- string
- mandatory
- stereotype (format)
- mandatory
- should be one of the application specific role
The function shall also:
- register the time of creation
- status=TO_TREAT
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
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(); Listlist = 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.
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=AskForRoleInIs 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