Chapter 9. Content Integration

Since JBoss Portal 2.6 it is possible to provide an easy integration of content within the portal. Up to the 2.4 version content integration had to be done by configuring a portlet to show some content from an URI and then place that portlet on a page. The new content integration capabilities allows to directly configure a page window with the content URI by removing the need to configure a portlet for that purpose.

Note

We do not advocate to avoid the usage portlet preferences, we rather advocate that content configuration managed at the portal level simplifies the configuration: it helps to make content a first class citizen of the portal instead of having an intermediary portlet that holds the content for the portal. The portlet preferences can still be used to configure how content is displayed to the user.

The portal uses portlets to configure content

The portal references directly the content and use portlet to interact with content

9.1. Window content

The content of a window is defined by

  • The content URI which is the resource that the window is pointing to. It is an arbitrary string that the portal cannot interpret and is left up to the content provider to interpret.
  • The window content type which defines how the portal interpret the window content
    • The default content type is for portlets and has the value portlet. In this case the content URI is the portlet instance id.
    • The CMS content type allows to integrate content from the CMS at the page and it has the value cms. For that content type, the content URI is the CMS file path.
  • The content parameters which are a set of additional key/value string pairs holding state that is interpreted by the content provider.

At runtime when the portal needs to render a window it delegates the production of markup to a content provider. The portal comes with a preconfigured set of providers which handles the portlet and the cms content types. The most natural way to plug a content provider in the portal is to use a JSR 168 Portlet. Based on a few carefully chosen conventions it is possible to provide an efficient content integration with the benefit of using standards and without requiring the usage of a proprietary API.

9.2. Content customization

Content providers must be able to allow the user or administrator to chose content from the external resource it integrates in the portal in order to properly configure a portal window. A few interactions between the portal, the content provider and the portal user are necessary to achieve that goal. Here again it is possible to provide content customization using a JSR 168 Portlet. For that purpose two special portlet modes called edit_content and select_content has been introduced. It signals to the portlet that it is selecting or editing the content portion of the state of a portlet. select_content is used to select a new content to put in a window while edit_content is used to modify the previously defined content, often the two modes will display the same thing. The traditional edit mode is not used because the edit mode is more targeted to configure how the portlet shows content to the end user rather than what content it shows.

Example of content customization - CMS Portlet

9.3. Content Driven Portlet

Portlet components are used to integrate content into the portal. It relies on a few conventions which allow the portal and the portlet to communicate.

9.3.1. Displaying content

At runtime the portal will call the portlet with the view mode when it displays content. It will send information about the content to display using the render parameters to the portlet. Therefore the portlet has just to read the render parameters and use them to properly display the content in the portlet. The render parameters values are the key/value pairs that form the content properties and the resource URI is available under the uri parameter name.

9.3.2. Configuring content

As explained before, the portal will call the portlet using the edit_content mode. In that mode the portlet and the portal will communicate using either action or render parameters. We have two use cases which are:

  • The portal needs to configure a new content item for a new window. In that use case the portal will not send special render parameters to the portlet and the initial set of render parameters will be empty. The portlet can then use render parameters in order to provide navigation in the content repository. For example the portlet can navigate the CMS tree and store the current CMS path in the render parameters. Whenever the portlet has decided to tell the portal that content has been selected by the user it needs to use an action URL with a special set of parameters:
    • content.action.select equals to any value
    • content.uri equals to the content URI
    • content.param. used as prefix to configure content parameters
  • The second use case happens when the portal needs to edit existing content. In such situation everything works as explained before except that the initial set of render parameters of the portlet will be prepopulated with the content uri URI and parameters.

9.3.3. Step by step example of a content driven portlet

9.3.3.1. The Portlet skeleton

Here is the base skeleton of the content portlet. The FSContentDrivenPortlet shows the files which are in the war file in which the portlet is deployed. The arbitrary name filesystem will be the content type interpreted by the portlet.

public class FSContentDrivenPortlet extends GenericPortlet
{

   /** The edit_content mode. */
   public static final PortletMode EDIT_CONTENT_MODE = new PortletMode("edit_content");

   /** The select_content mode. */
   public static final PortletMode SELECT_CONTENT_MODE = new PortletMode("select_content");

   ...

}

9.3.3.2. Overriding the dispatch method

First the doDispatch(RenderRequest req, RenderResponse resp) is overridden in order to branch the request flow to a method that will take care of displaying the editor.

protected void doDispatch(RenderRequest req, RenderResponse resp)
                  throws PortletException, PortletSecurityException, IOException
{
   if (EDIT_CONTENT_MODE.equals(req.getPortletMode()) || 
       SELECT_CONTENT_MODE.equals(req.getPortletMode()))
   {
      doEditContent(req, resp);
   }
   else
   {
      super.doDispatch(req, resp);
   }
}

9.3.3.3. Utilities methods

The portlet also needs a few utilities methods which take care of converting content URI to a file back and forth. There is also an implementation of a file filter that keep only text files and avoid the WEB-INF directory of the war file for security reasons.

protected File getFile(String contentURI) throws IOException
{
   String realPath = getPortletContext().getRealPath(contentURI);
   if (realPath == null)
   {
      throw new IOException("Cannot access war file content");
   }
   File file = new File(realPath);
   if (!file.exists())
   {
      throw new IOException("File " + contentURI + " does not exist");
   }
   return file;
}
   protected String getContentURI(File file) throws IOException
   {
      String rootPath = getPortletContext().getRealPath("/");
      if (rootPath == null)
      {
         throw new IOException("Cannot access war file content");
      }

      // Make it canonical
      rootPath = new File(rootPath).getCanonicalPath();

      // Get the portion of the path that is significant for us
      String filePath = file.getCanonicalPath();
      return filePath.length() >=
        rootPath.length() ? filePath.substring(rootPath.length()) : null;
   }
   private final FileFilter filter = new FileFilter()
   {
      public boolean accept(File file)
      {
         String name = file.getName();
         if (file.isDirectory())
         {
            return !"WEB-INF".equals(name);
         }
         else if (file.isFile())
         {
            return name.endsWith(".txt");
         }
         else
         {
            return false;
         }
      }
   };

9.3.3.4. The editor

The editor is probably the longest part of the portlet. It tries to stay simple though and goes directly to the point.

Content editor of FSContentDrivenPortlet in action
protected void doEditContent(RenderRequest req, RenderResponse resp)
                  throws PortletException, PortletSecurityException, IOException
{
   // Get the uri value optionally provided by the portal
   String uri = req.getParameter("content.uri");

   // Get the working directory directory
   File workingDir;
   if (uri != null)
   {
      workingDir = getFile(uri).getParentFile();
   }
   else
   {
      // Otherwise try to get the current directory we are browsing,
      // if no current dir exist we use the root
      String currentDir = req.getParameter("current_dir");
      if (currentDir == null)
      {
         currentDir = "/";
      }
      workingDir = getFile(currentDir);
   }

   // Get the parent path
   String parentPath = getContentURI(workingDir.getParentFile());

   // Get the children of the selected file, we use a filter
   // to retain only text files and avoid WEB-INF dir
   File[] children = workingDir.listFiles(filter);

   // Configure the response
   resp.setContentType("text/html");
   PrintWriter writer = resp.getWriter();

   //
   writer.print("Directories:<br/>");
   writer.print("<ul>");
   PortletURL choseDirURL = resp.createRenderURL();
   if (parentPath != null)
   {
      choseDirURL.setParameter("current_dir", parentPath);
      writer.print("<li><a href=\"" + choseDirURL + "\">..</a></li>");
   }
   for (int i = 0;i < children.length;i++)
   {
      File child = children[i];
      if (child.isDirectory())
      {
         choseDirURL.setParameter("current_dir", getContentURI(child));
         writer.print("<li><a href=\"" + choseDirURL + "\">" + child.getName() +
                      "</a></li>");
      }
   }
   writer.print("</ul><br/>");

   //
   writer.print("Files:<br/>");
   writer.print("<ul>");
   PortletURL selectFileURL = resp.createActionURL();
   selectFileURL.setParameter("content.action.select", "select");
   for (int i = 0;i < children.length;i++)
   {
      File child = children[i];
      if (child.isFile())
      {
         selectFileURL.setParameter("content.uri", getContentURI(child));
         writer.print("<li><a href=\"" + selectFileURL + "\">" + child.getName() +
                      "</a></li>");
      }
   }
   writer.print("</ul><br/>");

   //
   writer.close();
}

9.3.3.5. Viewing content at runtime

Last but not least the portlet needs to implement the doView(RenderRequest req, RenderResponse resp) method in order to display the file that the portal window wants to show.

protected void doView(RenderRequest req, RenderResponse resp)
                  throws PortletException, PortletSecurityException, IOException
{
   // Get the URI provided by the portal
   String uri = req.getParameter("uri");

   // Configure the response
   resp.setContentType("text/html");
   PrintWriter writer = resp.getWriter();

   //
   if (uri == null)
   {
      writer.print("No selected file");
   }
   else
   {
      File file = getFile(uri);
      FileInputStream in = null;
      try
      {
         in = new FileInputStream(file);
         FileChannel channel = in.getChannel();
         byte[] bytes = new byte[(int)channel.size()];
         ByteBuffer buffer = ByteBuffer.wrap(bytes);
         channel.read(buffer);
         writer.write(new String(bytes, 0, bytes.length, "UTF8"));
      }
      catch (FileNotFoundException e)
      {
         writer.print("No such file " + uri);
         getPortletContext().log("Cannot find file " + uri, e);
      }
      finally
      {
         if (in != null)
         {
            in.close();
         }
      }
   }

   //
   writer.close();
}

9.3.3.6. Hooking the portlet into the portal

Management portlet with filesystem content type enabled

Finally we need to make the portal aware of the fact that the portlet can edit and interpret content. For that we need a few descriptors. The portlet.xml descriptor will define our portlet, the portlet-instances.xml will create a single instance of our portlet. The web.xml descriptor will contain a servlet context listener that will hook the content type in the portal content type registry.

<?xml version="1.0" encoding="UTF-8"?>
<portlet-app
   xmlns="http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd
                       http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd"
   version="1.0">
   ...
   <portlet>
      <description>File System Content Driven Portlet</description>
      <portlet-name>FSContentDrivenPortlet</portlet-name>
      <display-name>File System Content Driven Portlet</display-name>
      <portlet-class>org.jboss.portal.core.portlet.test.FSContentDrivenPortlet</portlet-class>
      <supports>
         <mime-type>text/html</mime-type>
      </supports>
      <portlet-info>
         <title>File Portlet</title>
         <keywords>sample,test</keywords>
      </portlet-info>
   </portlet>
   ...
</portlet-app>

The portlet.xml descriptor

<deployments>
   ...
   <deployment>
      <instance>
         <instance-id>FSContentDrivenPortletInstance</instance-id>
         <portlet-ref>FSContentDrivenPortlet</portlet-ref>
      </instance>
   </deployment>
   ...
</deployments

The portlet-instances.xml descriptor

<web-app>
   ...
   <context-param>
      <param-name>org.jboss.portal.content_type</param-name>
      <param-value>filesystem</param-value>
   </context-param>
   <context-param>
      <param-name>org.jboss.portal.portlet_instance</param-name>
      <param-value>FSContentDrivenPortletInstance</param-value>
   </context-param>
   <listener>
      <listener-class>org.jboss.content.ContentTypeRegistration</listener-class>
   </listener>
   ...
</web-app>

The web.xml descriptor

Warning

You don't need to add the listener class into your war file. As it is provided by the portal it will always be available.

9.4. Configuring window content in deployment descriptor

How to create a portlet that will enable configuration of content at runtime has been covered above, however it is also possible to configure content in deployment descriptors. With our previous example it would give the following snippet placed in a *-portal.xml file:

<window>
<window-name>MyWindow</window-name>
<content>
   <content-type>filesystem</content-type>
   <content-uri>/dir1/foo.txt</content-uri>
</content>
<region>center</region>
<height>1</height>
</window>
Final effect - portal window with FSContentDrivenPortlet

Note

How to configure CMS file this way is covered in the CMS chapter: Section 21.3, “CMS content”