This section covers configuring JBoss Portal for a clustered environment.
JBoss Portal leverages various clustered services that are found in JBoss Application Server. This section briefly details how each is leveraged:
JBoss Cache: Used to replicate data among the different hibernate session factories that are deployed in each node of the cluster.
JBoss HA Singleton:
HA-JNDI: Used to replicate a proxy that will talk to the HA CMS on the cluster.
Http Session Replication: Used to replicate the portal and the portlet sessions.
JBoss SSO: Used to replicate the user identity, an authenticated user does not have to login again when failover occurs.
When you want to run JBoss Portal on a cluster there are a few things to keep in mind:
The portal associates with each user a http session in order to keep specific objects such as:
Replicating the portal session ensures that this state will be kept in sync on the cluster, e.g The user will see exactly the same portlet window on every node of the cluster. The activation of the portal session replication is made through the configuration of the web application that is the main entry point of the portal. This setting is available in the file jboss-portal.sar/portal-server.war/WEB-INF/web.xml
<web-app> <description>JBoss Portal</description> <!-- Comment/Uncomment to enable portal session replication --> <distributable/> ... </web-app>
JBoss Portal leverages hibernate for its database access. In order to improve performances it uses the caching features provided by hibernate. On a cluster the cache needs to be replicated in order to avoid state inconsistencies. Hibernate is configured with JBoss Cache which performs that synchronization transparently. Therefore the different hibernate services must be configured to use JBoss Cache. The following hibernate configurations needs to use a replicated JBoss Cache :
The cache configuration should look like :
<!-- | Uncomment in clustered mode : use transactional replicated cache --> <property name="cache.provider_class">org.jboss.portal.core.hibernate.JMXTreeCacheProvider </property> <property name="cache.object_name">portal:service=TreeCacheProvider,type=hibernate </property> <!-- | Comment in clustered mode <property name="cache.provider_configuration_file_resource_path"> conf/hibernate/instance/ehcache.xml</property> <property name="cache.provider_class">org.hibernate.cache.EhCacheProvider</property> -->
Also we need to ensure that the cache is deployed by having in the file jboss-portal.sar/META-INF/jboss-service.xml the cache service uncommented :
<!-- | Uncomment in clustered mode : replicated cache for hibernate --> <mbean code="org.jboss.cache.TreeCache" name="portal:service=TreeCache,type=hibernate"> <depends>jboss:service=Naming</depends> <depends>jboss:service=TransactionManager</depends> <attribute name="TransactionManagerLookupClass"> org.jboss.cache.JBossTransactionManagerLookup</attribute> <attribute name="IsolationLevel">REPEATABLE_READ</attribute> <attribute name="CacheMode">REPL_SYNC</attribute> <attribute name="ClusterName">portal.hibernate</attribute> </mbean> <mbean code="org.jboss.portal.core.hibernate.JBossTreeCacheProvider" name="portal:service=TreeCacheProvider,type=hibernate"> <depends optional-attribute-name="CacheName">portal:service=TreeCache,type=hibernate </depends> </mbean>
More information can be found here.
JBoss Portal leverages the servlet container authentication for its own authentication mechanism. When the user is authenticated on one particular node he will have to reauthenticate again if a different node of the cluster (during a failover for instance) is used. This is valid only for the FORM based authentication which is the default form of authentication that JBoss Portal uses. Fortunately JBoss provides transparent reauthentication of the user called JBoss clustered SSO. Its configuration can be found in $JBOSS_HOME/server/all/deploy/jboss-web.deployer/server.xml and you will need to uncomment the following valve:
<Valve className="org.jboss.web.tomcat.service.sso.ClusteredSingleSignOn" />
More information can be found here.
The CMS backend storage relies on the Apache Jackrabbit project. Jackrabbit does not support clustering out of the box. So the portal run the Jackrabbit service on one node of the cluster using the HA-Singleton technology. The file jboss-portal.sar/portal-cms.sar/META-INF/jboss-service.xml contains the configuration. We will not reproduce it in this documentation as the changes are quite complex and numerous. Access from all nodes of the cluster is provided by a proxy bound in HA-JNDI. In order to avoid any bottleneck JBoss Cache is leveraged to cache CMS content cluster wide.
We are going to outline how to setup a two node cluster on the same machine in order to test JBoss Portal HA. The only missing part from the full fledged setup is the addition of a load balancer in front of Apache Tomcat. However a lot of documentation exist on the subject. A detailed step by step setup of Apache and mod_jk is available from the JBoss Wiki.
As we need two application servers running at the same time, we must avoid any conflict. For instance we will need Apache Tomcat to bind its socket on two different ports otherwise a network conflict will occur. We will leverage the service binding manager this chapter of the JBoss AS documentation.
The first step is to copy the all configuration of JBoss into two separate configurations that we name ports-01 and ports-02 :
>cd $JBOSS_HOME/server >cp -r all ports-01 >cp -r all ports-02
Edit the file $JBOSS_HOME/server/ports-01/conf/jboss-service.xml and uncomment the service binding manager :
<mbean code="org.jboss.services.binding.ServiceBindingManager" name="jboss.system:service=ServiceBindingManager"> <attribute name="ServerName">ports-01</attribute> <attribute name="StoreURL"> ${jboss.home.url}/docs/examples/binding-manager/sample-bindings.xml</attribute> <attribute name="StoreFactoryClassName">org.jboss.services.binding.XMLServicesStoreFactory</attribute> </mbean>
Edit the file $JBOSS_HOME/server/ports-02/conf/jboss-service.xml, uncomment the service binding manager and change the value ports-01 into ports-02:
<mbean code="org.jboss.services.binding.ServiceBindingManager" name="jboss.system:service=ServiceBindingManager"> <attribute name="ServerName">ports-02</attribute> <attribute name="StoreURL"> ${jboss.home.url}/docs/examples/binding-manager/sample-bindings.xml</attribute> <attribute name="StoreFactoryClassName"> org.jboss.services.binding.XMLServicesStoreFactory</attribute> </mbean>
Setup a database that will be shared by the two nodes and obviously we cannot use an embedded database. For instance using postgresql we would need to copy the file portal-postgresql-ds.xml into $JBOSS_HOME/server/ports-01/deploy and $JBOSS_HOME/server/ports-02/deploy.
Copy JBoss Portal HA to the deploy directory of the two configurations.
To improve CMS performance JBoss Cache is leveraged to cache the content cluster wide. We recommend that you use the following version of JBoss Cache for best performance:
When building from source the following command: {core}/build.xml deploy-ha automatically upgrades your JBoss Cache version.
Alternative: If upgrading your JBoss Cache version is not an option, the following configuration change is needed in the jboss-portal-ha.sar/portal-cms.sar/META-INF/jboss-service.xml. Replace the following configuration in the cms.pm.cache:service=TreeCache Mbean:
<!-- Configuring the PortalCMSCacheLoader CacheLoader configuration for 1.4.0 --> <attribute name="CacheLoaderConfiguration"> <config> <passivation>false</passivation> <preload></preload> <shared>false</shared> <cacheloader> <class>org.jboss.portal.cms.hibernate.state.PortalCMSCacheLoader</class> <properties></properties> <async>false</async> <fetchPersistentState>false</fetchPersistentState> <ignoreModifications>false</ignoreModifications> </cacheloader> </config> </attribute>
with the following configuration:
<!-- Configuring the PortalCMSCacheLoader CacheLoader configuratoon for 1.2.4SP2 --> <attribute name="CacheLoaderClass">org.jboss.portal.cms.hibernate.state.PortalCMSCacheLoader </attribute> <attribute name="CacheLoaderConfig" replace="false"></attribute> <attribute name="CacheLoaderPassivation">false</attribute> <attribute name="CacheLoaderPreload"></attribute> <attribute name="CacheLoaderShared">false</attribute> <attribute name="CacheLoaderFetchTransientState">false</attribute> <attribute name="CacheLoaderFetchPersistentState">false</attribute> <attribute name="CacheLoaderAsynchronous">false</attribute>
Finally we can start both servers, open two shells and execute :
>cd $JBOSS_HOME/bin >sh run.sh -c ports-01
>cd $JBOSS_HOME/bin >sh run.sh -c ports-02
Web containers offer the capability to replicate sessions of web applications. In the context of a portal using portlets the use case is different. The portal itself is a web application that benefits of web application session replication. We have to make the distinction between local or remote portlets :
The servlet specification is very loose on the subject of replication and does not state anything about the replication of sessions during a dispatched request. JBoss Portal offers a portlet session replication mechanism that leverages the usage of the portal session instead which has several advantages
There are, however, some limitations. For example, you can only replicate portlet-scoped attributes of a portlet session. This means that any application-scoped attribute are not replicated.
The mandatory step to make JBoss Portal able to replicate portlet sessions is to configure the portal web application to be distributed as explained in Section 13.3.1, “Portal Session Replication”
In order to activate portlet session replication you need to:
<web-app> ... <listener> <listener-class> org.jboss.portal.portlet.session.SessionListener </listener-class> </listener> ... </web-app>
Example web.xml
<portlet-app> ... <portlet> <portlet-name>YourPortlet</portlet-name> ... <session-config> <distributed>true</distributed> </session-config> ... </portlet> ... </portlet-app>
Configure YourPortlet to be replicated in jboss-portlet.xml
As we noted above there are advantages as well as limitations to the clustering configuration
public void processAction(ActionRequest req, ActionResponse resp) throws PortletException, IOException { ... if ("addItem".equals(action)) { PortletSession session = req.getPortletSession(); ShoppingCart cart = (PortletSession)session.getAttribute("cart"); cart.addItem(item); // Perform an explicit set in order to signal to the container that the object // state has changed session.setAttribute("cart", cart); } ... }