Tuesday, August 27, 2013

Transient definition 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 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 release
He 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.