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

Posted by Paolo Predonzani
on June 29th, 2010


What if you want to go beyond the standard functionality of Portofino? What if you want a page that, from a perspective of interaction, layout and graphics, is designed based on very specific requirements?
The answer is that you need one or more custom pages. In this tutorial I'll show how this can be done by using the power of Struts 2, the MVC framework underlying Portofino.

The requirements

Suppose that we want a custom page to display the current date and time. Something like the following screenshot:

First of all we want the custom page to be accessible at the url http://localhost:8080/mywebapp/date-time.action (assuming your webapp is deployed at http://localhost:8080/mywebapp/ ).

Also we want the page to be linked from Portofino. So we need a way to add a custom link to the standard pages of Portofino.

Finally, while the functionality is very simple (printing the date/time), we want the page to be integrated in the look and feel of Portofino.

Setting up the project

First of all, if you're really impatient, you can download the source code of this tutorial from here. If you're not impatient, keep reading...

Portofino uses Maven for dependency and build management, so we recommend it also for customization projects. Most Java IDE's (IntelliJ IDEANetBeansEclipse) either support Maven natively or can synchronize with Maven projects. You shouldn't have any problems opening a pom.xml, regardless of the IDE you use. If you have any doubts about this, you can post a question in the public forums.

To learn or refresh the basics of a customization project make sure you read the tutorial on Using Maven overlays to build customized applications.

What we're aiming at is a simple file/directory structure like the following:

./pom.xml
./src
./src/main
./src/main/java
./src/main/java
./src/main/resources
./src/main/webapp

To create the directories you can type:

$ mkdir -p src/main/java/mywebapp
$ mkdir -p src/main/resources
$ mkdir -p src/main/webapp

To create the pom.xml, open a text editor and copy the following template:

<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                      http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.mycompany.app</groupId>
  <artifactId>mywebapp</artifactId>
  <packaging>war</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>mywebapp Maven Webapp</name>
  <url>http://maven.apache.org</url>
  <dependencies>
    <dependency>
      <groupId>com.manydesigns</groupId>
      <artifactId>portofino-war</artifactId>
      <version>3.1.0</version>
      <type>war</type>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>com.manydesigns</groupId>
      <artifactId>portofino-jar</artifactId>
      <version>3.1.0</version>
      <type>jar</type>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
  <build>
    <finalName>mywebapp</finalName>
  </build>
</project>

To get a fully working instance of Portofino you'all also have to create a database and provide asrc/main/resource/portofino-custom.properties file. Here is a sample that you can copy is you're using PostgreSQL and your database is called "tutorial":

database.jdbc.driverClass=org.postgresql.Driver
database.jdbc.connectionURL=jdbc:postgresql://localhost:5432/tutorial
database.jdbc.username=username
database.jdbc.password=password

Finally type:

mvn clean install

Maven will start and output lots of messages. If the last lines look like this:

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 10 seconds
[INFO] Finished at: Wed Jun 23 16:44:20 CEST 2010
[INFO] Final Memory: 10M/19M
[INFO] ------------------------------------------------------------------------

... then your project is correctly set up. The result is a file called mywebapp.war in the target directory. You can deploy that war to an application server in the same way as you would for the standard portofino.war.

Creating the custom operation in Portofino

Custom operations allow you add a link in Portofino to point to a custom page. There are four types of operations:

  • global operations
  • class operations
  • object operations
  • object set operations

Depending of the type of operation, the link (which actually is a button in the last two cases) will be placed at different points on the screen. In this tutorial we'll use a global operation, which is able to display the link as a tab in the main tab panel.

To create the operation:

  • go upstairs,
  • click on the "Operations" tab,
  • click on the "create" link,
  • select "Global operation" from the drop down list,
  • fill in the following values:
    • Name: Date and time
    • Link: /date-time.action
    • Order: leave this blank
    • Tab: check this checkbox
  • click on the "Create" button.

Now go downstairs. You should see a new tab like the following:

Click on the tab. The server will return a "HTTP Status 404 - There is no Action mapped for namespace / and action name date-time" error. At the moment this is correct since we haven't implemented the operation's action, which is the next thing we're going to do.

Creating the Struts 2 action

An action is fundamentally a URL of the application that performs some business logic and presents some HTML output.
Creating an action in Struts2 involves three steps:

  • Creating the mapping, i.e., associating a URL to a Java class for the business logic and to a jsp for the HTML result.
  • Creating the action class in Java
  • Creating the jsp

Let's see these in greater detail.

Creating the mapping in struts.xml

The most important configuration in Struts 2 is the struts.xml file. In our project it will be located atsrc/main/resources/struts.xml . Create/open the file in your favorite text editor and use the following lines as a template:
 

<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
    "http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
    <package name="mywebapp" extends="portofino-default" namespace="/">
        <action name="date-time" class="mywebapp.DateTimeAction">
            <result name="success">/date-time.jsp</result>
        </action>
    </package>
</struts>

As you can see, the xml contains four nested elements:

  • The struts element: the root of the document.
  • The package element: a container of related actions.
    • The "name" attribute can be anything you like. Here: "mywebapp" to remind us of the project name.
    • The "extends" attribute says that this package will use some defaults (like the interceptor stack) from the "portofino-default" package already present in Portofino.
    • The "namespace" attribute with value "/" means that any action will be mapped right after the application context.
  • The action element: an action mapping.
    • The "name" is name of the action. The full action url will be the composition of the application context, of the namespace, of the action name and of the ".action" suffix. In this case: http://localhost:8080/mywebapp/date-time.action
    • The "class" attribute is the name of the Java action class, to be implemented.
  • The result element: a result of the action.
    • The "name" attribute is the value ("success") that the action class will use to indicate the desired result.
    • The content of the element ("/date-time.jsp") is the name of the result's jsp, to be implemented.

Implementing the action class

The first version of the action class is straightforward: it generates a string variable called "dateTime" and selects the "success" result.
Copy the following text to the file src/main/java/mywebapp/DateTimeAction.java.

package mywebapp;

import java.util.Date;
import com.opensymphony.xwork2.ActionSupport;

public class DateTimeAction extends ActionSupport {

    //------------------------------------------------------------------
    // Attributes made accessible to the jsp
    //------------------------------------------------------------------
    public String dateTime;

    //------------------------------------------------------------------
    // Action method
    //------------------------------------------------------------------
    public String execute() {
        Date now = new Date();
        dateTime = now.toString();

        return "success";
    }
}

Notice that we declare dateTime public so the jsp will be able to access its value using the s:property tag.

Implementing the jsp

For a first version of the jsp, copy the following text to the file src/main/webapp/date-time.jsp.

<%@ page contentType="text/html;charset=ISO-8859-1"
         language="java"
         pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<s:property value="dateTime"/>

The first tag ("<%@ page ...") is standard for a jsp.
The second tag ("<%@ taglib ...") makes the Struts 2 tag library available in this page.
The third tag ("<s:property ...") prints the value of the public field dateTime taken from the action class.

Testing the (partial) result

Now you can save, build and re-deploy the application. Again, visit http://localhost:8080/mywebapp/ and click on the "Date and time" tab. This time you should see a simple page like this:
 

The page is working and shows that we are using Struts 2 correctly. However, it is minimal. Now we need to give it a bit of style or actually, according to the requirements, integrate it in Portofino's look and feel. This is the subject of the next section.

Applying the standard Portofino look and feel

Like in many other web applications, the general graphical appearance of Portofino is given by two jsp files ("WEB-INF/jsp/header.jsp" and "WEB-INF/jsp/footer.jsp" in the distribution war) that can be included or reused in any page. Let's modify date-time.jsp to use them:

<%@ page contentType="text/html;charset=ISO-8859-1"
         language="java" pageEncoding="ISO-8859-1"%><%@ taglib prefix="s"
         uri="/struts-tags"
%><s:include value="/WEB-INF/jsp/header.jsp"/>
<div id="body">
 <div id="title">
      <h1>Date and time</h1>
 </div>
 <div id="details">
      <s:property value="dateTime"/>
 </div>
</div>
<s:include value="/WEB-INF/jsp/footer.jsp"/>

Notice that we've used the Struts 2 tag "<s:include ..." to include the header and footer.
We've also added a few div's and a title to the page.

However, despite these changes, if we redeploy the application we can notice that very little has change, as if the header and footer had no effect.
The reason for this is that the header/footer are very dynamic depending on Portofino's configuration, so we need to bind the action to Portofino's main run-time configuration object: the MDConfig.

Without getting too much into the details, here is a second version of the action that is "aware" of theMDConfig. In fact, it implements the MDConfigAware interface:
 

package mywebapp;

import com.manydesigns.portofino.methods.MDConfigAware;
import com.manydesigns.portofino.base.MDConfig;
import com.opensymphony.xwork2.ActionSupport;

import java.util.Date;

public class DateTimeAction extends ActionSupport implements MDConfigAware {

    //------------------------------------------------------------------
    // MDConfigAware implementation
    //------------------------------------------------------------------
    public MDConfig config;

    public void setConfig(MDConfig config) {
        this.config = config;
    }

    //------------------------------------------------------------------
    // Attributes made accessible to the jsp
    //------------------------------------------------------------------
    public String dateTime;

    //------------------------------------------------------------------
    // Action method
    //------------------------------------------------------------------
    public String execute() {
        Date now = new Date();
        dateTime = now.toString();

        return "success";
    }
}

This time, redeploying the application, the page will look significantly different:
 

Better, but no tabs and no footer links yet. This time, what is missing is the Navigation object, which must be instanciated by the action and made available to the jsp. This brings us to the third, last version of the action:
 

package mywebapp;

import com.manydesigns.portofino.methods.MDConfigAware;
import com.manydesigns.portofino.methods.navigation.Navigation;
import com.manydesigns.portofino.base.MDConfig;
import com.opensymphony.xwork2.ActionSupport;

import java.util.Date;

public class DateTimeAction extends ActionSupport implements MDConfigAware {

    //------------------------------------------------------------------
    // MDConfigAware implementation
    //------------------------------------------------------------------
    public MDConfig config;

    public void setConfig(MDConfig config) {
        this.config = config;
    }

    //------------------------------------------------------------------
    // Attributes made accessible to the jsp
    //------------------------------------------------------------------
    public String dateTime;
    public Navigation navigation;

    //------------------------------------------------------------------
    // Action method
    //------------------------------------------------------------------
    public String execute() {
        Date now = new Date();
        dateTime = now.toString();

        // set-up the navigation
        navigation = new Navigation(config, null, null, null);

        return "success";
    }
}

This produces the desired result:

Conclusions

Setting up a custom global action is one of the simplest things that can be done to extend Portofino. Of course more interactive and interesting features can be achieved by using class, object and object set operations. But this will be the subject of a future tutorial.

As a further reading, I suggest... Portofino's source code. It is always an good source of inspiration (for action mappings, classes, and jsp's) if you don't know where to start.

For instance, Portofino has a struts-plugin.xml which demonstrates several features and tricks in Struts 2. At first, the struts-plugin.xml can be daunting for its size, but if you break it down into packages and single actions, there is a lot to be learned there. You can find it under portofino/portofino-jar/src/main/resources/struts-plugin.xml in the source distribution.

If you want to look at the Portofino's action classes, you can get their names from the struts-plugin.xml. For instance the action class for Portofino's homepage can be found at portofino/portofino-jar/src/main/java/com/manydesigns/portofino/methods/Index.java 

Finally, if you're looking for the jsp's, they're all under portofino/portofino-war/src/main/webapp/WEB-INF/jsp, while those that are specific to the upstairs level are under the upstairs sub-folder.

If you have any questions or doubts, let me know in the community forums.

Attachments:
writing-custom-pages-part-1.zip
3.9K view download