Main Page

From portlet-skeleton (inactive) Wiki
Jump to: navigation, search

Contents

PortletSkeleton Quickstart HOW-TO

Before starting with this tutorial, make yourself comfortable with the portlet technology. I will not explain basic portlet stuff here that is described better elswhere. Read "What is a Portlet?" at onjava.com Part 1 and Part 2.

What PortletSkeleton can do for you

The PortletSkeleton is a framework for implementing portlets. It gives you an easy-to-use skeleton for your new portlet(s). The PortletSkeleton supports Velocity as templating engine for rendering HTML and BeanScriptingFramework for business logic scripting, enabling you to write a complete portlet without coding/compiling Java code.

For example, you can write the business logic of your portlet in server-side JavaScript and add the Velocity templates for HTML rendering. As PortletSkeleton uses the BeanScriptingFramework, you can use any scripting language that is supported, like JavaScript (Rhino), Groovy, Python, PHP or even XSLT or REXX. The type of the scripting is autodetected on runtime. You only have to make sure that the BSF-aware language library for your scripting language is placed in the WEB-INF/lib directory on runtime.

PortletSkeleton is also a framework for developing portlet deployment archives using standard Maven plugins. Running the package goal creates a ready-to-deploy portlet archive.

Three basic implementation patterns

The PortletSkeleton supports two basic portlet implementation patterns, both enabling Velocity as templating engine for your new portlet:

  • A standard portlet implementing business logic in Java.
  • A scripting portlet implementing the business logic using scripting.
  • A standard portlet not using the extensions defined by the PortletSkeleton.

For small portlets that have a minimal requirement for business logic, choose the second approach, thus keeping the needed work to a minimum. Be sure that the logic to be implemented doesn't get too complex or your're busted with this pattern.

If you are developing a full blown portlet containing of many modes and complex business logic, choose the first approach to just use Velocity as rendering engine for your HTML output.

The third approach is not discussed here because it represents the standard way of implementing portlets as defined by the baseline portlet specification. ONLY use this approach for tarent portlets after discussing it with senior development people.

You can also mix up any number of portlets using any of the above approaches in a single deployment archive.

Directory Layout

PortletSkeleton follows the standard Maven project layout. For your new project, just copy the project, altering the needed files. A portlet deployment archive is simply a WAR archive also used for deploying J2EE web applications. In fact, a WAR created with a project extending the PortletSkeleton also contains at least one standard J2EE servlet implementing the Velocity rendering code. This servlet is automatically deployed along with the portlets contained in the archive. As you are able to add more portlets to the project, you can also add more servlets implementing code from your business logic layer.

As all servlets have to be described in src/main/webapps/WEB-INF/web.xml, you have to describe all portlets in your project in the deployment descriptor at src/main/webapps/WEB-INF/portlet.xml. The defualt portlet.xml contains two portlet definitions that act as working examples. Feel free to change or delete them. DO NOT delete the Velocity servlet definition from web.xml, just add your custom servlets there. The Velocity servlet gets the delegated render requests from the Velocity-based portlets and does all the Velocity stuff.

VelocityPortlet

If you don't want to script your business logic and just want to use Velocity as rendering engine, create a new class that extends de.tarent.portlet.VelocityPortlet. This new class has to overload the processAction() method to implement logic using the standard portlet api.

public class VelocityPortletDemo extends VelocityPortlet
{
  @Override
  public void processAction(ActionRequest request, ActionResponse response) 
          throws IOException, PortletException
  {
      // your logic here
  }
}

The next step is to define the portlet in portlet.xml. This adds the Velocity rendering parameters to the portlet definition.

<portlet>
  <portlet-name>VelocityPortletDemo</portlet-name>
  ...
  <portlet-class>de.tarent.portlet.demo.VelocityPortletDemo</portlet-class>
  ...
  <init-param>
    <name>de.tarent.portlet.theme.name</name>
    <value>themename</value>
  </init-param>
  ...
  <supports>
    <mime-type>text/html</mime-type>
    <portlet-mode>VIEW</portlet-mode>
    <portlet-mode>EDIT</portlet-mode>
    <portlet-mode>HELP</portlet-mode>
  </supports>

  <supported-locale>en</supported-locale>
  <supported-locale>de</supported-locale>
  ...
</portlet>

The supported modes gets directly mapped to velocity templates in WEB-INF/velocity/<portlet-name_lowercase>/<optional_themename>/<locale>. In this example, a portlet in "VIEW" mode that is receiving a render request in locale "de" will load and execute the template found at WEB-INF/velocity/velocityportletdemo/themename/de/view.vm. From there, you can delegate the request to subsequent templates or include other resources from the portlet's path. If you use custom modes, the mode name (in lowercase) plus ".vm" will be used as template name. If the parameter de.tarent.portlet.theme.name is not given or the given theme directory does not exist or is not readable, the theme directory is omitted in the path, resulting in WEB-INF/velocity/velocityportletdemo/de/view.vm.

Since Version 0.3 the directory layout has changed to better support Template Localization without duplication.

The locale part of the path has been removed, so the path is now WEB-INF/velocity/<portlet-name_lowercase>/<optional_themename>/<template>.vm

The default <template>.vm is derived from the portlet mode as in previous versions. But you can now explicitly set the template using the VelocityPortlet.setTemplate(path, request) method. The path is then derived from the previous path (including default) leading into support for relative paths, which is also highly recommended! If you leave out the extension too, the default velocity extension is used. Using path null resets the path to the default.

Velocity Templates

The Velocity templates used in portlets are called with a set of pre-defined variables containing references to data needed for rendering HTML for portlets. For example, you can access the PortletRequest an PortletResponse as well as the preferences or configuration from these variables. Defined variables are:

Variable Name Value Class Description
portletConfig javax.portlet.PortletConfig The portlet's configuration.
portlet javax.portlet.Portlet A reference to the portlet instance to which this template (and this request) belongs.
portletRequest javax.portlet.RenderRequest The render request.
portletResponse javax.portlet.RenderResponse The render response. Do not use the writer to write client output, this will be done by the velocity engine.
portletUtils de.tarent.portlet.PortletUtils A utility class that contains some useful operations.
portletMode java.lang.String The portlet mode, one of "VIEW", "EDIT", "HELP", or a custom mode string.
portletLocale java.lang.String Locale as standard ISO 3166 two-letter code.
portletPreferences javax.portlet.PortletPreferences The user's config. Don't forget to explicitly store() changes in user configurations.
portletSession javax.portlet.PortletSession This request's session object.
portletWindowState java.lang.String The current portlet window state. One of "NORMAL", "MINIMIZED", "MAXIMIZED".
portletParameters java.util.Map The request's parameters. This can also be retrieved from the request object, but is provided as a convinience for the template developer.
contextPath java.lang.String Returns the context path which is the path prefix associated with the deployed portlet application. If the portlet application is rooted at the base of the web server URL namespace (also known as "default" context), this path must be an empty string. Otherwise, it must be the path the portlet application is rooted to, the path must start with a '/' and it must not end with a '/' character.
namespace java.lang.String The value returned by this method should be prefixed or appended to elements, such as JavaScript variables or function names, to ensure they are unique in the context of the portal page.
basePath java.lang.String Filesystem base path of the portlet deployment.
templatePath java.lang.String Filesystem path to this template.
velocityServlet de.tarent.portlet.VelocityServlet A reference to the VelocityServlet instance executing this template.

All common Velocity features can be used inside the template. Remember that the HTML of a portlet is a fragment of the complete page, so do not render <html> or <body> tags and keep your HTML tidy so it doesn't affect other fragments rendered by other portlets.

To create URLs inside your template, use portletResponse.createActionUrl() or portletResponse.createRenderUrl() repectively.

#set($formActionURL = $portletResponse.createActionURL())
<form action="$formActionURL" method="POST">
	<input type="text" name="text">
	<input type="submit">
</form>

Form data transmitted to the portlet have to be sent via the POST method.

VelocityScriptPortlet

If you also want to script your business logic with a scripting language supported by the BeanScriptingFramework, first make sure the language's bsf binding library is part of your project by adding it to pom.xml.

<dependency>
      <groupId>org.codehaus.groovy</groupId>
      <artifactId>groovy</artifactId>
      <version>1.5.6</version>
</dependency>

This adds Groovy support to your project. The JAR containing the bindings will be automatically packaged with the resulting WAR archive when you build the project.

The portlet depoyment description in portlet.xml always defines the class de.tarent.portlet.VelocityScriptPortlet as the portlet's entry class. This is a generic implementation based on VelocityPortlet calling your script for executing business logic. The actual script file that gets called is defined using a portlet init parameter.

<portlet>
   <portlet-name>VelocityScriptPortletDemo</portlet-name>

   <portlet-class>de.tarent.portlet.VelocityScriptPortlet</portlet-class>

   <init-param>
     <name>de.tarent.portlet.script.entry</name>
     <value>demo.groovy</value>
   </init-param>

   <supports>
     <mime-type>text/html</mime-type>
     <portlet-mode>VIEW</portlet-mode>
     <portlet-mode>EDIT</portlet-mode>
     <portlet-mode>HELP</portlet-mode>
   </supports>

   <supported-locale>en</supported-locale>
   <supported-locale>de</supported-locale>
   ...
</portlet>

The parameter de.tarent.portlet.script.entry defines the name of the script that should be executed in the portlet's action phase. The script file is relative to WEB-INF/script/<portlet-name_lowercase>. The above example would execute WEB-INF/script/velocityscriptportletdemo/demo.groovy.

The script language is detected on runtime based on the file's extension. Make sure you are using a standard extension such as .groovy or .js.

While executing a script, some pre-defined ("declared" in BSF terms) variables can be used to access the request data. Due to the BeanScriptingFramework, these variables have to be extracted from the bsf context using the lookupBean() method.

actionRequest = bsf.lookupBean("actionRequest");

The declared pre-defined variables that can be accessed this way are listed below.

Variable Name Value Class Description
actionRequest javax.portlet.ActionRequest The request object.
actionResponse javax.portlet.ActionResponse The response object.
basePath java.lang.String Base path of the portlet. Can be used to access file resources common to all portlets in this deployment archive.
scriptPath java.lang.String Path of the script file. Can be used to load other resources from the same path the script was loaded.

The amount of pre-defined variables are kept to a minimum to not interfere with (possibly) complex business logic. As the access to all needed resources using the request and response objects is easy, fewer variables are defined. Accessing this resources in Velocity is much more complicated, so Velocity templates get more pre-defined variables.

Building and Debugging

The WAR archive containing the portlets and servlets can be built using mvn package. The resulting WAR is placed in the target/ folder of your project. Liferay does not support a reliable way of hot-deploying changed resource without using Liferay-proprietary elements. The best way to debug a portlet/servlet inside Liferay is to use the Liferay auto-deployment mechanism.

From the administration console (portlet "admin") more->Plugins->Install more plugins->Config set the source directory to your project's target directory. This will cause Liferay to monitor your target directory for new or updated WAR archives. If Lifera finds a WAR (after a mvn package run), it gets deployed. Already deployed portlets or servlets will be updated.

To debug portlets from Eclipse, enable the Liferay server's debug port by adding the needed parameters to the JAVA_OPTS variable in bin/catalina.sh.

export JAVA_OPTS="-Xdebug -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n"

Then add a debug configuration in Eclipse that connects to port 8000 of localhost for a JDWP connection. This enables debugging of portlet/servlet code running inside Liferay from Eclipse. Breakpoints and Stepping can be used as usual.