Under construction (need 0.8.5 for generation) but can be used in the meantime as a OX tutorial to work with DTO
Productivity challenge
Alongside with (1) smart reverse-engineering, (2) virtualization, (3) Statement-driven-development, Transient definition is a productivity tool and technic that fasten your development.This article presents you transient definition and how it will help developing ad-hoc UCs in the case of Openxava. Ad-hoc means that there is no direct binding necessary between your screen info and your database entities.
Transient definition
Your model persistence is not enough to define all your data or you want to model the data differently than in the current model perspective. In short you have a UC that is not purely data centric?Mission statement
UC general statement
You know the Input of a UC and a function to be performed.The input does not have to match to field or entities in your DB. It may but it is not a requirement.
You want:
- Input screen
- Validation/affectation UC
- Stub to actions
- Partial or empty implementation of the function
- to be able to change your requirement and your implementation without losing your modifications
Concrete UC
Model description
You have a model that store Release information of an application.The release is linked to an application and environment.
It has a releaser (the person issuing the release) and a validator (validating the release).
Release status can be ACTIVE, DONE, REJECTED.
A creation date is automatically created when inserting a release.
UC description
Provide releaser user access to create a releaseHe can:
- enter name, notes, time to start, time to end the release.
- pick-up application and environment, releaser (a bit redundant it should be granted implicitly but it need a security wrapping to guess the id of the releaser - which is not part of this demo)
- enter notification email (that is not persisted)
By clicking a release button
- Status will be set to ACTIVE
- Creation date populated by current date
- Store persistence data.
Extend
- input field notify available for any manipulation the user want (send mail for example)
Provide validator user access to view all the releases, select one fill extra info and validate or reject it.
He can view the release
Openxava Code
Create Release
@Entity (name="CreateRelease") @Table (name="bus_release") @Views({ //MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @view-base-bus_release@ @View( name="base", members= "" + "name ; " + "notes ; " + "timeToStart ; " + "timeToEnd ; " + "application ; " + "environment ; " + "releaser ; " + "notify ; " ) ) }) public class CreateRelease { @Hidden @Id @Column(name="ID" ) @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; @Column(name="NAME", length=45, nullable=false, unique=false) @Required private String name; @Column(name="NOTES", nullable=true, unique=false) @Stereotype ("HTML_TEXT") private String notes; @Column(name="STATUS", length=45, nullable=true, unique=false) private String status; @Column(name="CREATION_DATE", nullable=true, unique=false) @Temporal(TemporalType.TIMESTAMP) @ReadOnly(forViews="base,Create,Update,DEFAULT,createReleaseDEFAULT_VIEW") private java.util.Date creationDate; @Column(name="TIME_TO_START", nullable=true, unique=false) @Temporal(TemporalType.TIMESTAMP) private java.util.Date timeToStart; @Column(name="TIME_TO_END", nullable=true, unique=false) @Temporal(TemporalType.TIMESTAMP) private java.util.Date timeToEnd; @Transient private String validationReason; @Transient @Required @Stereotype ("EMAIL") private String notify; @ManyToOne (fetch=FetchType.LAZY ) @JoinColumn(name="application_id", referencedColumnName = "ID", nullable=true, unique=false ) @ReferenceView ("reference") private Application application; @ManyToOne (fetch=FetchType.LAZY ) @JoinColumn(name="environment_id", referencedColumnName = "ID", nullable=true, unique=false ) @ReferenceView ("reference") @DescriptionsList( descriptionProperties= "name " ,order= "displayOrder ASC " // "name asc // " ) @NoCreate @NoModify private Environment environment; @ManyToOne (fetch=FetchType.LAZY ) //remove optional=false to aggragate but leads to a side effect when going directly to the entity: required check is not performed=> if no set DB check constraint is raised... @JoinColumn(name="releaser_id", referencedColumnName = "ID", nullable=true, unique=false ) @ReferenceView ("reference") private User releaser; ... }Standard OX entity - mapping
Contains transient field, stereotype and validation
Release Action
public class ReleaseAction extends ViewBaseAction { public void execute() throws Exception { //MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @execute-porphyry@ //super.execute(); //TODO Messages errors = MapFacade.validate("CreateRelease", getView().getValues()); if (errors.contains()) throw new ValidationException(errors); EntityManager em = XPersistence.getManager(); try { CreateRelease e = new CreateRelease(); if (errors.contains()) throw new ValidationException(errors); // set init condition // copy field from form e.setName((String)getView().getValue("name")); e.setNotes((String)getView().getValue("notes")); e.setTimeToStart((Date)getView().getValue("timeToStart")); e.setTimeToEnd((Date)getView().getValue("timeToEnd")); e.setNotify((String)getView().getValue("notify")); // parent Map applicationMap = (Map)getView().getValue("application"); if (applicationMap!=null) { Integer applicationId = (Integer)applicationMap.get("id"); if (applicationId != null) { Application application = new Application(); application.setId(applicationId); e.setApplication(application); } } Map environmentMap = (Map)getView().getValue("environment"); if (environmentMap!=null) { Integer environmentId = (Integer)environmentMap.get("id"); if (environmentId != null) { Environment environment = new Environment(); environment.setId(environmentId); e.setEnvironment(environment); } } Map releaserMap = (Map)getView().getValue("releaser"); if (releaserMap!=null) { Integer releaserId = (Integer)releaserMap.get("id"); if (releaserId != null) { User releaser = new User(); releaser.setId(releaserId); e.setReleaser(releaser); } } // assign field e.setStatus(new String("ACTIVE")); XPersistence.getManager().persist(e); getView().reset(); resetDescriptionsCache(); } catch (Exception e) { errors = new Messages(); errors.add(e.getMessage()); throw new ValidationException(errors); } //TODO return list addInfo("call ReleaseAction done!"); //MP-MANAGED-UPDATABLE-ENDING } }
- Extends ViewBaseAction
- Performs validation
- Retrieves fields and object from getView()
- Adds status value to ACTIVE
- Stores value via XPersistence API
Validate release
@Entity (name="ValidateRelease") @Table (name="bus_release") @Views({ //MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @view-base-bus_release@ @View( name="base", members= "" + "name ; " + "notes ; " + "status ; " + "creationDate ; " + "timeToStart ; " + "timeToEnd ; " + "application ; " + "environment ; " + "releaser ; " + "validator ; " + "validationReason ; " + "inform ; " ), ... }) @Tabs({ @Tab( properties= " name " +", notes " +", status " +", creationDate " +", timeToStart " +", timeToEnd " +", environment.name " +", releaser.identifier " +", validator.identifier " ) ... }) public class ValidateRelease { @Hidden @Id @Column(name="ID" ) @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; @Column(name="NAME", length=45, nullable=false, unique=false) @Required @ReadOnly private String name; @Column(name="NOTES", nullable=true, unique=false) @Stereotype ("HTML_TEXT") @ReadOnly private String notes; @Column(name="STATUS", length=45, nullable=true, unique=false) @ReadOnly private String status; @Column(name="CREATION_DATE", nullable=true, unique=false) @Temporal(TemporalType.TIMESTAMP) @ReadOnly(forViews="base,Create,Update,DEFAULT,validateReleaseDEFAULT_VIEW") private java.util.Date creationDate; @Column(name="TIME_TO_START", nullable=true, unique=false) @Temporal(TemporalType.TIMESTAMP) @ReadOnly private java.util.Date timeToStart; @Column(name="TIME_TO_END", nullable=true, unique=false) @Temporal(TemporalType.TIMESTAMP) @ReadOnly private java.util.Date timeToEnd; @Transient private String validationReason; @Transient @Required @Stereotype ("EMAIL") private String inform; @ManyToOne (fetch=FetchType.LAZY ) @JoinColumn(name="application_id", referencedColumnName = "ID", nullable=true, unique=false ) @ReferenceView ("reference") @NoCreate @NoModify private Application application; @ManyToOne (fetch=FetchType.LAZY ) @JoinColumn(name="environment_id", referencedColumnName = "ID", nullable=true, unique=false ) @ReferenceView ("reference") @DescriptionsList( descriptionProperties= "name " ,order= "displayOrder ASC " // "name asc // " ) @NoCreate @NoModify private Environment environment; @ManyToOne (fetch=FetchType.LAZY ) @JoinColumn(name="releaser_id", referencedColumnName = "ID", nullable=true, unique=false ) @ReferenceView ("reference") @NoCreate @NoModify private User releaser; @ManyToOne (fetch=FetchType.LAZY ) @JoinColumn(name="validator_id", referencedColumnName = "ID", nullable=true, unique=false ) @ReferenceView ("reference") private User validator; }
Validate Action
public class ValidateAction extends ViewBaseAction { public void execute() throws Exception { //MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @execute-porphyry@ //super.execute(); //TODO Messages errors = MapFacade.validate("ValidateRelease", getView().getValues()); if (errors.contains()) throw new ValidationException(errors); EntityManager em = XPersistence.getManager(); try { ValidateRelease e = new ValidateRelease(); e.setId(Integer.valueOf(getView().getValueString("id"))); e = XPersistence.getManager().find(ValidateRelease.class, e.getId()); //check display condition if (!new String("ACTIVE").equals(e.getStatus())) errors.add ("Condition statusActive not met!"); if (errors.contains()) throw new ValidationException(errors); // set init condition // copy field from form e.setValidationReason((String)getView().getValue("validationReason")); e.setInform((String)getView().getValue("inform")); // parent Map validatorMap = (Map)getView().getValue("validator"); if (validatorMap!=null) { Integer validatorId = (Integer)validatorMap.get("id"); if (validatorId != null) { User validator = new User(); validator.setId(validatorId); e.setValidator(validator); } } // assign field e.setStatus(new String("DONE")); XPersistence.getManager().persist(e); getView().reset(); resetDescriptionsCache(); } catch (Exception e) { errors = new Messages(); errors.add(e.getMessage()); throw new ValidationException(errors); } //TODO return list addInfo("call ValidateAction done!"); //MP-MANAGED-UPDATABLE-ENDING }
- Implement little business logic
- Stores value
- Updates view display info
Application
<application name="porphyry"> <!-- transfer entities --> <module name="CreateRelease" > <model name="CreateRelease"/> <view name="base"/> <controller name="CreateReleaseController"/> <mode-controller name="DetailOnly"/> </module> <module name="ValidateRelease" > <model name="ValidateRelease"/> <view name="base"/> <controller name="ValidateReleaseController"/> <mode-controller name="SplitOnly"/> </module> ...CreateRelease is DetailOnly while ValidateRelease is SplitOnly mode (list+detail)
Controllers
<controllers> <!-- transfer entities --> <!-- bus_release --> <controller name="CreateReleaseController"> <action name="release" mode="detail" class="net.sf.mp.demo.porphyry.action.business.busrelease.ReleaseAction" > <use-object name="xava_view" /> </action> </controller> <!-- bus_release --> <controller name="ValidateReleaseController"> <action name="validate" mode="detail" class="net.sf.mp.demo.porphyry.action.business.busrelease.ValidateAction" > <use-object name="xava_view" /> </action> <action name="reject" mode="detail" class="net.sf.mp.demo.porphyry.action.business.busrelease.RejectAction" > <use-object name="xava_view" /> </action> </controller>
CreateReleaseController has one action 'Create' while ValidateReleaseController has 2 validate/reject
Minuteproject Configuration
Minuteproject abstract this effort by providing tooling to the analyst to describe those screens and actions that ultimately and implicitly become a flow.<enrichment> <!-- transient objects --> <entity name="create release" is-transfer-entity="true" is-searchable="false" main-entity="bus_release"> <field name="id" hidden="true"></field> <field name="name"></field> <field name="notes"></field> <field name="application_id"></field> <field name="environment_id"></field> <field name="releaser_id" ></field> <field name="status" value="ACTIVE" hidden="true"></field> <field name="creation_date" hidden="true"></field> <!-- <field name="validator_id"></field> --> <field name="time_to_start"></field> <field name="time_to_end"></field> <field name="validation_reason" hidden="true"></field> <!-- other fields --> <field name="notify" type="string" mandatory="true"> <stereotype stereotype="EMAIL" /> </field> <actions> <action name="release" default-implementation="insert" > <field name="status" value="ACTIVE"></field> </action> </actions> </entity> <!-- transient objects --> <entity name="validate release" is-transfer-entity="true" main-entity="bus_release" is-editable="false" is-searchable="true"> <field name="id" hidden="true"></field> <field name="name"></field> <field name="notes"></field> <field name="application_id"></field> <field name="environment_id"></field> <field name="releaser_id"></field> <field name="status"></field> <field name="creation_date"></field> <field name="validator_id" is-editable="true"/><!-- default-value="profile.user.id"></field> --> <field name="time_to_start"></field> <field name="time_to_end"></field> <field name="validation_reason" is-editable="true"></field> <!-- other fields --> <field name="inform" type="string" mandatory="true" is-editable="true"> <stereotype stereotype="EMAIL" /> </field> <actions> <action name="validate" default-implementation="update"> <action-condition name="statusActive"> <field name="status" value="ACTIVE"/> </action-condition> <field name="status" value="DONE"></field> </action> <action name="reject" default-implementation="update"> <action-condition name="statusActive"> <field name="status" value="ACTIVE"/> </action-condition> <field name="status" value="REJECT"></field> </action> </actions> </entity> </enrichment>
Screens
Create release
Validation
Releasing
Validator view
Select one entry
Validate
Try to reprocess a status-DONE-release
Conclusion
Minuteproject provides transient definition facilities & generation capabilities.Although the screens and actions are not very complex, minuteproject can induce a flow, and without a workflow engine!
Minuteproject is also generating portlet.xml in order to deploy 'Transient-definition portlet' on portal such as Liferay.
No comments:
Post a Comment