I think that we are reaching the maturity level in Liferay and Alfresco, because we can create applications on top of them of fastly and easy way. Alfresco ECM has functionalities exposed as a RESTful API, as know as Alfresco Webscripts, built on the basis of Spring Surf.
Liferay Portal has Liferay IDE based on Eclipse where we can create from scratch different types of Portlets. Also Liferay allows to include external libraries as jQuery, ExtJS, Vaadin, etc. that allows to develop highly customized portlets. Right now, when several people ask me how to integrate Alfresco into Liferay, after I ask them what does mean when you said integrate?. Well I say that implies several thing as: Integration mean:

  1. User and roles, SSO ?
  2. Include Alfresco Explorer or Share as a Portlet?
  3. Include Alfresco Explorer inside iFrame Portlet?
  4. Call any Alfresco’s functionality from a Portlet?
  5. … Well, everything is possible to do, but to create applications from scratch following point 5 was very difficult, but now I think is the quickest way to do it, also the best from an architectural point of view. This post explain how to do a portlet calling to Alfresco’s Webscripts (REST URIs) via ajax using jQuery. I also give some recommendations.

Ajax Portlet calls Alfresco Webscripts Ajax Portlet calls Alfresco Webscripts

Requeriments

  1. Liferay IDE version 1.3.1 as IDE
  2. Liferay Portal version 6.0.6 installed into IDE
  3. Liferay SDK version 6.0.6 installed into IDE
  4. Alfresco ECM version 3.4d CE installed
  5. Identify and verify Alfresco Webscripts:
    • Login and get Ticket: http://${ALFRESCO}/alfresco/service/api/login?u=${USR}&pw=${PWD}
    • Folder browser: http://${ALFRESCO}/alfresco/service/sample/folder${INITIAL_FOLDER}
  6. jQuery version 1.6.3 added to new portlet

Process

  1. From Liferay IDE create a new Liferay Project that implement GenericPortlet as follow:

[caption id=”” align=”alignnone” width=”322” caption=”Liferay IDE - creating new Liferay Project (1/6)”]Liferay IDE - creating new Liferay Project (1/6)[/caption]

[caption id=”” align=”alignnone” width=”360” caption=”Liferay IDE - creating new Liferay Project (2/6)”]Liferay IDE - creating new Liferay Project (2/6)[/caption]

[caption id=”” align=”alignnone” width=”274” caption=”Liferay IDE - creating new Liferay Project (3/6)”]Liferay IDE - creating new Liferay Project (3/6) [/caption]

[caption id=”” align=”alignnone” width=”298” caption=”Liferay IDE - creating new Liferay Project (4/6)”]Liferay IDE - creating new Liferay Project (4/6) [/caption]

[caption id=”” align=”alignnone” width=”368” caption=”Liferay IDE - creating new Liferay Project (5/6)”]Liferay IDE - creating new Liferay Project (5/6) [/caption]

[caption id=”” align=”alignnone” width=”344” caption=”Liferay IDE - creating new Liferay Project (6/6)”]Liferay IDE - creating new Liferay Project (6/6) [/caption]

  1. The structure of Project in Liferay IDE will be as follow:

[caption id=”” align=”alignnone” width=”378” caption=”Liferay IDE - folder structure of new project”]Liferay IDE - folder structure of new project[/caption]

  1. Add code in view.jsp to call serverResource method and to do ajax call to Alfresco. Also, in view.jsp you will add JavaScript code (jQuery) for parsing HTML/XML ajax responses.

[sourcecode language=”html” gutter=”true” wraplines=”false”]
<%@ taglib uri=”http://java.sun.com/portlet_2_0” prefix=”portlet” %>

This is the Ajax Alfresco Folder Browser portlet in View mode.


<%
// “http://192.168.56.101:8080”;
String strUrlAlfIP = renderRequest.getAttribute(“alfServer”).toString();
// “/alfresco/service/api/login?u=admin&pw=admin”;
String strUrlAlfLogin = renderRequest.getAttribute(“alfTicketSvc”).toString() + “?” + renderRequest.getAttribute(“alfTicketSvcParams”);
// “/alfresco/service/sample/folder/Company%20Home”;
String strUrlAlfDir = renderRequest.getAttribute(“alfWebscriptBrowserURL”).toString();
%>


...click on above button to start or change params in portlet menu preferences!




[+] Toggle error console

:)




[+] Toggle Alfresco content

:)

[/sourcecode]

  1. AjaxAlfrescoFolderBrowser.java extends GenericPortlet, in the serverResource method manages ajax calls and returns ResourceResponse to be parsed in view.jsp

[sourcecode language=”java” gutter=”true” wraplines=”false”]
package info.intix.lfry.samples; import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil; import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry; import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.GenericPortlet;
import javax.portlet.PortletException;
import javax.portlet.PortletMode;
import javax.portlet.PortletPreferences;
import javax.portlet.PortletRequestDispatcher;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import javax.portlet.ResourceRequest;
import javax.portlet.ResourceResponse; /**

  • Portlet implementation class AjaxAlfrescoFolderBrowser
    */
    public class AjaxAlfrescoFolderBrowser extends GenericPortlet { public void init() {
    editJSP = getInitParameter(“edit-jsp”);
    helpJSP = getInitParameter(“help-jsp”);
    viewJSP = getInitParameter(“view-jsp”);
    } /**
  • intix: Changes are persisted when the store method is called.
  • The store method can only be invoked within the scope of a processAction call.
  • Changes that are not persisted are discarded when the processAction or render method ends.
    */
    public void processAction(
    ActionRequest actionRequest, ActionResponse actionResponse)
    throws IOException, PortletException { //super.processAction(actionRequest, actionResponse);
    PortletPreferences prefs = actionRequest.getPreferences();
    prefs.setValue(“ticketUrl”, actionRequest.getParameter(“ticketUrl”));
    prefs.setValue(“alfServer”, actionRequest.getParameter(“alfServer”));
    prefs.setValue(“alfTicketSvc”, actionRequest.getParameter(“alfTicketSvc”));
    prefs.setValue(“alfTicketSvcParams”, actionRequest.getParameter(“alfTicketSvcParams”));
    prefs.setValue(“alfWebscriptBrowserURL”, actionRequest.getParameter(“alfWebscriptBrowserURL”));
    prefs.setValue(“alfWebscriptBrowserURLParams”, actionRequest.getParameter(“alfWebscriptBrowserURLParams”));
    prefs.setValue(“jQuery”, actionRequest.getParameter(“jQuery”));
    prefs.store();
    actionResponse.setPortletMode(PortletMode.EDIT);
    } /**
  • intix:
    */
    public void doEdit(
    RenderRequest renderRequest, RenderResponse renderResponse)
    throws IOException, PortletException {
    if (renderRequest.getPreferences() == null) {
    //super.doEdit(renderRequest, renderResponse);
    } else {
    // get editable preferences
    PortletPreferences prefs = renderRequest.getPreferences(); // intix: these values will override options in portlet.xml
    renderRequest.setAttribute(“alfServer”, (prefs.getValue(“alfServer”, “”)));
    renderRequest.setAttribute(“alfTicketSvc”, (prefs.getValue(“alfTicketSvc”, “”)));
    renderRequest.setAttribute(“alfTicketSvcParams”, (prefs.getValue(“alfTicketSvcParams”, “”)));
    renderRequest.setAttribute(“alfWebscriptBrowserURL”, (prefs.getValue(“alfWebscriptBrowserURL”, “”)));
    renderRequest.setAttribute(“alfWebscriptBrowserURLParams”, (prefs.getValue(“alfWebscriptBrowserURLParams”, “”)));
    renderRequest.setAttribute(“jQuery”, (prefs.getValue(“jQuery”, “”)));
    include(editJSP, renderRequest, renderResponse);
    }
    } public void doHelp(
    RenderRequest renderRequest, RenderResponse renderResponse)
    throws IOException, PortletException { include(helpJSP, renderRequest, renderResponse);
    } /**
  • intix:
    */
    public void doView(
    RenderRequest renderRequest, RenderResponse renderResponse)
    throws IOException, PortletException { try {
    // get portlet prefs
    PortletPreferences prefs = renderRequest.getPreferences(); String alfServer = prefs.getValue(“alfServer”, “”);
    String alfTicketSvc = prefs.getValue(“alfTicketSvc”, “”);
    String alfTicketSvcParams = prefs.getValue(“alfTicketSvcParams”, “”);
    String alfWebscriptBrowserURL= prefs.getValue(“alfWebscriptBrowserURL”, “”);
    String alfWebscriptBrowserURLParams = prefs.getValue(“alfWebscriptBrowserURLParams”, “”);
    String jQuery = prefs.getValue(“jQuery”, “”);
    String ticketUrl = alfServer + alfTicketSvc + “?” + alfTicketSvcParams; renderRequest.setAttribute(“ticketUrl”, ticketUrl);
    renderRequest.setAttribute(“alfServer”, alfServer);
    renderRequest.setAttribute(“alfTicketSvc”, alfTicketSvc);
    renderRequest.setAttribute(“alfTicketSvcParams”, alfTicketSvcParams);
    renderRequest.setAttribute(“alfWebscriptBrowserURL”, alfWebscriptBrowserURL);
    renderRequest.setAttribute(“alfWebscriptBrowserURLParams”, alfWebscriptBrowserURLParams);
    renderRequest.setAttribute(“jQuery”, jQuery);
    }catch(Exception ex) {
    _log.error(ex);
    }
    include(viewJSP, renderRequest, renderResponse);
    } protected void include(
    String path, RenderRequest renderRequest,
    RenderResponse renderResponse)
    throws IOException, PortletException { PortletRequestDispatcher portletRequestDispatcher =
    getPortletContext().getRequestDispatcher(path); if (portletRequestDispatcher == null) {
    _log.error(path + “ is not a valid include”);
    }
    else {
    portletRequestDispatcher.include(renderRequest, renderResponse);
    }
    } /**
  • intix: serveResource does HTTP and Ajax call behind of Liferay
    */
    public void serveResource(ResourceRequest request, ResourceResponse response) throws PortletException, IOException {
    response.setContentType(“text/xml”);
    String strAlfTicket= request.getParameter(“alf_ticket”);
    String strQueryString = “”;
    if (strAlfTicket != null) {
    // intix: if alf_ticket exists, then user was authenticate with alfresco
    Map<String, String[]> mapParameters = request.getParameterMap();
    for (Entry<String, String[]> entryParameter : mapParameters.entrySet()) {
    System.out.println(“» Key = “ + entryParameter.getKey() + “, Value = “ + entryParameter.getValue()[0]);
    strQueryString = strQueryString + entryParameter.getKey() + “=” + entryParameter.getValue()[0] + “&”;
    }
    } else {
    // intix: ticket is null
    String strUser = request.getParameter(“u”);
    String strPw = request.getParameter(“pw”);
    strQueryString = “u=” + strUser + “&pw=” + strPw;
    }
    String requestUrl = request.getResourceID();
    BufferedInputStream web2ProxyBuffer = null;
    BufferedOutputStream proxy2ClientBuffer = null;
    HttpURLConnection con;
    URL url = null;
    try {
    int oneByte = 0;
    String methodName;
    if (strAlfTicket != null) {
    url = new URL(requestUrl + “?” + strQueryString);
    } else {
    url = new URL(requestUrl);
    }
    con = (HttpURLConnection) url.openConnection();
    methodName = request.getMethod();
    System.out.println(“» methodName: “ + methodName); con.setRequestMethod(methodName);
    con.setDoOutput(true);
    con.setDoInput(true);
    con.setFollowRedirects(false);
    con.setUseCaches(true);
    con.connect(); // does not work in 6.0.6 CE
    // http://issues.liferay.com/browse/LPS-13039
    int httpRespCode = con.getResponseCode();
    response.setProperty(ResourceResponse.HTTP_STATUS_CODE, Integer.toString(httpRespCode));
    System.out.println(“» HTTP_STATUS_CODE: “ + httpRespCode); if(methodName.equals(“POST”)) {
    BufferedInputStream clientToProxyBuf = new BufferedInputStream(request.getPortletInputStream());
    BufferedOutputStream proxyToWebBuf = new BufferedOutputStream(con.getOutputStream());
    while ((oneByte = clientToProxyBuf.read()) != -1) {
    proxyToWebBuf.write(oneByte);
    }
    proxyToWebBuf.flush();
    proxyToWebBuf.close();
    clientToProxyBuf.close();
    } for( Iterator i = con.getHeaderFields().entrySet().iterator() ; i.hasNext() ;) {
    Map.Entry mapEntry = (Map.Entry)i.next();
    if(mapEntry.getKey()!=null) {
    //response.setHeader(mapEntry.getKey().toString(), ((List)mapEntry.getValue()).get(0).toString());
    System.out.println(“» HEADER > “ + mapEntry.getKey().toString() + “\t” + ((List)mapEntry.getValue()).get(0).toString());
    }
    } InputStream in = con.getInputStream();
    web2ProxyBuffer = new BufferedInputStream(in);
    proxy2ClientBuffer = new BufferedOutputStream(response.getPortletOutputStream()); byte [] byteArray = new byte[1024]; // intix: any array size is valid
    int intByteRead = web2ProxyBuffer.read(byteArray);
    while (intByteRead > 0) {
    // intix: print response-html/xml, must be the first line after while loop
    System.out.println(new String(byteArray, 0, intByteRead));
    proxy2ClientBuffer.write(byteArray, 0, intByteRead);
    intByteRead = web2ProxyBuffer.read(byteArray);
    }
    proxy2ClientBuffer.flush();
    proxy2ClientBuffer.close();
    web2ProxyBuffer.close();
    con.disconnect();
    } catch(Exception e) {
    e.getMessage();
    } finally {
    //
    }
    } protected String editJSP;
    protected String helpJSP;
    protected String viewJSP; private static Log _log = LogFactoryUtil.getLog(AjaxAlfrescoFolderBrowser.class); }

[/sourcecode]

  1. When you have successfully deployed the portlet, open a browser, login, then add the new portlet to any page. Then you see the following:

[caption id=”” align=”alignnone” width=”303” caption=”Ajax Portlet calling Alfresco Webscripts”]Ajax Portlet calling Alfresco Webscripts[/caption]

[caption id=”” align=”alignnone” width=”411” caption=”Ajax Portlet calling Alfresco Webscript - view mode”]Ajax Portlet calling Alfresco Webscript - view mode[/caption]

[caption id=”” align=”alignnone” width=”401” caption=”Ajax Portlet calling Alfresco Webscripts - edit mode”]Ajax Portlet calling Alfresco Webscripts - edit mode[/caption]

Conclussions

  1. In the JSR-286 specifications (Portlet 2.0) now is possible to use serveResource() method andto request data. I use it as a servlet-proxy to do ajax calls to Alfresco.
  2. Exists a issue in Liferay 6.0.6 when setting ResourceResponse.HTTP_STATUS_CODE in the Portlet response (http://issues.liferay.com/browse/LPS-13039), this implies I have to manage HTTP_STATUS_CODE by parsing the Ajax HTML/XML responses.
  3. I have Liferay and Alfresco in different VMs (different IP and Ports) and I never had cross-domain issues thanks to Point #1 (serveResource nad portlet:resourceURL), but if you run into it is recommended that you use Apache HTTP server as a reverse-proxy. You can download entire project (source code) and compiled from here:
  4. Source code (Liferay IDE project): AjaxAlfrescoFolderBrowser-portlet.zip
  5. Compiled: AjaxAlfrescoFolderBrowser-portlet.war End.