Chapter 18. CMS Portlet

Thomas Heute

JBoss Portal packages a Web Content Management System capable of serving and allowing administration of web content. This chapter describes the CMS Portlet which is responsible for serving resources requested, the following chapter describes the CMSAdmin Portlet and all administration functionality.

18.1. Introduction

The CMS Portlet displays content from the file store inside a portlet window, or, in the case of binary content, outside of the portlet window altogether.

18.2. Features

The CMSPortlet handles all requests for all content types.

The methodology of serving content within the CMSPortlet, allows for some beneficial features, like:

  1. Search-engine friendly URLs: http://domain/[portal]/content/company.html
  2. Serve binaries with simple urls independant of the portal: http://domain/content/products.pdf
  3. Deploy several instances of the CMSPortlet on any page and configure them to display different start pages.
  4. Localization support: CMSPortlet will display content based on the user request locale, or display content using the default locale setting.

18.3. CMS content

Since 2.6 displaying CMS content in the portal is done using the new content integration feature. Each window of the portal can be configured to display CMS content directly instead of having to configure the CMS portlet as it used to be.

18.3.1. Configuring a window to display CMS content

Showing CMS content in a portal window can be done in the deployment descriptor quite easily

<window>
   <window-name>MyCMSWindow</window-name>
   <content>
      <content-type>cms</content-type>
      <content-uri>/default/index.html</content-uri>
   </content>
   <region>center</region>
   <height>1</height>
</window>

At the first display of the window, the content is initialized with the content uri value. When the user clicks on a link that navigates to another CMS file, the CMS file will be shown in the same window.

18.4. CMS Configuration

18.4.1. Display CMS content

Since 2.6 displaying CMS content in the portal is done using the new content integration feature. The portal is also able to map urls content to the CMS through a specific window. The CMS portlet default page is defined as a preference and can be overriden like any other preference up to the user's preference level. The default CMS portlet displayed when you install JBoss Portal for the first time is describe in the following file: jboss-portal.sar/portal-core.war/WEB-INF/portlet.xml .

<portlet-preferences>
   <preference>
      <name>indexpage</name>
      <value>/default/index.html</value>
   </preference>
</portlet-preferences>

The preference key is "indexpage". To change the default page, just make sure to create an html document using the CMS Admin portlet then change the value of "indexpage" to the corresponding path.

18.4.2. Service Configuration

18.4.2.1. Tuning Jackrabbit

JBoss Portal uses Apache Jackrabbit as its Java Content Repository implementation. Configuration of the service descriptor, allows for changing many of the variables associated with the service.

Here is the default configuration for the CMS repository found under portal-cms.sar/META-INF-INF/jboss-service.xml

...
<attribute name="DoChecking">true</attribute>
<attribute name="DefaultContentLocation">portal/cms/conf/default-content/default/</attribute>
<attribute name="DefaultLocale">en</attribute>
<attribute name="RepositoryName">PortalRepository</attribute>
<attribute name="HomeDir">${jboss.server.data.dir}${/}portal${/}cms${/}conf</attribute>
...

Below is a list of items found in the service descriptor and their definitions. Only items commonly changed are covered here and it is recommended you do not change any others unless you are very brave.

  • DoChecking: Should the portal attempt to check for the existence of the repository configuration files and default content on startup?
  • DefaultContentLocation: Location of the default content used to pre-populate the repository.
  • DefaultLocale: Two-letter abbreviation of the default locale the portal should use when fetching content for users. A complete ISO-639 list can be found here .
  • HomeDir: Location of configuration information for the repository when in 100% FileSystem store mode. Otherwise, its in the database.

18.4.2.2. Changing the url under which the content should be accessible

By default, the content will be accessible to a url like this: http://www.example.com/content/[...], if you need or prefer to change "content" to something else you will need to edit the following file: portal-cms.sar/META-INF-INF/jboss-service.xml and change the value of Prefix to something else. Please note that you cannot change it to "nothing", you need to provide a value.

...
<mbean
   code="org.jboss.portal.core.cms.CMSObjectCommandFactory"
   name="portal:commandFactory=CMSObject"
   xmbean-dd=""
   xmbean-code="org.jboss.portal.common.system.JBossServiceModelMBean">
   <xmbean/>
   <attribute name="Prefix">content</attribute>
   <attribute name="TargetWindowRef">default.default.CMSPortletWindow</attribute>
   <depends optional-attribute-name="Factory" proxy-type="attribute">
   portal:commandFactory=Delegating
   </depends>
   <depends optional-attribute-name="CMSService" proxy-type="attribute">
   portal:service=CMS
   </depends>
</mbean>
...
            
  • Prefix: This is the context path prefix that will trigger the portal to render content. By default, navigating to a URL such as http://localhost:8080/[portal_context]/content/Test.PDF will trigger the portal to display the PDF isolated from the portal pages. The path following the Prefix has to be absolute when fetching content.

18.4.3. Configuring the Content Store Location

By default, the JBoss Portal CMS stores all node properties, references, and binary content in the database, using the portal datasource. The location of some of these items is configurable, and there are 3 options:

18.4.3.1. 100% Filesystem Storage

To enable 100% Filesystem storage, you must edit the file: jboss-portal.sar/portal-cms.sar/META-INF/jboss-service.xml . You will note that the file is set to use the HibernateStore and HibernatePersistenceManager classes, by default. To have the CMS use 100% file system storage, simply comment these blocks. Then, you should uncomment to use the LocalFileSystem and XMLPersistenceManager classes. Follow these steps to activate 100% FS storage:

  1. Comment out the following blocks (there are 3 in total):

    <!-- HibernateStore: uses RDBMS + Hibernate for storage -->
    <FileSystem class="org.jboss.portal.cms.hibernate.HibernateStore">
    ...
    </FileSystem>
    

    And uncomment the blocks under them (there are 3 in total):

    <!-- LocalFileSystem: uses FileSystem for storage. -->
    <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
    ...
    </FileSystem>

  2. Now comment out the following blocks (there are 2 in total):

    <!-- HibernatePersistentManager: uses RDBMS + Hibernate for storage -->
    <PersistenceManager class="org.jboss.portal.cms.hibernate.state.HibernatePersistenceManager">
    ...
    </PersistenceManager>

    And uncomment the blocks under them (there are 2 in total):

    <!-- XMLPersistenceManager: uses FileSystem for storage -->
    <PersistenceManager class="org.apache.jackrabbit.core.state.xml.XMLPersistenceManager"/>
    

Warning

If you do any change at the workspaces configuration you will need to delete the file $JBOSS_HOME/server/xxx/data/portal/cms/conf/workspaces/default/workspace.xml before restarting JBoss or redeploying JBoss Portal. If you forget to do that, the changes won't affect the CMS. For the same reason, you also need to delete that file if you recompile JBoss Portal after changing the name of the datasource. Note that on a cluster environment, you need to remove that file (if it exists) on all the nodes.

18.4.3.2. 100% Database Storage

This is the default configuration for the CMS store. Please view the original jboss-portal.sar/portal-cms.sar/META-INF/jboss-service.xml , for guidance on how to reset it.

18.4.3.3. Mixed Storage

Mixed storage consists of meta-data being stored in the DB and blobs being stored on the Filesystem. This is the recommended setting for those of you that serve large files or stream media content.

Setting the repository this way is simple. Change every instance in the file jboss-portal.sar/portal-cms.sar/META-INF/jboss-service.xml , from:

<param name="externalBLOBs" value="false"/>

to:

<param name="externalBLOBs" value="true"/>

Warning

If you do any change at the workspaces configuration you will need to delete the file $JBOSS_HOME/server/xxx/data/portal/cms/conf/workspaces/default/workspace.xml before restarting JBoss or redeploying JBoss Portal. If you forget to do that, the changes won't affect the CMS. For the same reason, you also need to delete that file if you recompile JBoss Portal after changing the name of the datasource. Note that on a cluster environment, you need to remove that file (if it exists) on all the nodes.

18.5. Localization Support

The CMS Portlet now serves content based on the user's locale setting. For example: if a user's locale is set to Spanish in his browser, and he requests URL: default/index.html , the CMSPortlet will first try and retrieve the Spanish version of that file. If a Spanish version is not found, it will then try and retrieve the default language version set for the CMSPortlet.

18.6. CMS Service

The CMS portlet calls a CMS service that can be reused in your own portlets.

18.6.1. CMS Interceptors

Since JBoss Portal 2.4 you can add your own interceptor stack to the CMS service. The interceptors are called around each command (Get a file, write a file, create a folder...), this is a very easy way to customize some actions based on your needs.

To create your own interceptor you just need to extend the org.jboss.portal.cms.CMSInterceptor class and provide the content of the invoke(JCRCommand) method. Do not forget to make a call to JCRCommand.invokeNext() or the command will never be executed.

JBoss Portal relies on the interceptor mechanism to integrate its Fine Grained Security Service and the Publish/Approve Workflow Service

To add or remove an interceptor, you just need to edit the following file: portal-cms-sar/META-INF/jboss-service.xml. It works the same way as the server interceptor, for each interceptor you need to define an mbean then add it to the cms interceptor stack. For example, if you have the 2 default interceptors, you should have the following lines in the jboss-service.xml file:

<!-- ACL Security Interceptor -->
<mbean code="org.jboss.portal.cms.impl.interceptors.ACLInterceptor"
	name="portal:service=Interceptor,type=Cms,name=ACL" xmbean-dd=""
	xmbean-code="org.jboss.portal.jems.as.system.JBossServiceModelMBean">
	<xmbean />
	<attribute name="JNDIName">
		java:/portal/cms/ACLInterceptor
	</attribute>
	<attribute name="CmsSessionFactory">
		java:/portal/cms/CMSSessionFactory
	</attribute>
	<attribute name="IdentitySessionFactory">
		java:/portal/IdentitySessionFactory
	</attribute>
	<attribute name="DefaultPolicy">
		<policy>
			<!-- permissions on the root cms node -->
			<criteria name="path" value="/">
				<permission name="cms" action="read">
					<role name="Anonymous" />
				</permission>
				<permission name="cms" action="write">
					<role name="User" />
				</permission>
				<permission name="cms" action="manage">
					<role name="Admin" />
				</permission>
			</criteria>
			<!-- permissions on the default cms node -->
			<criteria name="path" value="/default">
				<permission name="cms" action="read">
					<role name="Anonymous" />
				</permission>
				<permission name="cms" action="write">
					<role name="User" />
				</permission>
				<permission name="cms" action="manage">
					<role name="Admin" />
				</permission>
			</criteria>
			<!-- permissions on the private/protected node -->
			<criteria name="path" value="/default/private">
				<permission name="cms" action="manage">
					<role name="Admin" />
				</permission>
			</criteria>
		</policy>
	</attribute>
	<depends optional-attribute-name="AuthorizationManager"
		proxy-type="attribute">
		portal:service=AuthorizationManager,type=cms
	</depends>
	<depends>portal:service=Hibernate,type=CMS</depends>
	<depends>
		portal:service=Module,type=IdentityServiceController
	</depends>
</mbean>

<!-- Approval Workflow Interceptor -->
<mbean
	code="org.jboss.portal.cms.impl.interceptors.ApprovalWorkflowInterceptor"
	name="portal:service=Interceptor,type=Cms,name=ApprovalWorkflow"
	xmbean-dd=""
	xmbean-code="org.jboss.portal.jems.as.system.JBossServiceModelMBean">
	<xmbean />
	<attribute name="JNDIName">
		java:/portal/cms/ApprovalWorkflowInterceptor
	</attribute>
	<depends>portal:service=Hibernate,type=CMS</depends>
</mbean>

<!-- CMS Interceptor Registration -->
<mbean
	code="org.jboss.portal.server.impl.invocation.JBossInterceptorStackFactory"
	name="portal:service=InterceptorStackFactory,type=Cms" xmbean-dd=""
	xmbean-code="org.jboss.portal.jems.as.system.JBossServiceModelMBean">
	<xmbean />
	<depends-list optional-attribute-name="InterceptorNames">
		<depends-list-element>
			portal:service=Interceptor,type=Cms,name=ACL
		</depends-list-element>
		<depends-list-element>
			portal:service=Interceptor,type=Cms,name=ApprovalWorkflow
		</depends-list-element>
	</depends-list>
</mbean>
   
            

The first two mbeans define the interceptors and the third mbean, define which interceptors to add to the CMS service.

If you create your own interceptor org.example.myCMSInterceptor, the service descriptor file will look like:

<mbean code="org.example.myCMSInterceptor"
	name="portal:service=Interceptor,type=Cms,name=MyName" xmbean-dd=""
	xmbean-code="org.jboss.portal.common.system.JBossServiceModelMBean">
	<xmbean />
</mbean>

<!-- ACL Security Interceptor -->
<mbean code="org.jboss.portal.cms.impl.interceptors.ACLInterceptor"
	name="portal:service=Interceptor,type=Cms,name=ACL" xmbean-dd=""
	xmbean-code="org.jboss.portal.jems.as.system.JBossServiceModelMBean">
	<xmbean />
	<attribute name="JNDIName">
		java:/portal/cms/ACLInterceptor
	</attribute>
	<attribute name="CmsSessionFactory">
		java:/portal/cms/CMSSessionFactory
	</attribute>
	<attribute name="IdentitySessionFactory">
		java:/portal/IdentitySessionFactory
	</attribute>
	<attribute name="DefaultPolicy">
		<policy>
			<!-- permissions on the root cms node -->
			<criteria name="path" value="/">
				<permission name="cms" action="read">
					<role name="Anonymous" />
				</permission>
				<permission name="cms" action="write">
					<role name="User" />
				</permission>
				<permission name="cms" action="manage">
					<role name="Admin" />
				</permission>
			</criteria>
			<!-- permissions on the default cms node -->
			<criteria name="path" value="/default">
				<permission name="cms" action="read">
					<role name="Anonymous" />
				</permission>
				<permission name="cms" action="write">
					<role name="User" />
				</permission>
				<permission name="cms" action="manage">
					<role name="Admin" />
				</permission>
			</criteria>
			<!-- permissions on the private/protected node -->
			<criteria name="path" value="/default/private">
				<permission name="cms" action="manage">
					<role name="Admin" />
				</permission>
			</criteria>
		</policy>
	</attribute>
	<depends optional-attribute-name="AuthorizationManager"
		proxy-type="attribute">
		portal:service=AuthorizationManager,type=cms
	</depends>
	<depends>portal:service=Hibernate,type=CMS</depends>
	<depends>
		portal:service=Module,type=IdentityServiceController
	</depends>
</mbean>

<!-- Approval Workflow Interceptor -->
<mbean
	code="org.jboss.portal.cms.impl.interceptors.ApprovalWorkflowInterceptor"
	name="portal:service=Interceptor,type=Cms,name=ApprovalWorkflow"
	xmbean-dd=""
	xmbean-code="org.jboss.portal.jems.as.system.JBossServiceModelMBean">
	<xmbean />
	<attribute name="JNDIName">
		java:/portal/cms/ApprovalWorkflowInterceptor
	</attribute>
	<depends>portal:service=Hibernate,type=CMS</depends>
</mbean>
<mbean
	code="org.jboss.portal.server.impl.invocation.JBossInterceptorStackFactory"
	name="portal:service=InterceptorStackFactory,type=Cms" xmbean-dd=""
	xmbean-code="org.jboss.portal.jems.as.system.JBossServiceModelMBean">
	<xmbean />
	<depends-list optional-attribute-name="InterceptorNames">
		<depends-list-element>
			portal:service=Interceptor,type=Cms,name=ACL
		</depends-list-element>
		<depends-list-element>
			portal:service=Interceptor,type=Cms,name=ApprovalWorkflow
		</depends-list-element>
	</depends-list>
</mbean>

<!-- CMS Interceptor Registration -->
<mbean
	code="org.jboss.portal.server.impl.invocation.JBossInterceptorStack"
	name="portal:service=InterceptorStack,type=Cms" xmbean-dd=""
	xmbean-code="org.jboss.portal.common.system.JBossServiceModelMBean">
	<xmbean />
	<depends-list optional-attribute-name="InterceptorNames">
		<depends-list-element>
			portal:service=Interceptor,type=Cms,name=ACL
		</depends-list-element>
		<depends-list-element>
			portal:service=Interceptor,type=Cms,name=ApprovalWorkflow
		</depends-list-element>
		<depends-list-element>
			portal:service=Interceptor,type=Cms,name=MyName
		</depends-list-element>
	</depends-list>
</mbean>

Note

The interceptor order is important !

To check that the interceptors have been correctly added, you can check the JMX console, by going to: http://localhost.localdomain:8080/jmx-console/HtmlAdaptor?action=inspectMBean&name=portal%3Aservice%3DInterceptorStack%2Ctype%3DCms You should notice all the interceptors in the attribute "interceptors".