Heads up! These docs are for Portofino 3, which is a legacy product. Check out Portofino 4!

Posted by Giampiero Granatella
on 28th June, 2011


As a developer of applications with ManyDesigns Portofino, I often run into a problem, that we commonly call "constrained relationships". It happens when a relationship has a semantic constraint with another one. I solved this problem creating custom pages in Portofino as I'm going to show you in this tutorial.

This tutorial is intended to be for an "advanced user" who knows well ManyDesigns Portofino and has started to use our framework for building real applications.

The problem

In a model it often happens that a relationship has a semantic constraint with another. You notice those patterns finding circularities in you Entity-Relationship diagram as in the following picture.

In the above picture I model projects, tasks and persons. A project has many persons as participants (many-to-many relationship through the class participant) and has several tasks. A task has a task leader, which is an other relationship with the 'person' class. This is strictly the model, but we want to add a semantic constraint in the task leader relationship: in a real environment the task and the task's leader must belong to the same project (the semantic constraint).

Let's make an example. I've implemented the model in Portofino and put some data in the application. I've created some projects, persons and participants. In the following picture you see a project Myproj with two participants Charles King and  Jeff Moore.

Now I create a task and Portofino correctly show for the task leader all the persons in the application and not only the participants of the project Myproj.

To solve the problem I have to customize our application writing a custom page to replace the standard pages for creating and updating tasks.

Writing custom pages

If you don't know how to create custom pages, please first read Paolo's tutorial Writing custom pages (part 1).

Portofino uses the framework Apache Struts 2, so we are going to create an Action CreateTaskAction that extends com.manydesigns.portofino.methods.scrud.Create  the original action in the Portofino Framework. Portofino actions create the elements that compose the forms, the basic idea is to substitute the possible options in the select component.

Below you'll find the source code for CreateTaskAction.java. I override the execute method (line 28). In this method I call the super.execute() and save the result, which I'll use later (line 65).

The super.create() instantiates the form. Forms and their components (as the select field we want to modify) are modeled with our library com.manydesigns.elements. I am going to search the component who access the property "task_leader" and change its options (lines 53-64).

First of all I create an OptionProvider which collects all the options (lines 32-33). To populate the options I make a query on the database (lines 34-62), which has the semantic of the relationship among participants, projects and tasks ("select B.id, B.surname ||' '|| B.name from model.participant A inner join model.person B on A.person=B.id where project = ?").

Now I create a new SelectField (line 56) an element that insists on Task property task_leader, I set the OptionProvider previously created (line 58), and, finally, I replace the new component in place of the old one (line 62).

CreateTaskAction.java

01 package mywebapp.actions;
02 import com.manydesigns.elements.fields.*;
03 import com.manydesigns.elements.forms.FieldSet;
04 import com.manydesigns.elements.reflection.PropertyAccessor;
05 import com.manydesigns.portofino.base.MDClass;
06 import com.manydesigns.portofino.base.MDRelAttribute;
07 import com.manydesigns.portofino.base.Transaction;
08 import com.manydesigns.portofino.base.reflection.MDRelAttributeAccessor;
09 import com.manydesigns.portofino.methods.scrud.Create;
10 import com.manydesigns.portofino.util.Util;
11 import java.sql.PreparedStatement;
12 import java.sql.ResultSet;
13 import java.util.ArrayList;
14 import java.util.Collection;
15 /**
16  * @author Giampiero Granatella - giampiero.granatella@manydesigns.com
17  */
18 public class CreateTaskAction extends Create {
19     public static final String copyright =
20             "Copyright (c) 2005-2010, ManyDesigns srl";
21     private static String sqlPartecipantiPrj = "select B.id, B.surname ||' '|| B.name" +
22             " from 22 model.participant A " +
23             " inner join model.person B on A.person=B.id " +
24             "where project = ?";
25
26     public Integer attr_project;
27
28     public String execute()  {
29         String result = super.execute();
30         FieldSet fs = form.get(0);
31         MDClass taskCls = config.getMDClassByName(className);
32         Collection<SelectFieldOption> options = new ArrayList<SelectFieldOption>();
33         OptionProvider optionProvider = new DefaultOptionProvider(options);
34         Transaction tx = config.getCurrentTransaction();
35         PreparedStatement ps = null;
36         ResultSet rs = null;
37         try {
38             ps = tx.prepareStatement(sqlPartecipantiPrj);
39             ps.setInt(1, attr_project);
40             rs = ps.executeQuery();
41             while (rs.next()){
42                 options.add(new DefaultSelectFieldOption(
43                        new Integer(rs.getInt(1)).toString(),
44                        rs.getString(SELECT_COMPONENT_INDEX), null));
45             }
46         } catch (Exception e) {
47             e.printStackTrace();
48             throw new Error(e);
49         }
50         finally {
51             Util.closeResultSetAndStatement(rs, ps);
52         }
53         PropertyAccessor accessor =
54                     new MDRelAttributeAccessor((MDRelAttribute)
55                             taskCls.getAttributeByName("task_leader"));
56         SelectField attSelect = new SelectField(accessor);
57         attSelect.setComboLabel("-- select the leader --");
58         attSelect.setOptionProvider(optionProvider);
59         for (int i=0; i < fs.size(); i++){
60             AbstractField af = (AbstractField) fs.get(i);
61             if (af.getAccessor().getName().equals(accessor.getName())){
62                 fs.set(i, attSelect);
63             }
64         }
65         return result;
66     }
67 }

Now that I made the "create", I'll do exactely the same thing for "update" as I'll show you the code below.

UpdateTaskAction.java

01 package mywebapp.actions;
02 import com.manydesigns.elements.fields.*;
03 import com.manydesigns.elements.forms.FieldSet;
04 import com.manydesigns.elements.reflection.PropertyAccessor;
05 import com.manydesigns.portofino.base.MDClass;
06 import com.manydesigns.portofino.base.MDRelAttribute;
07 import com.manydesigns.portofino.base.Transaction;
08 import com.manydesigns.portofino.base.reflection.MDRelAttributeAccessor;
10 import com.manydesigns.portofino.methods.scrud.Update;
11 import com.manydesigns.portofino.util.Util;
12 import java.sql.PreparedStatement;
13 import java.sql.ResultSet;
14 import java.util.ArrayList;
15 import java.util.Collection;
16 /*
17 * @author Giampiero Granatella - giampiero.granatella@manydesigns.com
18 */
19 public class UpdateTaskAction extends Update{
20     public static final String copyright =
21             "Copyright (c) 2005-2011, ManyDesigns srl";
22     private static String sqlPartecipanti = "select B.id, B.surname ||' '|| B.name from
23          model.participant A " +
24             " inner join model.person B on A.person=B.id inner join model.task C " +
25             " on A.project=C.project where C.id=? ";
26
27     public String execute()  {
28         String result = super.execute();
29         FieldSet fs = form.get(0);
30         MDClass taskCls = config.getMDClassByName(className);
31
32         Collection<SelectFieldOption> options = new ArrayList<SelectFieldOption>();
33         OptionProvider optionProvider = new DefaultOptionProvider(options);
34         Transaction tx = config.getCurrentTransaction();
35         PreparedStatement ps = null;
36         ResultSet rs = null;
37         try{
38             ps = tx.prepareStatement(sqlPartecipanti);
39             ps.setInt(1, id);
40             rs = ps.executeQuery();
41             while (rs.next()){
42                 options.add(new DefaultSelectFieldOption(
43                         new Integer(rs.getInt(1)).toString(),
44                         rs.getString(SELECT_COMPONENT_INDEX), null));
45             }
46         } catch (Exception e) {
47             e.printStackTrace();
48             throw new Error(e);
49         }
50         finally {
51             Util.closeResultSetAndStatement(rs, ps);
52         }
53         PropertyAccessor accessor =
54                     new MDRelAttributeAccessor((MDRelAttribute)
55                             taskCls.getAttributeByName("task_leader"));
56         SelectField attSelect = new SelectField(accessor);
57         attSelect.setComboLabel("-- select the leader --");
58         attSelect.setOptionProvider(optionProvider);
59         for (int i=0; i < fs.size(); i++){
60             AbstractField af = (AbstractField) fs.get(i);
61             if (af.getAccessor().getName().equals(accessor.getName())){
62                 fs.set(i, attSelect);
63             }
64         }
65         return result;
66     }
67 }

Since I wrote two new Struts actions, I have to map them in struts.xml. The new struts.xml extends portofino-default (line 06). But it overrides  the mapping for task/Create (lines 14-35) and task/Update (lines 36-57). If you'll look at the orginal portofino-default (in the file struts-plugin.xml), you'll notice that I only changed the class, e.g. from com.manydesigns.portofino.methods.scrud.Create to mywebapp.actions.CreateTaskAction.

struts.xml

01<!DOCTYPE struts PUBLIC 
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
02    "http://struts.apache.org/dtds/struts-2.0.dtd">
03<struts>
04    <constant name="struts.action.extension" value="action"/>
05    <constant name="struts.locale" value="en"/>
06    <package name="mywebapp" extends="portofino-default" namespace="/">
07        <result-types>
08     <result-type name="chart" class="org.apache.struts2.dispatcher.ChartResult">
09     <param name="height">150</param>
10     <param name="width">200</param>
11     </result-type>
12     </result-types>
13        <!-- Azioni Custom -->
14        <action name="task/Create" class="mywebapp.actions.CreateTaskAction">
15            <param name="className">task</param>
16             <result name="input">
17                /WEB-INF/jsp/create.jsp
18            </result>
19            <result name="success" type="redirect">
20                <param name="location">${successReturnUrl}</param>
21                <param name="prependServletContext">false</param>
22            </result>
23            <result name="cancel" type="redirect">
24                <param name="location">${cancelReturnUrl}</param>
25                <param name="prependServletContext">false</param>
26            </result>
27            <result name="read" type="redirectAction">
28                <param name="actionName">task/Read</param>
29                <param name="id">%{idTask}</param>
30            </result>
31            <result name="updateWorkflow" type="redirect">
32                <param name="location">${updateWorkflowUrl}</param>
33                <param name="prependServletContext">false</param>
34            </result>
35        </action>
36        <action name="task/Update" class="mywebapp.actions.UpdateTaskAction">
37            <param name="className">task</param>
38             <result name="input">
39                /WEB-INF/jsp/update.jsp
40            </result>
41            <result name="success" type="redirect">
42                <param name="location">${successReturnUrl}</param>
43                <param name="prependServletContext">false</param>
44            </result>
45            <result name="cancel" type="redirect">
46                <param name="location">${cancelReturnUrl}</param>
47                <param name="prependServletContext">false</param>
48            </result>
49            <result name="read" type="redirectAction">
50                <param name="actionName">sal/Read</param>
51                <param name="id">%{idTask}</param>
52            </result>
53            <result name="updateWorkflow" type="redirect">
54                <param name="location">${updateWorkflowUrl}</param>
55                <param name="prependServletContext">false</param>
56            </result>
57        </action>
58    </package>
59</struts>

Stop and start you application and you'll find few option to choose the task leader. Now persons may become task leader only if they participate to the project.

Conclusions

Constrained relationships are a common situation in developing real application with Portofino. In this example I show you how to discover the pattern of a constrained relationship in the model and how to customize your application to overcome this problem.

This tutorial is also a starting point to make deeper customization in your application.