Monday, November 18, 2013

Minuteproject with Ant HOW-TO

This is a sample ant integration

This page is a HOW-TO integrate Minuteproject with Ant build tool.

Set up

 

Minuteproject distribution

Download Minuteproject zip and install in MP_HOME directory.

Environment variable

$set MP_HOME=mp-home.directory

Minuteproject config

Have you minuteproject configuration in MP_HOME/mywork/config or MP_HOME/demo/config.
Remark:
 MP_HOME/demo/config is shipped with many configuration working with HSQLDB database located on the file system. 
For this article we will use mp-config-JPA2.xml


Ant Script

Sample ant script to place in MP_HOME/mywork/config or MP_HOME/demo/config.
The script has an ant macro that calls java command on Minuteproject kernel main class passing the minuteproject config.


<?xml version="1.0" encoding="UTF-8"?>
<project default="generation-sample" basedir="." name="MinuteProject - ANT integration">
 <description>This is a sample ant integration</description>

 <!-- ===================================================================== -->
 <!-- properties definitions -->
 <!-- ===================================================================== -->

 <property environment="env"/>
 <property name="minuteproject.home" value="${env.MP_HOME}" />

 <!-- ===================================================================== -->
 <!-- classpath definitions -->
 <!-- ===================================================================== -->

 <!-- 
    this is used to get the minuteproject release, dependencies and config    
 -->
 
 <path id="mp.library.path">
  <pathelement location="${env.MP_HOME}/demo/config" />
  <fileset dir="${env.MP_HOME}/application">
   <include name="**/*.jar" />
  </fileset>
 </path>
 
 <!-- ===================================================================== -->
 <!-- macro definitions -->
 <!-- ===================================================================== -->
 
 <macrodef name="macro_generate">
  <attribute name="config" />
  <sequential>
   <echo message="Generate for config @{config}" />
   <java fork="true" classname="net.sf.minuteProject.application.ModelViewGenerator">
    <classpath>
     <path refid="mp.library.path" />
    </classpath>
    <arg line="@{config}" />
   </java>
  </sequential>
 </macrodef>

 <!-- ===================================================================== -->
 <!-- ant generation tasks definitions -->
 <!-- ===================================================================== -->
 
 <target name="generation-sample">
  <!-- 
      check that your configuration file mp-config-xxx  are place in 
      ${env.MP_HOME}/demo/config
   
   The ouput of the generation with go to the outputdir specified in the configuration
   If none it goes in ${env.MP_HOME}/demo/output
   
   Remark:
    this sample works with Minuteproject distribution.
    The configuration relies on a hsqldb database located on the file system.
    For advanced test check that the database is up and running at the url specified 
    in the url node of the configuration
  -->
  
        <macro_generate config="mp-config-JPA2.xml" />
  
 </target>

</project> 

The result will go to MP_HOME/demo/output


Wednesday, October 16, 2013

Packt Columbus action days


For hungry minds that look to satisfy their IT knowledge, there are Packt Columbus Days  

The exclusive 50% discount code COL50 will be active on all eBooks and Videos until Thursday October 17th

Couple of books that I recommend

Thursday, October 3, 2013

minuteproject 4 propel motivations

Propel is based on some forward engineering technics that creates your Php Object Domain and ORM classes for your based on a file describing your model.

Propel ORM also offers reverse-engineering facilities to generate this input file.
Minuteproject meanwhile creates a track for propel. Why? What are the motivations?

Minuteproject 4 propel track will be delivered in version 0.8.5.

Polyglot generator


Minuteproject was essentially generating java framework oriented artifact but the php tracks with propel and soon doctrine offers another perspective.
Whatever your language and your framework, minuteproject can offer you reverse-engineering facilities.


Get Php knowledge

Testing with php was quite easy and interesting.

Propel specifics

As mentionned previously Propel offers reverse-engineering solution.

Interesting points

Their solutions is interesting because for each database Propel support the tool issue database query-specific-data-dictionnary lookup. It is the same philosophy that JOOQ framework with JOOQ meta is following.

Minuteproject 4 propel advantages

Propel reverse-engineering solution propose raw reverse-engineering, while minuteproject offers you the possibility to make a reverse-analysis before.

Enrichment

All the enrichment facilities are available for Propel track. Which means that you can have smart reverse-engineering aspects:
  • Alias for tables (DB table naming convention can differ from you Php classes conventions)
    • In schema.xml 'name' is filled by DB name and phpName by your given name.
  • Alias for columns same principle as above
  • Apply alias (column or view) globally by conventions or individually
    • Ex tables starting with T_ such as T_USER can get User in php.
    • Ex field ending with FK (ex PRODUCT_FK) have the variable $product.
  • Granularity select only tables or views you need
    • No need to reverse-engineer all the entities

Multiple artifacts

Minuteproject 4 propel generate other artifacts such as
  • runtime-conf.xml
  • build.properties
  • command line scripts
  • setup.php

Updatable code

Generator tools are often see as generate once and no more...
It is not the case of minuteproject!

The code that you generate is updatable! 
If you do not like it change it! 
If it misses something add it!

Next time you generate YOUR ALTERATIONS WILL BE KEPT!


This is call updatable code feature. And all the minuteproject 4 propel templates have this feature!


Which means that if you do not like what  you have in the schema.xml node you can change them. If your model changes add couples of db object (table/view), change cardinality between objects, the next time you generate, new object and relationship will be added BUT your modification (ex table phpName) will be kept!

Friday, September 20, 2013

nested SDD

SDD (Statement driven development) is a powerfull productivity weapon.

Until now minuteproject SDD configuration was limited to a single statement.
This is enough when you want to query to display a list or graph or even call a store proc.

But often your business need is make of nested action. The output of some query serves as the input for other actions.
This is this capacity to handle multiple successive actions based on statement that is called nested SDD.

Something concrete


Imagine the following scenario
I have DB connections that are stucked and I want to kill them. How I want to do that is via a web application.
And I want to have this application without writing a single LOC and within 1 minute...
Here is the kind of productivity challenge Minuteproject is bound to solve with nested SDD.

Decomposition of the scenario

Infrastructure

Oracle DB.

Queries

I have DB connections that are stucked and I want to kill them.
http://appsdbanew.wordpress.com/2007/11/05/how-to-find-blocking-session-and-kill-the-session-from-database/
It can be reduced in writing 2 queries:

Retrieve the connections (and stucked ones)
select process,sid, serial#, blocking_session from v$session where blocking_session is not null

Kill collections
alter system kill session '#SID,#SERIAL' immediate

Nesting

The output of the first query (sid and serial#) serves as input of the second.


Minuteproject

Those two queries show be enough to get a application in Primefaces for quite handy for the help desked.



Thursday, September 5, 2013

piping generators with minuteproject

There are couple of really interesting generators working based on some input file and execution principles.
It is easy to understand and tooling-oriented. Input 2 Tool gives Result Code.

Minuteproject principle is the same

When working with generators dealing with RDBMS persistence (Rev-Eng or Fwd-Eng), minuteproject can generate initial input for those generators.

This strategy is called generator piping.

Each generator has its own particularities and kind of DSL.
The goal is then to degrade as less as possible the 'other-generator' input in order to keep the added value as high as possible.


Minuteproject is particularily interested by the following OS generators:
  • Liferay Service Builder
  • JOOQ
  • Benerator
  • Roo (input script instructions)
  • RoR (input script instructions)
  • Propel

Tooling to deal with other generators available in minuteproject
  • Plugin
    • write a mp plugin (java bean match target technology specifics)
    • this plugin is available at template level to query target-generator-centric info.
  • Parametrization
    • offer track customization
    • a classical parameter could be the version of the target generator
  • Updatable code feature
    • tune your output without losing your altertions over consecutive generations
  • Enrichment
    • Conventions
      • Some configuration can be tedious because quite verbose, fortunately minuteproject can pin common caracteristics via conventions
    • Aliasing
      • provides indirection level to have DB names look like DB names and Java name matching Java conventions
The main difficulty is to know what is the input for field type (is it an SQL, UML, Java, own DSL type) that has to be treated at plugin level.

Wednesday, August 28, 2013

performance magnitude ft. sql java xml

This page is NOT a benchmark but the performance feeling that I experienced working with those 3 technologies.

While processing data 

SQL runs 100+ faster than JAVA which runs 100+ faster than XML


Is this impression true?
Do you have some magnitude order?

Do feed-back your impressions and results!

working with updatable code in mp4ox

Minuteproject promotes updatable code as one integration technic.
What it means is that you can change the generated code AND your modifications will be kept if you generate again (ex when your model change).

So this as the interesting effect, that minuteproject is not only for bootstrap time (the early age of your project) BUT throughout the lifecycle of your project.

Things change: code and model but it's OK you can still keep the power of reverse-engineering without losing your code.

Concretely, what does it mean when working with Minuteproject 4 Openxava?


The early age of your project - bootstrap time

Model

You have a idea, model it! the DB way (do not lose the power of your RDBMS)!
  • smart reverse-engineering (your model value more that just storing information, hunt the hidden concepts, enrich it!)
  • virtualization (use views as alternate models or graphs of views)
  • SDD (jdbc statements are sometimes enough)
  • Transient definition (DTO are welcome back for any purpose UC linked or not or partially to some persisted entities)

Generation

By default when you pick-up the target with catalog-entry="OpenXava" the resulting code will go to ../output//OpenXava

Create your OpenXava project and test your idea

If you open a prompt on your generated code directory and set/export your 2 environment variables MP_HOME and OX_HOME then you can call build-.
This will create an OX project; build it, deploy it and open your browser to a menu to access your model.

This is fine and described to create OX project.

Maturity age

Now your OpenXava project is up and running, you can extend your model, and sometimes you want to alter the generated code

Enabling updatable code

Enable your minuteproject to work and merge updated code.
You have to add
<generator-config>
	<configuration>
        <conventions>
            <target-convention type="enable-updatable-code-feature" />
        </conventions>

Working with updatable code

You can check your OX classes and xml, they contain comments.
Some comments are for extension MP-MANAGED-ADDED-AREA-BEGINNING
Some comments are for modification MP-MANAGED-UPDATABLE-BEGINNING-DISABLE to change to MP-MANAGED-UPDATABLE-BEGINNING-ENABLE when modifying the area.
If your artifact (java class, xml file) is final and you do not want to generate it again add MP-MANAGED-STOP-GENERATING

3 possibilities to alter your code are discribed here


Now you just have point Minuteproject 4 Openxava generation towards you Openxava workspace project.

Add the outputdir-root of the node targets and set the directory of your Openxava workspace project

And that's it!

Conclusion

This feature allows you to goes for continuous generation and continuous modification.
Both approach are not longer exclusive BUT complementary.
You got a development methodology to use the power of both approach:

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.

Monday, August 26, 2013

reviewing liferfay service builder for minuteproject integration


This article carries a study on Liferay service builder tooling and check the matching in minuteproject ecosystem.

The input from this article comes from:

Service builder principles

Service builder relies on forward engineering and a descriptive format of the model that complies with a dtd. So the user needs to create a valid xml file called service.xml.
Service xml main nodes are 'entity' node.
Entity node describe a persistence entity (i.e. table) for a RDBMS.
The entity description covers columns and relationships (parents and children via collection) as well as primary key strategy.
This file serves as input to an ant task that carries on the generation process. Some 20 different artifacts are generated. All of those artifacts are service-builder 'entity' node centric.

Service builder from a generation perspective

Forward-engineering

Is a approach domain centric as some advantages:
  • density of the information (a simple file to describe)

Service builder from minuteproject perspective

Minuteproject is evolving from smart reverse-engineering to a development methodology platform focusing on 4 Weapons of Mass Productivity:
  • smart reverse-engineering
  • virtualization
  • statement driven development (SDD)
  • transient definition

Service builder / Minuteproject matching

Although the philosophies (forward-engineering vs. reverse-engineering) are different there is a mapping between the both approach.

The question is are all the information in service builder available by minuteproject reverse-engineering?

YES!

Let's have a element review of service builder nodes

Service builder 

<
ATTLIST service-builder
    package-path CDATA #REQUIRED
    auto-namespace-tables CDATA #IMPLIED
>
<!ELEMENT author (#PCDATA)>
<!ELEMENT namespace (#PCDATA)
>

package-path, author, namespace can be deduced by minuteproject model node

Entity

<
ATTLIST entity
    name CDATA #REQUIRED
    human-name CDATA #IMPLIED
    table CDATA #IMPLIED
    uuid CDATA #IMPLIED
    uuid-accessor CDATA #IMPLIED
    local-service CDATA #IMPLIED
    remote-service CDATA #IMPLIED
    persistence-class CDATA #IMPLIED
    data-source CDATA #IMPLIED
    session-factory CDATA #IMPLIED
    tx-manager CDATA #IMPLIED
    cache-enabled CDATA #IMPLIED
    json-enabled CDATA #IMPLIED
>

Entity name can be deduced from table name (or view name)
Human name can be deduce by table alias name
All the other boolean field can be deduce by so convention or some parameters to set on the mp4liferay track
Datasource can be deduced by convention (related to the namespace) (and also generated by MP)

Column

<!ATTLIST column
    name CDATA #REQUIRED
    db-name CDATA #IMPLIED
    type CDATA #REQUIRED
    primary CDATA #IMPLIED
    accessor CDATA #IMPLIED
    filter-primary CDATA #IMPLIED
    entity CDATA #IMPLIED
    mapping-key CDATA #IMPLIED
    mapping-table CDATA #IMPLIED
    id-type CDATA #IMPLIED
    id-param CDATA #IMPLIED
    convert-null CDATA #IMPLIED
    lazy CDATA #IMPLIED
    localized CDATA #IMPLIED
    json-enabled CDATA #IMPLIED
>
Column info can be found by reverse-engineering name (via alias), db-name, type, primary, entity, mapping-key, mapping-table.
Id-type and id-param can be deduced from the PK strategy associated to the database type.
MP covers those described in service.xml (uuid, sequence, autoincrement, identity)

Finder and finder-column
can be deduce from defaulting (pk, fk) or with the semantic reference of the table.

References correspond to DB relationships.

Review conclusion

This means that the main information can be deduced from the smart reverse-engineering.
What is missing can be added by convention a liferay configuration for defaulting (such as companyId, groupId, finder extra info) and parametrization of the liferay track.
In all the case the configuration of service.xml (generated by MP) can be manually changed and at the next generation from the DB your alterations are kept!! Thank to updatable code feature of MP.

Can minuteproject even extend service-builder facility?

YES AGAIN!

Virtualization

This is the strong point of reverse-engineering: you can work with Views. In forward-engineering it is impossible since you cannot guess the implementation whereas in reverse-engineering, you take it as is!
So your view can be set as entity in service.xml (with readonly access)
But what is the identity field of this view. Minuteproject enrichment or convention enable to grant one to a field acting as a unique key.
Even better, minuteproject provides you the ability to grant relationships between view like parent-child foreign key and this can also be used in service.xml

Enrichment

Example: detect relationship between entity when no foreign key present.

Can minuteproject do more for service builder?

YES!

Validation - mandatory and type are not enough

You should have stereotype such as email, url, password. A stereotype is an abstraction between presentation and validation aspects.

Constraints

Limit the column value to a enumeration.

Updatable code

The code you generate can be updated via some extension points. The next time you generate your modification are kept!

Datasource

Minuteproject can generate the datasource code

Semantic reference

Sometimes you do not want to display the entire entity fields but just the more important. This is the semantic reference.

Work with Statement only

Sometimes you can go back to the fundamentals of your UCs (I/O + function).
Ex a jdbc call to a stored procedure.

DTO oriented service

The input is not mapped to a persistence entity.


Conclusion


After this feasability review, minuteproject can have the capabilities to generate service.xml of liferay from the DB. But also to extend it.
A sample generation is foreseen for MP release 0.8.5.

The first approach is to generate the SB input configuration. But nearly all generated artifacts SB generate can also be generated by MP.


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.


Monday, August 19, 2013

Northwind DB revisited with MP 4 OX

Minuteproject (0.8.4) now supports ms-sqlserver.
It has been tested with MS Northwind DB on sqlserver 2014.
Here are the steps to follow to get an OpenXava application from the northwind DB.

Goal:
Get a working Northwind Openxava application in couple of seconds.
For hungry minds please find the resulting code on googlecode.

Northwind DB

Northwind database is a sample DB provided by Microsoft.
It can be found at http://northwinddatabase.codeplex.com/releases/view/71634

The sql provided to create the schema has DB objects (tables/views) containing space.
This sql has been revisited to remove those spaces a version can be found here.

  • Install SQLServer
  • Create an account
  • Run the script.

MinuteProject with SQLSERVER

Minuteproject
  • uses com.microsoft.sqlserver.jdbc.SQLServerDriver jdbc driver
  • associates 'identity' as a primary key strategy by default on the console
  • associates org.hibernate.dialect.SQLServer2008Dialect for hibernate dialect
  • proposes maven artifact-id=sqljdbc4; group-id=com.microsoft.sqlserver; version=4.0 for pom configuration
Furthermore when retrieving the metadata of your model, set 'dbo' of the schema node.

Minuteproject configuration



<!DOCTYPE root>
<generator-config>
 <configuration>
  <model name="nortewind" version="1.0" package-root="net.sf.mp.demo">
   <data-model>
    <driver name="sqlserver" version="4.0" groupId="com.microsoft.sqlserver"
     artifactId="sqljdbc4"></driver>
    <dataSource>
     <driverClassName>com.microsoft.sqlserver.jdbc.SQLServerDriver</driverClassName>
     <url>jdbc:sqlserver://localhost:1433;databaseName=northwind</url>
     <username>sqlserver</username>
     <password>xxxxxxxx</password>
    </dataSource>
    <!-- for Oracle and DB2 please set the schema <schema> </schema> -->
    <schema>dbo</schema>
    <primaryKeyPolicy oneGlobal="true">
     <primaryKeyPolicyPattern name="identityPattern"></primaryKeyPolicyPattern>
    </primaryKeyPolicy>
   </data-model>
   <business-model>
    <business-package default="business">
        <condition type="package" database-object-type="VIEW" result="review"></condition>   
    </business-package>
    <enrichment>
     <conventions>
          <view-primary-key-convention 
            type="apply-default-primary-key-otherwise-first-one" 
            default-primary-key-names="ID" /> 
         <column-naming-convention
       type="apply-fix-primary-key-column-name-when-no-ambiguity-and-not-natural"
       default-value="ID" />
         <entity-naming-convention type="apply-field-alias-based-on-camelcase"/>
         <column-naming-convention type="apply-field-alias-based-on-camelcase"/>

      <reference-naming-convention
       type="apply-referenced-alias-when-no-ambiguity" is-to-plurialize="false" />
      <reference-naming-convention type="apply-many-to-many-aliasing" is-to-plurialize="false"/>
     </conventions>
    </enrichment>
   </business-model>

  </model>
<!-- -->    <targets catalog-entry="OpenXava">
  </targets>  
<!--    <targets catalog-entry="JPA2" >
  </targets>   -->
 </configuration>
</generator-config>


This configuration will allow you to

  • retrieve sqlserver information for the model northwind (do not forget to specify 'dbo' in schema)
  • assign identity as primary key strategy
  • separate table package from view package. (tables go in package business, views in package review)
  • apply convention
    • use camel case for field and entity (table/view)
    • associate a primary key if not present. The primary key would then be attribute to the field ID if present otherwise the first found.
    • have clean name (here not plurialized because northwind already use plural in DB object name) when possible (i.e. there is no variable name collisition - this occurs when you have more than one relationship between two objects).
  • use the track OpenXava from the catalog.

Steps

Copy this configuration to /mywork/config as northwind-OX.xml 
From a command line run model-generation.cmd/sh northwind-OX.xml 
The result will go in /mywork/output/nortewind/OpenXava

Open a prompt in /mywork/output/nortewind/OpenXava
set OX_HOME and MP_HOME (where you install Openxava and Minuteproject)
  • set OX_HOME= path-to-Openxava // export OX_HOME in linux
  • set MP_HOME= path-to-Minuteproject // export MP_HOME in linux

Run build-nortewind.cmd/sh

This should be enough to get a

Enjoy!

Screenshots

You have Openxava CRUD on all tables and selection on views

Menu entries


Select Order

 Region details
List of products (view)

MinuteProject with console

The code can also be generated with Minuteproject console, it is faster since you do not have to write any configuration. Meanwhile it is more limited since not all the convention are available on the console.

click /bin/start-console.sh/cmd
apply the following parameters



Click on generate and the output will go to /output/northwind/Openxava

You can them process with the same steps as with the configuration.


Conclusion

Minuteproject 0.8.4 offers sqlserver generation and simplifies the configuration of the tracks.
Feel free to pick up others by picking them from the drop down list or by changing the attribute catalog-entry of the node targets by one of the following value:

JPA2
JPA2-ABL
BSLA-JPA2
REST-JEE
REST-SpringMVC
REST-CXF-Spring
REST-CXF-JEE
WS-JEE
JOOQ
Primefaces-Spring
Primefaces-JEE
FitNesse
Solr
OpenXava
Grails
Play
Vaadin
Roo
Maven Spring Hibernate
Maven Spring JPA/Hibernate
SpringService

Remark some tracks are under construction.



Friday, August 9, 2013

Reviewing primefaces starter

I have been contacted to review Primefaces starter book, and here are my impressions as a Primefaces user and with a 'minuteproject' perspective.


This book is a starter targeting developers with JSF knowledge, and it is very rewarding. 
It covers Primefaces (PF) core features as well cool ones that make your application look great! such as Calendar, Google map, interactive chat, reporting diagram.
It is based on concrete data model with a github source code ready to clone and package seamlessly. All the installation procedure and tooling is described making your environment setup easy. This starter spurs up you curiosity to dive in to the code while the chapter provides you the substantial insights of PF JSF components. The result is a comprehensive advanced application based on non-trivial model spanning from CRUD, JEE (JPA/CDI) to advanced PF component.
Another aspect that I liked was the density of information: it sticks to the essential, no fuss, no dozens-of-API-page syndrome.


From a minuteproject perspective, and seeing Primefaces as a major actor in the presentation framework, I am always interested to grab extra bits of info on the subject.
I was particularly interested in the cool features (Calendar, Maps) since I think that with SDD (Statement Driven Development ) some of those could be generated. SDD can be already used in PF to produce graphs

So it was quite an enjoyable review.


Thursday, August 8, 2013

Minuteproject facets for analysts

This article resumes the features Minuteproject offers to the analyst to perform a reverse-analysis of the application model.

Core

Minuteproject core functionality is smart reverse-engineering from a database.
  • Reverse-engineering means that you can deduce lots of artifacts based on the database schema structure.
  • Smart means that you can enrich the database elements individually or globally by convention to get the resulting artifacts finer (with added-value) instead of a default bulk solution. Examples:
    •  Your database objects can be:
      • packaged
      • secured
      • aliased (the name of your development object (in java) can follow different convention than those of your DB
Sample
http://minuteproject.blogspot.be/2012/05/rigajug-demo-2-jpa2.html
But Minuteproject is even more.

Views and alternative models

You can work with views conveniently by enriching them with primary key (a unique field will do) and foreign key (pointing to another table or view). This feature enables you to get quickly alternative model with a graph (parent-child relationship) that you can query.
An illustration is when you want to get a report grouping information per country/dept on one side and on the other side you have some report details.

SDD - Statement driven development

Why just reverse-engineer the structure? We can also reverse-engineer the statement (queries, store-proc calls etc...)
Statement-driven-development focuses on I/O: it goes back to the fundamentals and follow the philosophy WYSIWYG (What You State Is What You Get)
If you know how-to write sql, you are not far from having a REST application or any application in the track that support this feature (at the moment of writing Primefaces, ADF).

Sample
http://minuteproject.blogspot.be/2013/08/sdd-as-productivity-weapon-4-openxava.html

Transfer Objects

Sometimes, you need to have object that may not be persisted or completely persisted.
Example you want ad-hoc forms and actions where some info comes from the database some not.
Minuteproject offers you the possibility to describe and generate for those objects.

Sample
http://minuteproject.blogspot.be/2013/08/transient-definition-as-productivity.html



Monday, April 29, 2013

How-to cloudbees minuteproject 4 primefaces

Introduction

Cloudbees offers JEE app and Mysql DB storage.
Minuteproject generates CRUD and reporting application for Primefaces based on a database model.

This article is based on upon the petshop mysql database.
The result can be show on cloudbees at http://petshopapp.minuteproject.cloudbees.net

The application, minuteproject configuration and DB model can be found on google code at https://code.google.com/p/minuteproject/downloads/detail?name=petshop-mp4primeface-cloudbees.zip

Prerequisits

Download and install Minuteproject set MP_HOME
Download and install Cloudbees SDK set BEES_HOME
Create a cloudbees account
Mysql local DB running with a schema.

Steps

In order to get Cloudbees Primefaces application quickly follow the next steps.

Cloudbees setups

Cloudbees setup can be done on the website, but also by the following command lines

Cloudbees app

$bees app:create petshopapp

Cloudbees  DB

$bees db:create petshopappDB -u username -p password

Cloudbees binding

$bees app:bind -db petshopappDB -a petshopapp -as petshopAppDS

Deployment on Cloudbees

$bees app:deploy -a account/petshopapp -t tomcat7 path-to-your-app.war
This step is performed after you have build your app.

Install you DB schema

Get the server connection information:
  • server
  • port
  • user_name
  • pass_word

$mysql --user=user_name --password=pass_word --host=server --port=port < schema.sql

Minuteproject 4 Primefaces app

To create a primefaces application, minuteproject can generate one based on a database structure.
Minuteproject runs with a command line.
The configuration file used is petshop-mp4primeface-cloudbees.zip 

Configuration

The configuration file can be found in the zip under mp-config-JSF-Spring.xml
The main points of the file are
<!DOCTYPE root>
<generator-config xmlns="http://minuteproject.sf.net/xsd/mp-config" 
xmlns:xs="http://www.w3.org/2001/XMLSchema-instance" 
xs:noNamespaceSchemaLocation="mp-config.xsd">
    <configuration>
  <conventions>
   <target-convention type="enable-updatable-code-feature" />
  </conventions> 
  <model name="petshop" 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/petshop</url>
                    <username>root</username>
                    <password>mysql</password>
                </dataSource>
                <primaryKeyPolicy oneGlobal="false">
                    <primaryKeyPolicyPattern name="autoincrementPattern"></primaryKeyPolicyPattern>
                </primaryKeyPolicy>
   </data-model>
   <business-model>
    <generation-condition>
     <condition type="exclude" startsWith="DUAL"></condition>
     <condition type="exclude" startsWith="ID_GEN"></condition>
     <condition type="exclude" startsWith="SEQUENCE"></condition>
    </generation-condition>  
    <business-package default="pet">
     <condition type="package" startsWith="PRODUCT" result="product"></condition>
     <condition type="package" startsWith="ITEM" result="product"></condition>
    </business-package> 
    <enrichment>
       <conventions>
          <entity-naming-convention type="apply-strip-table-name-prefix" pattern-to-strip="SYS,FIN"/>
          <reference-naming-convention type="apply-referenced-alias-when-no-ambiguity" is-to-plurialize="true"></reference-naming-convention>
       </conventions>
        <package name="product">
         <entity-group entities="PRODUCT"></entity-group>
         <entity-group entities="ITEM"></entity-group>
        </package>
     <entity name="PRODUCT" alias="MY_GOOD_PRODUCT">    
     </entity>        
     <entity name="ITEM" alias="MY_GOOD_ITEM" comment="my item table">
      <field name="PRODUCTID" alias="THIS_IS_MY_PRODUCT" comment="my product field reference"></field>     
     </entity>        
     <entity name="CATEGORY" content-type="reference-data" >
        <field name="DESCRIPTION" ordering="asc" label="my description" is-searchable="true"></field>
        <field name="NAME" ordering="asc" ></field>
        <semantic-reference>
          <sql-path path="NAME"/>
        </semantic-reference>
     </entity>
    </enrichment>     
   </business-model>
            <statement-model>
                <queries>
                     <query name="get address abstract" id="dashAddress" type="dashboard" category="pie-chart">
                         <query-body> <!-- dimensions column first -->
                         <value>
<![CDATA[select city, count(*) as nb from address group by city order by count(*) desc limit ? ]]>
                            </value>
                         </query-body>
                         <query-params>
                             <query-param name="top city" is-mandatory="false" type="INT" sample="37" default="10"></query-param>
                         </query-params>
                     </query>
                     <query name="get address summary" id="dashCity" type="dashboard" category="bar-chart">
                         <query-body> <!-- dimensions column first -->
                         <value>
<![CDATA[select city, count(*) as nb, count(*) as nb2 from address group by city order by count(*) desc]]>
                            </value>
                         </query-body>
                     </query>
                     <query name="get addresses by criteria" id="c">
                         <query-body><value>
<![CDATA[select * from address where lcase(city) like ?]]>
                            </value>
                         </query-body>
                         <query-params>
                             <query-param name="city" type="STRING" sample="'S'" convert="lowercase,append%" default="%">
                             </query-param>
                         </query-params>
                     </query>
                </queries>
            </statement-model>
  </model> 
The database driver is mysql and the primary key strategy is autoincrement for not natural primary key
Product and Item tables have been aliased
Product and Item tables goes in product package, which means graphically a distinct menu access.
Category is a reference-data table, which means caching and access via drop down list

Statement:
Misc statement queries can be used to produce report in forms of list, pie-chart, bar-chart.

Generate

Copy the file in $MP_HOME/mywork/config From a prompt run
$model-generation mp-config-JSF-Spring.xml

Build

The generation result is in $MP_HOME/dev/JSF/petshop From a prompt run
$mvn clean package

Customise package for cloudbees


Cloudbees context

Optional step to perform if your not creating the binding between the Application and DB.

Add a Cloudbees file context.xml

In /WEB-INF/lib add context.xml in war/META-INF
<Context>
<Resource name="jdbc/petshopAppDS"
            auth="Container"
            type="javax.sql.DataSource"

            url="jdbc:${DATABASE_URL}"
            username="${USER_NAME}"
            password="${PASS_WORD}"

            driverClassName="com.mysql.jdbc.Driver"

            maxActive="20"
            maxIdle="1"
            maxWait="10000"
            removeAbandoned="true"
            removeAbandonedTimeout="60"
            logAbandoned="true"

            validationQuery="SELECT 1"
            testOnBorrow="true"
            />
</Context>

The ${DATABASE_URL} is retrieved from 

$bees db:info petshopapp

Persistence.xml

adapt persistence.xml with Cloudbees binding alias
change non-jta-data-source node with java:comp/env/jdbc/petshopAppDS
remove properties hibernate connection info

The persistence.xml is located in petshopBackEnd-1.0-SNAPSHOT.jar!/META-INF

<?xml version="1.0"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
             version="1.0">
<!--MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @PERSISTENCE-UNIT-petshop@-->
    <persistence-unit name="petshop">
<!--MP-MANAGED-UPDATABLE-ENDING-->
<non-jta-data-source>java:comp/env/jdbc/petshopAppDS</non-jta-data-source>
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
        <!-- pet --> 
        <class>net.sf.mp.demo.petshop.domain.pet.Address</class>
        <class>net.sf.mp.demo.petshop.domain.pet.Category</class>
        <class>net.sf.mp.demo.petshop.domain.pet.Sellercontactinfo</class>
        <class>net.sf.mp.demo.petshop.domain.pet.Tag</class>
        <class>net.sf.mp.demo.petshop.domain.pet.Ziplocation</class>

        <!-- product --> 
        <class>net.sf.mp.demo.petshop.domain.product.MyGoodItem</class>
        <class>net.sf.mp.demo.petshop.domain.product.MyGoodProduct</class>


        <properties>
            <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>
            <property name="hibernate.cache.provider_class" value="net.sf.ehcache.hibernate.SingletonEhCacheProvider" />
            <property name="net.sf.ehcache.configurationResourceName" value="/ehcache.xml" />
            <property name="hibernate.cache.use_query_cache" value="true" />
            <property name="hibernate.cache.use_second_level_cache" value="true" />
<!--MP-MANAGED-ADDED-AREA-BEGINNING @properties@-->
<!--MP-MANAGED-ADDED-AREA-ENDING @properties@-->
        </properties> 


<!--MP-MANAGED-ADDED-AREA-BEGINNING @persistence-unit@-->
<!--MP-MANAGED-ADDED-AREA-ENDING @persistence-unit@-->
    </persistence-unit>

<!--MP-MANAGED-ADDED-AREA-BEGINNING @persistence@-->
<!--MP-MANAGED-ADDED-AREA-ENDING @persistence@-->

</persistence>


Deploy

$bees app:deploy -a account/petshopapp -t tomcat7 JSF/target/petshopApp.war
where account is the account you created

Tomcat7 target ensure the EL libraries are present.

Result


Improvements

For those who think it's a bit long, they are right!
Most of the step could be simplified since most of it could be generated!

Minuteproject will add a cloud track with cloudbees for that in the next release.