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

Posted by Giampiero Granatella
on March 30, 2009


In this tutorial, I'll explain how to integrate Portofino with other systems in an enterprise environment. Portofino has several integration features: Listeners, Custom workflow Actions, Custom operations. In this tutorial we'll see:

  • a simple listener to send email notifications, 
  • a more complex workflow action to store content in Alfresco.

Alfresco (http://www.alfresco.com) is an Enterprise content manager (ECM), a tool used to capture, manage, store, and deliver content and documents related to an organization and it's processes.


Before we start

Before we start, I suggest that you read:

You also need:

  • a Portofino up and running at http://127.0.0.1:8080/portofino

  • an Alfresco up and running at http://127.0.0.1:8080/alfresco

Adapt these URLs your installation.

A “Job Application” demo

We'll make a simple example: a “Job Application” to store candidates with their personal data and curricula. The demo application will have a simple workflow to manage its state: "submitted" when it is inserted and still needs to be evaluated; after an evaluation it can be “accepted” or "rejected".

We also want that:

  • when a user inserts a curriculum, the application sends an email to him,

  • when a curriculum is accepted, Portofino stores it in Alfresco.

Let's start to model the app. I used this portofino-custom.properties to connect to a derby db. 

portofino-custom.properties

database.configurationType=jdbc
database.jdbc.driverClass=org.apache.derby.jdbc.EmbeddedDriver
database.jdbc.connectionURL=jdbc:derby:demodb
model.application.name=Job Application Demo
mail.smtp.host=mail.example.com

Now connect to your instance (http://127.0.0.1:8080/portofino) and create a class “job_application”, with the following attributes:

  • surname - TEXT attribute,

  • name - TEXT attribute,

  • email - TEXT attribute,

  • cv -:BLOB attribute,

  • state: - WORKFLOW attribute, with the following states and transition.

    • States are: “submitted”, “accepted” and “rejected",

    • transitions are: “accept” (from “submitted” to "accepted”), and “reject” (from “submitted to “rejected”)

Until now we have used the basic features of Portofino, but we still need the mail notification and the integration with Alfresco which cannot be obtained without coding.

Let's consider first the email notification. For this purpose we can use a listener.

Sending an email notification through a Portofino listener

In Portofino a listener allows you to intercept (create, update, delete) operations on objects, in a similar way to triggers in relational databases. To create our listener:

  • upstairs, click on the tab “classes” and then on “job_application”

  • click on “Add class listener”

  • Select the type of Listener between “Java Class Listener” and “Script Class Listener” (see the reference manual). We'll use a “Script Class Listener.

  • Enter the name of the listener (e.g. “Notify”)

  • Now add the following BeanShell script for the event “create pre script”, which will send an email after the creation of a job application.

create pre script” for job_application

String surname = obj.getTextAttribute("surname");
String email = obj.getTextAttribute("email");
String server = "smtp.example.com";
com.manydesigns.portofino.base.email.EMailTask em =
    new com.manydesigns.portofino.base.email.EMailTask(config.getConfigContainer());
String body = "Dear "+surname+", \n we've received your cv.\nHugs and kisses";
String subject = "We receveid your job application";
em.addEmail(new java.util.Date(),
            sender@example.com",
            email,
            body,
            subject);

Inside the script, the following variables are pre-set by Portofino: obj (the object for which the listener is executed) and cls (the class to which the listener is bound).

We can read the values of the object trough the getter obj.getTypeAttribute(String AttributeName), where Type can be Text, Integer, Boolean, Decimal, ... and the AttributeName is the name of the attribute, e.g.:

obj.getTextAttribute("surname");

The remaining code is the standard way to send an email through Portofino API. Remember to customize the sender.

Storing documents in Alfresco through a Portofino workflow action

Next step, we want to save the blob in Portofino into Content repository managed by Alfresco. Here, I won't deepen Alfresco's architecture and configuration (see the wiki at http://www.alfresco.org or Jeff Potts' blog http://ecmarchitect.com/ ).

I have simply created a space called “CV” where I'm going to store the PDFs during the workflow transition from “submitted” to "accepted".


To create a workflow action we need to create a maven overlay project to include the original portofino war and jars, the alfresco client libraries and our custom classes.

Follow the tutorial to create a maven overlay. Then we need some modifications in the pom.xml. First of all we add a new dependency “portofino-jar” to the pom.xml of the previous tutorial, because in this project our classes will use the Portofino API.

...
<dependency>
  <groupId>com.manydesigns</groupId>
  <artifactId>portofino-jar</artifactId>
  <version>2.0.16</version>
  <type>jar</type>
  <scope>compile</scope>
</dependency>
...

Then we add all the jars needed by Alfresco Web Service Client (see the pom.xml in the References section). I found them in the JBoss repository. The only one that is missing is “alfresco-web-service-client” (download it from Alfresco http://wiki.alfresco.com/wiki/Download_Alfresco_Community_Network), I manually installed it with the following maven command.

mvn install:install-file -DgroupId=alfresco \
-DartifactId=alfresco-web-service-client -Dversion=3.0 \
-Dpackaging=jar -Dfile=alfresco-web-service-client.jar

Now open your favorite Java IDE and create a Custom Action (StoreCV.java), with which you can launch your java code every time a workflow transition is triggered.

A custom Action must implement com.manydesigns.portofino.base.workflow.WfActionAPI and its method “run” - 

void run(MDObject obj, MDWfTransition wft,
         Collection<String> errors)

“run” is executed when object "obj" changes its state through transition "wft". A collection of strings ("errors") is passed to accumulate any error messages. 

The comments are in the code.

package com.manydesigns.demo;
import com.manydesigns.portofino.base.MDBlob; 
import com.manydesigns.portofino.base.MDClass; 
import com.manydesigns.portofino.base.MDObject;
import com.manydesigns.portofino.base.workflow.MDWfTransition;
import com.manydesigns.portofino.base.workflow.WfActionAPI;
import com.manydesigns.portofino.util.Defs;
import org.alfresco.webservice.content.Content;
import org.alfresco.webservice.content.ContentServiceSoapBindingStub;
import org.alfresco.webservice.repository.UpdateResult;
import org.alfresco.webservice.types.*;
import org.alfresco.webservice.util.AuthenticationUtils;
import org.alfresco.webservice.util.Constants;
import org.alfresco.webservice.util.Utils;
import org.alfresco.webservice.util.WebServiceFactory; 
import java.io.File; 
import static java.io.File.separatorChar; 
import java.io.FileInputStream; import java.io.IOException; 
import java.io.InputStream; 
import java.util.*;

/** 
 * Author: Giampiero Granatella giampiero.granatella@manydesigns.com 
 * (c) ManyDesigns srl 2009 
 */ 
public class StoreCV 
 implements WfActionAPI { 
 public static final String copyright =
  "Copyright (c) 2009, ManyDesigns srl"; 
 protected static final Store STORE =
  new Store(Constants.WORKSPACE_STORE,  "SpacesStore"); 
 private static final String username="admin"; 
 private static final String password="admin"; 
 private static final String folder="/app:company_home/cm:CV"; 
 private static final String UTF_8 = "UTF-8"; 
 private static String mimeType = "application/pdf"; 
 /**
* Method executed when the object change its worflow state trough a 
 * specific transition 
 * @param mdObject the bound object 
 * @param mdWfTransition the executes transition 
 * @param strings a collection of messages 
 * @throws Exception 
 */ 
 public void run(MDObject mdObject, MDWfTransition mdWfTransition, 

     Collection<String> strings) throws Exception { 

     //Portofino stores its documents in the file system.
     //I get the path for the document of the specific object 

 
    String pathBlob = mdObject.getConfig().getMDCvsWorkingDirectory() 
 
        + separatorChar + mdObject.getConfig().getMDCvsModule() 
 
        + separatorChar 
 
        + Defs.BLOB_DIRECTORY + separatorChar; 
 
    try { 
 
    //Store the document in Alfresco 

 
        store(mdObject, pathBlob); 
 
    } catch (Throwable e) { 
 
        e.printStackTrace(); 
 
    } 
 }
 /** 
 * Store the document in Alfresco 

 * @param obj 
 * @param path 
 * @throws Exception 
 */ 
 private void store(MDObject obj, String path) throws Exception { 
 
    MDClass objClass = obj.getActualClass(); 
 
    MDBlob blob = obj.getBlobAttribute("cv"); 
 
    //get the document 

 
    File file = new File(path + separatorChar + blob.getId()); 
 
    //User authentication in alfresco, I use admin/admin, change 
 
    // it in production 

 
    AuthenticationUtils.startSession(username, password); 
 
    // Get the content service 

 
    Store storeRef =
         new Store(Constants.WORKSPACE_STORE, "SpacesStore"); 
 
    ParentReference docParent = new ParentReference( 
 
        storeRef, 
 
        null, 
 
        folder, 
 
        Constants.ASSOC_CONTAINS, 
 
        Constants.createQNameString( 
 
        Constants.NAMESPACE_CONTENT_MODEL, "qname")); 
 
    //Set the mimeType of the content 

 
    ContentFormat contentFormat =
         new ContentFormat(mimeType, UTF_8); 
 
    //Set the properties of the document in Alfresco 
 
    // the name for the content in Alfresco is given by the id
     // of the Portofino Object 

 
    NamedValue nameValue = Utils.createNamedValue(
                    Constants.PROP_NAME, 
                
    obj.getId().toString()); 
 
    NamedValue[] properties = new NamedValue[]{nameValue}; 
 
    //Alfresco uses a CML - Content Manipulation Language - 
 
    // to encapsulate the operations to be executed. 

 
    CMLCreate createDoc = new CMLCreate("ref1", docParent,
         null, null, null, Constants.TYPE_CONTENT, properties); 
 
    // Construct CML 

 
    CML cml = new CML(); 
 
    cml.setCreate(new CMLCreate[]{createDoc}); 
 
    // Execute CML Block and store the content in Alfresco 

 
    UpdateResult[] results = WebServiceFactory. 
 
    getRepositoryService().update(cml); 
 
    Reference docRef = results[0].getDestination(); 
 
    ContentServiceSoapBindingStub contentService =
         WebServiceFactory.getContentService(); 
 
    Content docContentRef = contentService.write(docRef, 
 
    Constants.PROP_CONTENT, getBytes(file), contentFormat); 
 } 
 /** 
 * read the file 

 * @param file 
 * @return 
 * @throws Exception 
 */ 
 private static byte[] getBytes(File file) throws Exception { 

     InputStream is = new FileInputStream(file); 

     long length = file.length(); 

     if (length > Integer.MAX_VALUE) { 

        throw new Exception("file too large"); 
 
    } 
 
    byte[] bytes = new byte[(int) length]; 
 
    // Read in the bytes 

 
    int offset = 0; 
 
    int numRead = 0; 
 
    while (offset < bytes.length && (numRead =
         is.read(bytes, offset, bytes.length - offset)) >= 0) { 
 
        offset += numRead; 
 
    } 
 
    // Ensure all the bytes have been read in 


     if (offset < bytes.length) { 
 
        throw new IOException("Could not completely " +
         "read file" + " " + file.getName()); 

     } 

     // Close the input stream and return bytes 


     is.close(); 

     return bytes; 

 } 
} 

You need to create a resource (alfresco/webserviceclient.properties) to tell to our application where Alfresco is listening for connections. This file has only the following row.

repository.location=http://127.0.0.1:8080/alfresco/api

Now run:

mvn package

At the end you have your war file under the "target" directory. Deploy it under your running apache tomcat.

Now we'll tell Portofino about our custom action. Open your browser and go to your application:

  • Go upstairs
  • Click on "Workflow Action" tab
  • Click on create
  • Select "Java Workflow Action"
  • Enter the "name"
  • Set the "Java class" to "com.manydesigns.demo.StoreCV"
  • Click on "Create" button
  • Click on the link "Connect to workflow transition"
  • Select the right transition


Now test your application, go downstairs and create a "job application" with a cv in pdf attached. You'll receive an email after the creation of a job application. Move the workflow forward by clicking on "approve": this will store the CV in Alfresco. Connect to Alfresco and look at the "CV" space" to see the uploaded CV.

Conclusions

In a complex enterprise environment, Portofino can be viewed as one of the components. Using Portofino's API you can integrate it with complementary system (e.g. an ECM), while maintaing its ease of use and high levels of productivity.

Resources

[1] – complete source code
[2] – pom.xml