JBoss.orgCommunity Documentation
Since GateIn beta 2, there are a set of features added to customize a GateIn instance without modifying the GateIn binary. This usecase will be called portal extension in this documentation. Those features are also required to be able to launch several portal instances at the same time, in "eXo terminology" that means to have several "portal.war".
Up to now, to create an application over an GateIn such as DMS, WCM, CS and KS, we need to modify files into the "portal.war". This has many painful consequences, such as:
It is quite hard to manage from the support point of view since we never know if or how the customer changed his eXo product.
It is hard to be able to package several eXo products (WCM, CS...) as we need to merge everything manually which is quite error prone.
Finally, at the very beginning, eXo was developed to be able to support several portal instances (which also means several portal containers) but with the time several bad practices made it impossible. So it was important to review the whole code base in order to help/enforce all the GateIn developers to follow the "good practices".
To be able to migrate an application to GateIn, the first thing we need to do is to ensure that our application supports properly several portal container instances. The following section aims to help you to be compatible with GateIn.
Now if we need to get the portal container name (even in a standalone mode: in case of standalone mode the default value will be returned), we can:
If the component is instantiated by Pico container, you can add in the constructor of your component, the component ExoContainerContext, then call the method getPortalContainerName()
If the component is not instantiated by Pico container, you can call at runtime the static method PortalContainer.getCurrentPortalContainerName()
In js files, you can use the variable currentContext if your script must be loaded before the variable eXo.env.server.context, otherwise use eXo.env.server.context instead.
In jsp files, you can use request.getContextPath().
Now if we need to get the rest context name (even in a standalone mode: in case of standalone mode the default value will be returned), we can:
If the component is instantiated by Pico container, you can add in the constructor of your component, the component ExoContainerContext, then call the method getRestContextName()
If the component is not instantiated by Pico container, you can call at runtime the static method PortalContainer.getCurrentRestContextName()
Now if we need to get the realm name (even in a standalone mode: in case of standalone mode the default value will be returned), we can:
If the component is instantiated by Pico container, you can add in the constructor of your component, the component ExoContainerContext, then call the method getRealmName()
If the component is not instantiated by Pico container, you can call at runtime the static method PortalContainer.getCurrentRealmName()
Now all your Http Filters that need to get the current ExoContainer must extends org.exoplatform.container.web.AbstractFilter. You just need to call the method getContainer() to get the current ExoContainer.
Now all your HttpServlets that need to get the current ExoContainer must extends org.exoplatform.container.web.AbstractHttpServlet. This abstract class will ensure that the environment has been properly set, so you will be able to call the usual methods such as ExoContainerContext.getCurrentContainer() (if it must also be compatible with the standalone mode) or PortalContainer.getInstance() (if it will only work on a portal environment mode).
If you had to implement the method service(HttpServletRequest req, HttpServletResponse res), now you will need to implement onService(ExoContainer container, HttpServletRequest req, HttpServletResponse res), this method will directly give you the current ExoContainer in its signature.
In the class org.exoplatform.container.web.AbstractHttpServlet you have a method called requirePortalEnvironment() that is used to indicate that we would like the abstract class to setup or not the full portal environment ( PortalContainer, ClassLoader and ServletContext) before executing the servlet. This value should return true when the servlet is executed within the web application of a portal container. By default, it checks if the name of the current ServletContext is a portal container name, it is sufficient in most of cases but you can still overload this method if you already know that the servlet will always been executed within the web application of portal container (i.e. the method always return true) or will never be executed within the web application of a portal container (i.e. the method always return false) .
Now all your HttpSessionListeners that need to get the current ExoContainer must extends org.exoplatform.container.web.AbstractHttpSessionListener. This abstract class will give you the current ExoContainer directly in the signature of the methods to implement which are _ onSessionCreated(ExoContainer container, HttpSessionEvent event)\_ and onSessionDestroyed(ExoContainer container, HttpSessionEvent event)
You will also need to implement the method called requirePortalEnvironment() that is used to indicate that we would like the abstract class to setup or not the full portal environment ( PortalContainer and ClassLoader) before processing the event. This value should return true when the event is processed within the web application of a portal container.
If your Http Filter or your HttpServlet requires a PortalContainer to initialize, you need to convert your code in order to launch the code responsible for the initialization in the method onAlreadyExists of an org.exoplatform.container.RootContainer.PortalContainerInitTask.
We need to rely on init tasks, in order to be sure that the portal container is at the right state when the task is executed, in other words the task could be delayed if you try to execute it too early. Each task is linked to a web application, so when we add a new task, we first retrieve all the portal containers that depend on this web application according to the PortalContainerDefinitions, and for each container we add the task in a sorted queue which order is in fact the order of the web applications dependencies defined in the PortalContainerDefinition. If no PortalContainerDefinition can be found we execute synchronously the task which is in fact the old behavior (i.e. without the starter).
The supported init tasks are:
The org.exoplatform.container.RootContainer.PortalContainerPreInitTask which are executed before the portal container has been initialized
The org.exoplatform.container.RootContainer.PortalContainerPostInitTask which are executed after the portal container has been initialized
The org.exoplatform.container.RootContainer.PortalContainerPostCreateTask which are executed after the portal container has been fully created (i.e. after all the post init tasks).
An init task is defined as below:
/** * This interface is used to define a task that needs to be launched at a given state during the * initialization of a portal container */ public static interface PortalContainerInitTask { /** * This method allows the implementation to define what the state "already exists" * means for a portal container * * @param portalContainer the value of the current portal container * @return <code>true</code> if the portal container exists according to the task * requirements, <code>false</code> otherwise */ public boolean alreadyExists(PortalContainer portalContainer); /** * This method is called if the related portal container has already been registered * * @param context the servlet context of the web application * @param portalContainer the value of the current portal container */ public void onAlreadyExists(ServletContext context, PortalContainer portalContainer); /** * Executes the task * * @param context the servlet context of the web application * @param container The portal container on which we would like to execute the task */ public void execute(ServletContext context, PortalContainer portalContainer); /** * @return the type of the task */ public String getType(); }
To add a task, you can either call:
PortalContainer.addInitTask(ServletContext context, PortalContainerInitTask task) in order to execute the task on all the portal containers that depend on the given ServletContext according to the PortalContainerDefinitions.
PortalContainer.addInitTask(ServletContext context, PortalContainerInitTask task, String portalContainerName) in order to execute the task on a given portal container.
RootContainer.addInitTask(ServletContext context, PortalContainerInitTask task) in order to execute the task on the portal container which name is the name of the given ServletContext.
We will take for example the class GadgetRegister that is used to register new google gadgets on a given portal container.
The old code was:
... public class GadgetRegister implements ServletContextListener { ... public void contextInitialized(ServletContextEvent event) { try { ExoContainer pcontainer = ExoContainerContext.getContainerByName("portal") ; SourceStorage sourceStorage = (SourceStorage)pcontainer.getComponentInstanceOfType(SourceStorage.class); ... } ... }
The new code relies on a org.exoplatform.container.RootContainer.PortalContainerPostInitTask, as you can see below
... public class GadgetRegister implements ServletContextListener { ... public void contextInitialized(ServletContextEvent event) { // Create a new post init task final PortalContainerPostInitTask task = new PortalContainerPostInitTask() { public void execute(ServletContext context, PortalContainer portalContainer) { contextInitialized(context, portalContainer); } }; // Add the init task to all the related portal containers PortalContainer.addInitTask(event.getServletContext(), task); } private void contextInitialized(ServletContext context, PortalContainer pcontainer) { try { SourceStorage sourceStorage = (SourceStorage)pcontainer.getComponentInstanceOfType(SourceStorage.class); ... } ... }
Now all your LoginModules that need to get the current ExoContainer must extends org.exoplatform.services.security.jaas.AbstractLoginModule. You just need to call the method getContainer() to get the current ExoContainer.
The class org.exoplatform.services.security.jaas.AbstractLoginModule supports 2 login module options which are portalContainerName and realmName, to allow you to indicate the realm name and the portal container name, if you want to change the default value.
A local variable that stores a component dependency must not be static. In other words, when you create a component A that depends on component B, we don't store B in a static variable of A otherwise we cannot have several different instances of A in the same JVM which is not compatible with a multi-portal instance.
We will have more and more extensible components (i.e. that can be extended thanks to an external plugin) which means that those components can only be initialized in the start method, thus it is not a good practice to initialize a component in its constructor if this initialization uses other components because those components may not be initialized. For example, now the ResourceBundleService is extensible, so if you create a component that depends on the ResourceBundleService and you need the ResourceBundleService to initialize your component, your component will need to be "Startable" and you will have to initialize your component in a start method.
The main difference with previous versions is the way to package your application, in the previous versions you had to change the content of the file portal.war in order to customize the portal. Now we more consider your application as an add-on that you can packaged in another ear/war file. You just need to follow some rules in order to notify the platform that it must take into account your add-on in order to customize the portal.
Among other things, you will have to:
Indicate the platform how to initialize and manage your application by defining and registering the PortalContainerDefinition related to your portal instance.
Deploy the starter ear/war file that is used to create and start all the portals (i.e. portal containers).
Please take into account, that you need to ensure that the starter is launched after all the other ear/war files.
If you don't need to customize the portal, you don't have to deploy the starter. The old way to deploy an application is still compatible.
An extension is just a set of files that we use to customize or add new features to a given portal. An extension must be the least intrusive as possible, as we could potentially have several extensions for the same portal. In other words, we are supposed to only add what is missing in the portal and avoid changing or duplicated files that are in the portal.war.
The sarter is a web application that has been added to create and start all the portals (i.e. portal containers) at the same time when all the other web applications have already been started. In fact all other web applications can potentially defined several things at startup such as skins, javascripts, google gadgets and configuration files, thus the loading order is important as we can redefine skins or configuration files or a javascript from a web application 1 could depend on another javascript from a web application 2 so if the web application 2 is loaded after the web application 1, we will get errors in the merged javascript file.
If a PortalContainerDefinition has been defined, the loading order will be the order that has been used to define the list of dependency. And if you defined a PortalContainerDefinition you need to deploy the starter otherwise the loading order will be the default one (i.e. the loading order of the Application Server)
So if you need to customize your portal by adding a new extension and/or a new portal, you need to defined the related PortalContainerDefinitions so you need to deploy also the starter. Otherwise, you don't need to define any PortalContainerDefinition and to deploy the starter.
Each portal instance has its own portal container which allows the portal to have its own set of components/services. It will ensure the isolation between the different portal instances.
A PortalContainerDefinition allows you to indicate the platform how it must initialize and manage your portal. In a PortalContainerDefinition, you can define a set of properties, such as:
The name of the portal container
The name of the context name of the rest web application
The name of the realm
The list of all the dependencies of the portal container ordered by priority
You can define and register a PortalContainerDefinition thanks to an external plugin that has to be treated at the RootContainer level. In other words, your configuration file must be a file conf/configuration.xml packaged into a jar file or $AS_HOME/exo-conf/configuration.xml (for more details, please have a look to the article Container Configuration).
See below an example of configuration file that define and register a PortalContainerDefinition:
<?xml version="1.0" encoding="UTF-8"?> <configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.exoplatform.org/xml/ns/kernel_1_3.xsd http://www.exoplatform.org/xml/ns/kernel_1_3.xsd" xmlns="http://www.exoplatform.org/xml/ns/kernel_1_3.xsd"> <external-component-plugins> <!-- The full qualified name of the PortalContainerConfig --> <target-component>org.exoplatform.container.definition.PortalContainerConfig</target-component> <component-plugin> <!-- The name of the plugin --> <name>Add PortalContainer Definitions</name> <!-- The name of the method to call on the PortalContainerConfig in order to register the PortalContainerDefinitions --> <set-method>registerPlugin</set-method> <!-- The full qualified name of the PortalContainerDefinitionPlugin --> <type>org.exoplatform.container.definition.PortalContainerDefinitionPlugin</type> <init-params> <object-param> <name>portal</name> <object type="org.exoplatform.container.definition.PortalContainerDefinition"> <!-- The name of the portal container --> <field name="name"><string>portal</string></field> <!-- The name of the context name of the rest web application --> <field name="restContextName"><string>rest</string></field> <!-- The name of the realm --> <field name="realmName"><string>exo-domain</string></field> <!-- All the dependencies of the portal container ordered by loading priority --> <field name="dependencies"> <collection type="java.util.ArrayList"> <value> <string>eXoResources</string> </value> <value> <string>portal</string> </value> <value> <string>dashboard</string> </value> <value> <string>exoadmin</string> </value> <value> <string>eXoGadgets</string> </value> <value> <string>eXoGadgetServer</string> </value> <value> <string>rest</string> </value> <value> <string>web</string> </value> <value> <string>wsrp-producer</string> </value> <value> <string>sample-ext</string> </value> </collection> </field> </object> </object-param> </init-params> </component-plugin> </external-component-plugins> </configuration>
In the previous example, we define a portal container called "portal", which rest context name is "rest", which realm name is "exo-domain" and which dependencies are the web applications "eXoResources", "portal"... The platform will load first "eXoResources", then "portal" and so on.
The dependency order defined into the PortalContainerDefinition is really crucial since it will be interpreted the same way by several components of the platform. All those components, will consider the 1st element in the list is less important than the second element and so on.
So it is currently used to:
Know loading order of all the dependencies
If we have several PortalContainerConfigOwner (see next section for more details about a PortalContainerConfigOwner)
The ServletContext of all the PortalContainerConfigOwner will be unified, if we use the unified ServletContext (PortalContainer.getPortalContext()) to get a resource, it will try to get the resource in the ServletContext of the most important PortalContainerConfigOwner (i.e. last in the dependency list) and if it cans find it, it will try with the second most important PortalContainerConfigOwner and so on.
The ClassLoader of all the PortalContainerConfigOwner will be unified, if we use the unified ClassLoader (PortalContainer.getPortalClassLoader()) to get a resource, it will try to get the resource in the ClassLoader of the most important PortalContainerConfigOwner (i.e. last in the dependency list) and if it cans find it, it will try with the second most important PortalContainerConfigOwner and so on.
To do that you need first to change the default values used by a PortalContainer that has not been defined thanks to a PortalContainerDefinition. Those default values can be modified thanks to a set of init parameters of the component PortalContainerConfig.
The component PortalContainerConfig must be registered at the RootContainer level. In other words, your configuration file must be a file conf/configuration.xml packaged into a jar file or $AS_HOME/exo-conf/configuration.xml (for more details please have a look to the article Container Configuration).
In the example below we will rename:
The portal name "portal" to "myPortal".
The rest servlet context name "rest" to "myRest".
The realm name "exo-domain" to "my-exo-domain".
See below an example
<?xml version="1.0" encoding="UTF-8"?> <configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.exoplatform.org/xml/ns/kernel_1_3.xsd http://www.exoplatform.org/xml/ns/kernel_1_3.xsd" xmlns="http://www.exoplatform.org/xml/ns/kernel_1_3.xsd"> <component> <!-- The full qualified name of the PortalContainerConfig --> <type>org.exoplatform.container.definition.PortalContainerConfig</type> <init-params> <!-- The name of the default portal container --> <value-param> <name>default.portal.container</name> <value>myPortal</value> </value-param> <!-- The name of the default rest ServletContext --> <value-param> <name>default.rest.context</name> <value>myRest</value> </value-param> <!-- The name of the default realm --> <value-param> <name>default.realm.name</name> <value>my-exo-domain</value> </value-param> </init-params> </component> </configuration>
Once your configuration is ready, you need to:
Update the file WEB-INF/web.xml of the file "portal.war" by changing the "display-name" (the new value is "myPortal") and the "realm-name" in the "login-config" (the new value is "my-exo-domain").
If you use JBoss AS: Update the file WEB-INF/jboss-web.xml of the file "portal.war" by changing the "security-domain" (the new value is "java:/jaas/my-exo-domain").
Rename the "portal.war" to "myPortal.war" (or "02portal.war" to "02myPortal.war")
Update the file WEB-INF/web.xml of the file "rest.war" by changing the "display-name" (the new value is "myRest") and the "realm-name" in the "login-config" (the new value is "my-exo-domain").
If you use JBoss AS: Update the file WEB-INF/jboss-web.xml of the file "rest.war" by changing the "security-domain" (the new value is "java:/jaas/my-exo-domain").
Rename the "rest.war" to "myRest.war"
If "portal.war" and "rest.war" were embedded into an ear file: Update the file META-INF/application.xml of the file "exoplatform.ear" by remaming "02portal.war" to "02myPortal.war", "portal" to "myPortal", "rest.war" to "myRest.war" and "rest" to "myRest".
The end of the process depends on your application server
You need to change the name of the application policy in your file conf/login-config.xml (the new name is "my-exo-domain").
You need to:
Update the file tomcat/conf/Catalina/localhost/portal.xml by changing the "path" (the new value is "/myPortal"), the "docBase" (the new value is "myPortal") and the "appName" in the "Realm" definition (the new value is "my-exo-domain").
Rename the file tomcat/conf/Catalina/localhost/portal.xml to myPortal.xml.
Update the file tomcat/conf/Catalina/localhost/rest.xml by changing the "path" (the new value is "/myRest"), the "docBase" (the new value is "myRest") and the "appName" in the "Realm" definition (the new value is "my-exo-domain").
Rename the file tomcat/conf/Catalina/localhost/rest.xml to myRest.xml.
Change the realm name in the file tomcat/conf/jaas.conf (the new name is "my-exo-domain").
To indicate the platform that a given web application has configuration file to provide, you need to:
Add the ServletContextListener org.exoplatform.container.web.PortalContainerConfigOwner in its web.xml.
Add the servlet context name of this web application as a new dependency in the PortalContainerDefinition of all the portal containers for which you want to share the configuration file embedded into the war file, located at WEB-INF/conf/configuration.xml.
The simple fact to add this Servlet Context Listener, will add the Servlet Context of this web application to the Unified Servlet Context of all the PortalContainers that depend on this web application according to their PortalContainerDefinition.
The position of the servlet context name of this web application in the dependency list is important since the last configuration file loaded has always right towards other configuration files. Each configuration file loaded, could potentially redefine a configuration file that has already been loaded. Moreover, as we now use a unified Servlet Context to load the configuration files, if you want for instance to import the file war:/conf/database/database-configuration.xml and this file exists in 2 different web applications, the file from the last (according to the dependency order) web application will be loaded.
A portal is implicitly considered as a PortalContainerConfigOwner without having to define the ServletContextListener org.exoplatform.container.web.PortalContainerConfigOwner in its web.xml.
See an example of a web.xml below:
<?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd"> <web-app> <display-name>sample-ext</display-name> <context-param> <param-name>org.exoplatform.frameworks.jcr.command.web.fckeditor.digitalAssetsWorkspace</param-name> <param-value>collaboration</param-value> <description>Binary assets workspace name</description> </context-param> <context-param> <param-name>org.exoplatform.frameworks.jcr.command.web.fckeditor.digitalAssetsPath</param-name> <param-value>/Digital Assets/</param-value> <description>Binary assets path</description> </context-param> <context-param> <param-name>CurrentFolder</param-name> <param-value>/Digital Assets/</param-value> <description>Binary assets workspace name</description> </context-param> <!-- ================================================================== --> <!-- RESOURCE FILTER TO CACHE MERGED JAVASCRIPT AND CSS --> <!-- ================================================================== --> <filter> <filter-name>ResourceRequestFilter</filter-name> <filter-class>org.exoplatform.portal.application.ResourceRequestFilter</filter-class> </filter> <filter-mapping> <filter-name>ResourceRequestFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- ================================================================== --> <!-- LISTENER --> <!-- ================================================================== --> <listener> <listener-class>org.exoplatform.container.web.PortalContainerConfigOwner</listener-class> </listener> <!-- ================================================================== --> <!-- SERVLET --> <!-- ================================================================== --> <servlet> <servlet-name>GateInServlet</servlet-name> <servlet-class>org.gatein.wci.api.GateInServlet</servlet-class> <load-on-startup>0</load-on-startup> </servlet> <!-- ================================================================= --> <servlet-mapping> <servlet-name>GateInServlet</servlet-name> <url-pattern>/gateinservlet</url-pattern> </servlet-mapping> </web-app>
A portal extension is in fact a web application declared as a PortalContainerConfigOwner (see previous section for more details about a PortalContainerConfigOwner) that has been added to the dependency list of the PortalContainerDefinition of a given portal.
See below an example of configuration file that add the portal extension "portal-ext" to the dependency list of the portal "portal":
<?xml version="1.0" encoding="UTF-8"?> <configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.exoplatform.org/xml/ns/kernel_1_3.xsd http://www.exoplatform.org/xml/ns/kernel_1_3.xsd" xmlns="http://www.exoplatform.org/xml/ns/kernel_1_3.xsd"> <external-component-plugins> <!-- The full qualified name of the PortalContainerConfig --> <target-component>org.exoplatform.container.definition.PortalContainerConfig</target-component> <component-plugin> <!-- The name of the plugin --> <name>Add PortalContainer Definitions</name> <!-- The name of the method to call on the PortalContainerConfig in order to register the PortalContainerDefinitions --> <set-method>registerPlugin</set-method> <!-- The full qualified name of the PortalContainerDefinitionPlugin --> <type>org.exoplatform.container.definition.PortalContainerDefinitionPlugin</type> <init-params> <object-param> <name>portal</name> <object type="org.exoplatform.container.definition.PortalContainerDefinition"> <!-- The name of the portal container --> <field name="name"><string>portal</string></field> <!-- The name of the context name of the rest web application --> <field name="restContextName"><string>rest</string></field> <!-- The name of the realm --> <field name="realmName"><string>exo-domain</string></field> <!-- All the dependencies of the portal container ordered by loading priority --> <field name="dependencies"> <collection type="java.util.ArrayList"> <value> <string>eXoResources</string> </value> <value> <string>portal</string> </value> <value> <string>dashboard</string> </value> <value> <string>exoadmin</string> </value> <value> <string>eXoGadgets</string> </value> <value> <string>eXoGadgetServer</string> </value> <value> <string>rest</string> </value> <value> <string>web</string> </value> <value> <string>wsrp-producer</string> </value> <!-- The sample-ext has been added at the end of the dependency list in order to have the highest priority towards the other web applications and particularly towards "portal" --> <value> <string>sample-ext</string> </value> </collection> </field> </object> </object-param> </init-params> </component-plugin> </external-component-plugins> </configuration>
To duplicate the entire "portal.war" file to create a new portal, you just need to duplicate the following files from the original "portal.war":
login/jsp/login.jsp
login/skin: You can customize the css files and pictures
bookmark.jsp
favicon.ico: You can replace it by your own logo
index.jsp
portal-unavailable.jsp
portal-warning.jsp
WEB-INF/web.xml: You just need to change the "display-name" and set a different value for the "realm-name" in the "login-config". Indeed, we must have one realm name per portal.
WEB-INF/jboss-web.xml: If you use JBoss AS, you need to duplicate also this file and set the new "security-domain" with the new realm name.
You need also to duplicate the "rest.war" file to create a dedicated rest web application for your portal as we must have one rest web application per portal, in fact you just need to duplicate the following files from the original "rest.war":
WEB-INF/web.xml: You just need to change the "display-name" and set a different value for the "realm-name" in the "login-config". Indeed, we must have one realm name per portal.
WEB-INF/jboss-web.xml: If you use JBoss AS, you need to duplicate also this file and set the new "security-domain" with the new realm name.
Finally, you need to register and define the corresponding PortalContainerDefinition. The PortalContainerDefinition of your portal will be composed of:
The name of new portal
The name of the context name of the new rest web application
The name of the new realm
The list of all the dependencies of the original portal, with the new name of the rest web application instead of the old name (i.e. "rest") and with a new dependency which is in fact the name of your portal. As we leave the dependency of the original portal in the list of dependencies, it will load the configuration files of original "portal.war" file.
See an example below:
<?xml version="1.0" encoding="UTF-8"?> <configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.exoplatform.org/xml/ns/kernel_1_3.xsd http://www.exoplatform.org/xml/ns/kernel_1_3.xsd" xmlns="http://www.exoplatform.org/xml/ns/kernel_1_3.xsd"> <external-component-plugins> <!-- The full qualified name of the PortalContainerConfig --> <target-component>org.exoplatform.container.definition.PortalContainerConfig</target-component> <component-plugin> <!-- The name of the plugin --> <name>Add PortalContainer Definitions</name> <!-- The name of the method to call on the PortalContainerConfig in order to register the PortalContainerDefinitions --> <set-method>registerPlugin</set-method> <!-- The full qualified name of the PortalContainerDefinitionPlugin --> <type>org.exoplatform.container.definition.PortalContainerDefinitionPlugin</type> <init-params> <object-param> <name>sample-portal</name> <object type="org.exoplatform.container.definition.PortalContainerDefinition"> <!-- The name of the portal container --> <field name="name"><string>sample-portal</string></field> <!-- The name of the context name of the rest web application --> <field name="restContextName"><string>rest-sample-portal</string></field> <!-- The name of the realm --> <field name="realmName"><string>exo-domain-sample-portal</string></field> <!-- All the dependencies of the portal container ordered by loading priority --> <field name="dependencies"> <collection type="java.util.ArrayList"> <value> <string>eXoResources</string> </value> <value> <string>portal</string> </value> <value> <string>dashboard</string> </value> <value> <string>exoadmin</string> </value> <value> <string>eXoGadgets</string> </value> <value> <string>eXoGadgetServer</string> </value> <value> <string>rest-sample-portal</string> </value> <value> <string>web</string> </value> <value> <string>wsrp-producer</string> </value> <value> <string>sample-portal</string> </value> </collection> </field> </object> </object-param> </init-params> </component-plugin> </external-component-plugins> </configuration>
A portal is implicitly a PortalContainerConfigOwner which means that it shares the configuration file embedded into the war file, located at WEB-INF/conf/configuration.xml
The position of the servlet context name of this web application in the dependency list is important since the last configuration file loaded has always right towards other configuration files. Each configuration file loaded, could potentially redefine a configuration file that has already been loaded. Moreover, as we now use a unified Servlet Context to load the configuration files, if you want for instance to import the file war:/conf/database/database-configuration.xml and this file exists in 2 different web applications, the file from the last (according to the dependency order) web application will be loaded.
Now, the ConfigurationManager uses by default the unified servlet context of the portal in order to get any resources in particular the configuration files. The unified servlet context is aware of the priorities that has been set in the PortalContainerDefinition of the portal. In other words, if you want for instance to import the file war:/conf/database/database-configuration.xml and this file exists in 2 different web applications, the file from the last (according to the dependency order) web application will be loaded.
So, in order to avoid issues when we would like to package several products at the same time (i.e. WCM, DMS, CS, KS), we need to:
Avoid the best you can to redefine a configuration file from the "portal.war" by using the exact same path (like the previous example)
Add your configuration files in a dedicated folder which name will be the name of the product, in oder to ensure that no other products will use the same path
The example below, is an the example of a file WEB-INF/conf/configuration.xml of the product "sample-ext".
<?xml version="1.0" encoding="ISO-8859-1"?> <configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.exoplatform.org/xml/ns/kernel_1_3.xsd http://www.exoplatform.org/xml/ns/kernel_1_3.xsd" xmlns="http://www.exoplatform.org/xml/ns/kernel_1_3.xsd"> <import>war:/conf/sample-ext/common/common-configuration.xml</import> <import>war:/conf/sample-ext/jcr/jcr-configuration.xml</import> <import>war:/conf/sample-ext/portal/portal-configuration.xml</import> <import>war:/conf/sample-ext/web/web-inf-extension-configuration.xml</import> </configuration>
In your configuration file, you can use a special variable called container.name.suffix in order to add a suffix to values that could change between portal containers. The value of this variable will be an empty sting if no PortalContainerDefinition has been defined otherwise the value will be \-$portal.container.name. See an example below:
<?xml version="1.0" encoding="ISO-8859-1"?> <configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.exoplatform.org/xml/ns/kernel_1_3.xsd http://www.exoplatform.org/xml/ns/kernel_1_3.xsd" xmlns="http://www.exoplatform.org/xml/ns/kernel_1_3.xsd"> <component> <key>org.exoplatform.services.database.HibernateService</key> <jmx-name>database:type=HibernateService</jmx-name> <type>org.exoplatform.services.database.impl.HibernateServiceImpl</type> <init-params> <properties-param> <name>hibernate.properties</name> <description>Default Hibernate Service</description> <property name="hibernate.show_sql" value="false"/> <property name="hibernate.cglib.use_reflection_optimizer" value="true"/> <property name="hibernate.connection.url" value="jdbc:hsqldb:file:../temp/data/exodb${container.name.suffix}"/> <property name="hibernate.connection.driver_class" value="org.hsqldb.jdbcDriver"/> <property name="hibernate.connection.autocommit" value="true"/> <property name="hibernate.connection.username" value="sa"/> <property name="hibernate.connection.password" value=""/> <property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/> <property name="hibernate.c3p0.min_size" value="5"/> <property name="hibernate.c3p0.max_size" value="20"/> <property name="hibernate.c3p0.timeout" value="1800"/> <property name="hibernate.c3p0.max_statements" value="50"/> </properties-param> </init-params> </component> </configuration>
Now you can add new JCR repositories or workspaces thanks to an external plugin, the configuration of your JCR Repositories will be merged knowing that the merge algorithm will:
Add missing Repository Definitions and/or Workspace Definitions.
Change the properties of a Repository Definition if it has already been defined.
Replace the Workspace Definition if it has already been defined.
See an example of jcr-configuration.xml below:
<?xml version="1.0" encoding="ISO-8859-1"?> <configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.exoplatform.org/xml/ns/kernel_1_3.xsd http://www.exoplatform.org/xml/ns/kernel_1_3.xsd" xmlns="http://www.exoplatform.org/xml/ns/kernel_1_3.xsd"> <external-component-plugins> <!-- The full qualified name of the RepositoryServiceConfiguration --> <target-component>org.exoplatform.services.jcr.config.RepositoryServiceConfiguration</target-component> <component-plugin> <!-- The name of the plugin --> <name>Sample RepositoryServiceConfiguration Plugin</name> <!-- The name of the method to call on the RepositoryServiceConfiguration in order to add the RepositoryServiceConfigurations --> <set-method>addConfig</set-method> <!-- The full qualified name of the RepositoryServiceConfigurationPlugin --> <type>org.exoplatform.services.jcr.impl.config.RepositoryServiceConfigurationPlugin</type> <init-params> <value-param> <name>conf-path</name> <description>JCR configuration file</description> <value>war:/conf/sample-ext/jcr/repository-configuration.xml</value> </value-param> </init-params> </component-plugin> </external-component-plugins> </configuration>
See an example of repository-configuration.xml below:
<repository-service default-repository="repository"> <repositories> <repository name="repository" system-workspace="system" default-workspace="portal-system"> <security-domain>exo-domain</security-domain> <access-control>optional</access-control> <authentication-policy>org.exoplatform.services.jcr.impl.core.access.JAASAuthenticator</authentication-policy> <workspaces> <workspace name="sample-ws"> <container class="org.exoplatform.services.jcr.impl.storage.jdbc.optimisation.CQJDBCWorkspaceDataContainer"> <properties> <property name="source-name" value="jdbcexo${container.name.suffix}" /> <property name="dialect" value="hsqldb" /> <property name="multi-db" value="false" /> <property name="max-buffer-size" value="204800" /> <property name="swap-directory" value="../temp/swap/sample-ws${container.name.suffix}" /> </properties> <value-storages> <value-storage id="sample-ws" class="org.exoplatform.services.jcr.impl.storage.value.fs.TreeFileValueStorage"> <properties> <property name="path" value="../temp/values/sample-ws${container.name.suffix}" /> </properties> <filters> <filter property-type="Binary" /> </filters> </value-storage> </value-storages> </container> <initializer class="org.exoplatform.services.jcr.impl.core.ScratchWorkspaceInitializer"> <properties> <property name="root-nodetype" value="nt:unstructured" /> <property name="root-permissions" value="any read;*:/platform/administrators read;*:/platform/administrators add_node;*:/platform/administrators set_property;*:/platform/administrators remove" /> </properties> </initializer> <cache enabled="true"> <properties> <property name="max-size" value="20000" /> <property name="live-time" value="30000" /> </properties> </cache> <query-handler class="org.exoplatform.services.jcr.impl.core.query.lucene.SearchIndex"> <properties> <property name="index-dir" value="../temp/jcrlucenedb/sample-ws${container.name.suffix}" /> </properties> </query-handler> <lock-manager class="org.exoplatform.services.jcr.impl.core.lock.infinispan.ISPNCacheableLockManagerImpl"> <properties> <property name="time-out" value="15m" /> <property name="infinispan-configuration" value="conf/standalone/cluster/test-infinispan-lock.xml" /> <property name="jgroups-configuration" value="udp-mux.xml" /> <property name="infinispan-cluster-name" value="JCR-cluster" /> <property name="infinispan-cl-cache.jdbc.table.name" value="lk" /> <property name="infinispan-cl-cache.jdbc.table.create" value="true" /> <property name="infinispan-cl-cache.jdbc.table.drop" value="false" /> <property name="infinispan-cl-cache.jdbc.id.column" value="id" /> <property name="infinispan-cl-cache.jdbc.data.column" value="data" /> <property name="infinispan-cl-cache.jdbc.timestamp.column" value="timestamp" /> <property name="infinispan-cl-cache.jdbc.datasource" value="jdbcjcr" /> <property name="infinispan-cl-cache.jdbc.dialect" value="${dialect}" /> <property name="infinispan-cl-cache.jdbc.connectionFactory" value="org.exoplatform.services.jcr.infinispan.ManagedConnectionFactory" /> </properties> </lock-manager> </workspace> </workspaces> </repository> </repositories> </repository-service>
If you have to change the default repository or the default workspace, please be careful since it could cause side effects as you could be incompatible with some portal configuration files that refer explicitly to portal-system and/or to repository. To solve this problem you can either redefine the configuration file in a portal extension to change the workspace name and/or the repository name or you could also add your own repository instead of your own workspace.
Now you can add new Resource Bundles, thanks to an external plugin.
See an example below:
<?xml version="1.0" encoding="ISO-8859-1"?> <configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.exoplatform.org/xml/ns/kernel_1_3.xsd http://www.exoplatform.org/xml/ns/kernel_1_3.xsd" xmlns="http://www.exoplatform.org/xml/ns/kernel_1_3.xsd"> <external-component-plugins> <!-- The full qualified name of the ResourceBundleService --> <target-component>org.exoplatform.services.resources.ResourceBundleService</target-component> <component-plugin> <!-- The name of the plugin --> <name>Sample ResourceBundle Plugin</name> <!-- The name of the method to call on the ResourceBundleService in order to register the ResourceBundles --> <set-method>addResourceBundle</set-method> <!-- The full qualified name of the BaseResourceBundlePlugin --> <type>org.exoplatform.services.resources.impl.BaseResourceBundlePlugin</type> <init-params> <!--values-param> <name>classpath.resources</name> <description>The resources that start with the following package name should be load from file system</description> <value>locale.portlet</value> </values-param--> <values-param> <name>init.resources</name> <description>Store the following resources into the db for the first launch </description> <value>locale.portal.sample</value> </values-param> <values-param> <name>portal.resource.names</name> <description>The properties files of the portal , those file will be merged into one ResoruceBundle properties </description> <value>locale.portal.sample</value> </values-param> </init-params> </component-plugin> </external-component-plugins> </configuration>
Now each portal container has its own ClassLoader which is automatically set for you at runtime (FYI: it could be retrieved thanks to portalContainer.getPortalClassLoader()). This ClassLoader is an unified ClassLoader that is also aware of the dependency order defined into the PortalContainerDefinition, so to add new keys or update key values, you just need to:
Add the corresponding resource bundle with the same name, with the same extension (xml or properties) at the same location into the classpath of your Web application (i.e. directly into the WEB-INF/classes directory or into a jar file in the WEB-INF/lib directory)
Ensure that your web application is defined after the web application of the portal in the dependency list of the related PortalContainerDefinition.
In the example below, we want to change the values of the keys UIHomePagePortlet.Label.Username and UIHomePagePortlet.Label.Password, and add the new key UIHomePagePortlet.Label.SampleKey into the Resource Bundle locale.portal.webui.
############################################################################# #org.exoplatform.portal.webui.component.UIHomePagePortlet # ############################################################################# UIHomePagePortlet.Label.Username=Usr: UIHomePagePortlet.Label.Password=Pwd: UIHomePagePortlet.Label.SampleKey=This is a new key that has been added to the Resource Bundle "locale.portal.webui" of "sample-ext"
Now each portal container has its own ServletContext which is automatically set for you at runtime (FYI: it could be retrieved thanks to portalContainer.getPortalContext()). This ServletContext is an unified ServletContext that is also aware of the dependency order defined into the PortalContainerDefinition so to replace a groovy template of the portal, you just need to:
Add the corresponding groovy template with the same name at the same location into your Web application
Ensure that your web application is defined after the web application of the portal in the dependency list of the related PortalContainerDefinition.
Now you can add new Portal Configurations, Navigations, Pages or Portlet Preferences thanks to an external plugin.
See an example below:
<?xml version="1.0" encoding="ISO-8859-1"?> <configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.exoplatform.org/xml/ns/kernel_1_3.xsd http://www.exoplatform.org/xml/ns/kernel_1_3.xsd" xmlns="http://www.exoplatform.org/xml/ns/kernel_1_3.xsd"> <external-component-plugins> <!-- The full qualified name of the UserPortalConfigService --> <target-component>org.exoplatform.portal.config.UserPortalConfigService</target-component> <component-plugin> <!-- The name of the plugin --> <name>new.portal.config.user.listener</name> <!-- The name of the method to call on the UserPortalConfigService in order to register the NewPortalConfigs --> <set-method>initListener</set-method> <!-- The full qualified name of the NewPortalConfigListener --> <type>org.exoplatform.portal.config.NewPortalConfigListener</type> <description>this listener init the portal configuration</description> <init-params> <object-param> <name>portal.configuration</name> <description>description</description> <object type="org.exoplatform.portal.config.NewPortalConfig"> <field name="predefinedOwner"> <collection type="java.util.HashSet"> <value> <string>classic</string> </value> </collection> </field> <field name="ownerType"> <string>portal</string> </field> <field name="templateLocation"> <string>war:/conf/sample-ext/portal</string> </field> </object> </object-param> <object-param> <name>group.configuration</name> <description>description</description> <object type="org.exoplatform.portal.config.NewPortalConfig"> <field name="predefinedOwner"> <collection type="java.util.HashSet"> <value> <string>platform/users</string> </value> </collection> </field> <field name="ownerType"> <string>group</string> </field> <field name="templateLocation"> <string>war:/conf/sample-ext/portal</string> </field> </object> </object-param> <object-param> <name>user.configuration</name> <description>description</description> <object type="org.exoplatform.portal.config.NewPortalConfig"> <field name="predefinedOwner"> <collection type="java.util.HashSet"> <value> <string>root</string> </value> </collection> </field> <field name="ownerType"> <string>user</string> </field> <field name="templateLocation"> <string>war:/conf/sample-ext/portal</string> </field> </object> </object-param> </init-params> </component-plugin> </external-component-plugins> </configuration>
We added a GenericFilter that allows you to define new Http Filters thanks to an external plugin. Your filter will need to implement the interface org.exoplatform.web.filter.Filter.
See an example of configuration below:
<?xml version="1.0" encoding="UTF-8"?> <configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.exoplatform.org/xml/ns/kernel_1_3.xsd http://www.exoplatform.org/xml/ns/kernel_1_3.xsd" xmlns="http://www.exoplatform.org/xml/ns/kernel_1_3.xsd"> <external-component-plugins> <!-- The full qualified name of the ExtensibleFilter --> <target-component>org.exoplatform.web.filter.ExtensibleFilter</target-component> <component-plugin> <!-- The name of the plugin --> <name>Sample Filter Definition Plugin</name> <!-- The name of the method to call on the ExtensibleFilter in order to register the FilterDefinitions --> <set-method>addFilterDefinitions</set-method> <!-- The full qualified name of the FilterDefinitionPlugin --> <type>org.exoplatform.web.filter.FilterDefinitionPlugin</type> <init-params> <object-param> <name>Sample Filter Definition</name> <object type="org.exoplatform.web.filter.FilterDefinition"> <!-- The filter instance --> <field name="filter"><object type="org.exoplatform.sample.ext.web.SampleFilter"/></field> <!-- The mapping to use --> <!-- WARNING: the mapping is expressed with regular expressions --> <field name="patterns"> <collection type="java.util.ArrayList" item-type="java.lang.String"> <value> <string>/.*</string> </value> </collection> </field> </object> </object-param> </init-params> </component-plugin> </external-component-plugins> </configuration>
See an example of Filter below:
... import org.exoplatform.web.filter.Filter; import java.io.IOException; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; public class SampleFilter implements Filter { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println("SampleFilter start"); try { chain.doFilter(request, response); } finally { System.out.println("SampleFilter end"); } } }
We added a GenericHttpListener that allows you to define new HttpSessionListeners and/or ServletContextListeners thanks to an external plugin. Actually, the GenericHttpListener will broadcast events thanks to the ListenerService that you can easily capture. The events that it broadcasts are:
org.exoplatform.web.GenericHttpListener.sessionCreated: When a new session is created in the portal.
org.exoplatform.web.GenericHttpListener.sessionDestroyed: When a session is destroyed in the portal.
org.exoplatform.web.GenericHttpListener.contextInitialized: When the servlet context of the portal is initialized.
org.exoplatform.web.GenericHttpListener.contextDestroyed: When the servlet context of the portal is destroyed.
If you want to listen to org.exoplatform.web.GenericHttpListener.sessionCreated, you will need to create a Listener that extends _Listener<PortalContainer, HttpSessionEvent>_If you want to listen to \_org.exoplatform.web.GenericHttpListener.sessionDestroyed_, you will need to create a Listener that extends _Listener<PortalContainer, HttpSessionEvent>_If you want to listen to \_org.exoplatform.web.GenericHttpListener.contextInitialized_, you will need to create a Listener that extends _Listener<PortalContainer, ServletContextEvent>_If you want to listen to \_org.exoplatform.web.GenericHttpListener.contextDestroyed_, you will need to create a Listener that extends Listener<PortalContainer, ServletContextEvent>
See an example of configuration below:
<?xml version="1.0" encoding="UTF-8"?> <configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.exoplatform.org/xml/ns/kernel_1_3.xsd http://www.exoplatform.org/xml/ns/kernel_1_3.xsd" xmlns="http://www.exoplatform.org/xml/ns/kernel_1_3.xsd"> <external-component-plugins> <!-- The full qualified name of the ListenerService --> <target-component>org.exoplatform.services.listener.ListenerService</target-component> <component-plugin> <!-- The name of the listener that is also the name of the target event --> <name>org.exoplatform.web.GenericHttpListener.sessionCreated</name> <!-- The name of the method to call on the ListenerService in order to register the Listener --> <set-method>addListener</set-method> <!-- The full qualified name of the Listener --> <type>org.exoplatform.sample.ext.web.SampleHttpSessionCreatedListener</type> </component-plugin> <component-plugin> <!-- The name of the listener that is also the name of the target event --> <name>org.exoplatform.web.GenericHttpListener.sessionDestroyed</name> <!-- The name of the method to call on the ListenerService in order to register the Listener --> <set-method>addListener</set-method> <!-- The full qualified name of the Listener --> <type>org.exoplatform.sample.ext.web.SampleHttpSessionDestroyedListener</type> </component-plugin> <component-plugin> <!-- The name of the listener that is also the name of the target event --> <name>org.exoplatform.web.GenericHttpListener.contextInitialized</name> <!-- The name of the method to call on the ListenerService in order to register the Listener --> <set-method>addListener</set-method> <!-- The full qualified name of the Listener --> <type>org.exoplatform.sample.ext.web.SampleContextInitializedListener</type> </component-plugin> <component-plugin> <!-- The name of the listener that is also the name of the target event --> <name>org.exoplatform.web.GenericHttpListener.contextDestroyed</name> <!-- The name of the method to call on the ListenerService in order to register the Listener --> <set-method>addListener</set-method> <!-- The full qualified name of the Listener --> <type>org.exoplatform.sample.ext.web.SampleContextDestroyedListener</type> </component-plugin> </external-component-plugins> </configuration>
See an example of Session Listener below:
.. import org.exoplatform.container.PortalContainer; import org.exoplatform.services.listener.Event; import org.exoplatform.services.listener.Listener; import javax.servlet.http.HttpSessionEvent; public class SampleHttpSessionCreatedListener extends Listener<PortalContainer, HttpSessionEvent> { @Override public void onEvent(Event<PortalContainer, HttpSessionEvent> event) throws Exception { System.out.println("Creating a new session"); } }
See an example of Context Listener below:
.. import org.exoplatform.container.PortalContainer; import org.exoplatform.services.listener.Event; import org.exoplatform.services.listener.Listener; import javax.servlet.ServletContextEvent; public class SampleContextInitializedListener extends Listener<PortalContainer, ServletContextEvent> { @Override public void onEvent(Event<PortalContainer, ServletContextEvent> event) throws Exception { System.out.println("Initializing the context"); } }
Actually, it is not possible but you can create a rest component instead. For more details about rest, please refer to the following article WS.
Actually, you have nothing to do, you just need to ensure that you get the context parameter value from the unified servlet context of the portal, that should be set for you but you can still get it from portalContainer.getPortalContext().
We added an example of portal extension (i.e. ability to customize a portal without changing anything in the portal.ear) that you can find in the git repository of gatein at https://github.com/gatein/gatein-portal/tree/master/examples.
We assume that you have a clean JBoss version of GateIn, in other words, we assume that you have already the file exoplatform.ear in the deploy directory of JBoss and you have the related application policy in your conf/login-config.xml.
You need to:
Add the file sample-ext.ear from sample/extension/ear/target/ to the deploy directory of JBoss, this file contains:
The file sample-ext.war which is the web application that contains (potentially) configuration files, groovy templates, resource bundles, skins and javascript files, that will extend the portal.
The file exo.portal.sample.extension.config-X.Y.Z.jar which is the file in which we defined the PortalContainerDefinition of the original portal in which we added sample-ext at the end of dependency list.
The file exo.portal.sample.extension.jar-X.Y.Z.jar which is the file in which we have internal classes that are actualy a set of sample classes (SampleFilter, SampleContextInitializedListener, SampleContextDestroyedListener, SampleHttpSessionCreatedListener and SampleHttpSessionDestroyedListener)
Add the file starter.ear from starter/ear/target/ to the deploy directory of JBoss, this file contains:
The file starter.war which is the web application that will create and start all the portal containers, that is why it must be launched after all the other web applications.
This can only work if a Unified ClassLoader has been configured on your JBoss (default behavior) and the load order is first the exoplatform.ear then the sample-ext.ear and finally the starter.ear.
The file starter.ear must always been started last.
We assume that you have a clean Tomcat version of GateIn, in other words, we assume that you have already all the jar files of GateIn and their dependencies into tomcat/lib, you have all the war files of GateIn into tomcat/webapps and you have the realm name "exo-domain" defined into the file tomcat/conf/jaas.conf.
Add the file sample-ext.war from sample/extension/war/target/ to the tomcat/webapps directory. (See the purpose of this file in the JBoss section).
Add the folder starter from starter/war/target/ to the tomcat/webapps directory. (See the purpose of this file in the JBoss section).
Rename the directory (unzipped folder) starter to starter.war (for more details see the warning below)
Add the jar file exo.portal.sample.extension.config-X.Y.Z.jar from sample/extension/config/target/ to the tomcat/lib directory. (See the purpose of this file in the JBoss section).
Add the jar file exo.portal.sample.extension.jar-X.Y.Z.jar from sample/extension/jar/target/ to the tomcat/lib directory. (See the purpose of this file in the JBoss section).
This can only work if the starter.war is the last war file to be loaded, so don't hesitate to rename it if your war files are loaded following to the alphabetic order.
We added an example of new portal (i.e. ability to create a new portal from another portal without changing anything in the portal.ear) that you can find in the git repository of gatein at https://github.com/gatein/gatein-portal/tree/master/examples.
We assume that you have a clean JBoss version of GateIn, in other words, we assume that you have already the file exoplatform.ear in the deploy directory of JBoss and you have the related application policy in your conf/login-config.xml.
You need to:
Add the file sample-portal.ear from sample/portal/ear/target/ to the deploy directory of JBoss, this file contains:
The file sample-portal.war which is the entry point of the new portal
The file rest-sample-portal.war which is the entry point for rest outside the portal (in the portal you can access to rest thanks to path prefix /sample-portal/rest)
The file exo.portal.sample.portal.config-X.Y.Z.jar which is the file in which we defined the PortalContainerDefinition of this portal
The file exo.portal.sample.portal.jar-X.Y.Z.jar which is the file in which we have internal classes that are actualy a set of sample classes (SampleFilter, SampleContextInitializedListener, SampleContextDestroyedListener, SampleHttpSessionCreatedListener and SampleHttpSessionDestroyedListener)
Add the file starter.ear from starter/ear/target/ to the deploy directory of JBoss, this file contains:
The file starter.war which is the web application that will create and start all the portal containers, that is why it must be launched after all the other web applications.
Define the related application policy in your file conf/login-config.xml, as below:
<application-policy name="exo-domain-sample-portal"> <authentication> <login-module code="org.exoplatform.web.security.PortalLoginModule" flag="required"> <module-option name="portalContainerName">sample-portal</module-option> <module-option name="realmName">exo-domain-sample-portal</module-option> </login-module> <login-module code="org.exoplatform.services.security.jaas.SharedStateLoginModule" flag="required"> <module-option name="portalContainerName">sample-portal</module-option> <module-option name="realmName">exo-domain-sample-portal</module-option> </login-module> <login-module code="org.exoplatform.services.security.j2ee.JbossLoginModule" flag="required"> <module-option name="portalContainerName">sample-portal</module-option> <module-option name="realmName">exo-domain-sample-portal</module-option> </login-module> </authentication> </application-policy>
This can only work if a Unified ClassLoader has been configured on your JBoss (default behavior) and the load order is first the exoplatform.ear then the sample-portal.ear and finally the starter.ear.
The file starter.ear must always been started last.
We assume that you have a clean Tomcat version of GateIn, in other words, we assume that you have already all the jar files of GateIn and their dependencies into tomcat/lib, you have all the war files of GateIn into tomcat/webapps and you have the realm name "exo-domain" defined into the file tomcat/conf/jaas.conf.
Add the file sample-portal.war from sample/portal/war/target/ to the tomcat/webapps directory. (See the purpose of this file in the JBoss section).
Add the file rest-sample-portal.war from sample/portal/rest-war/target/ to the tomcat/webapps directory. (See the purpose of this file in the JBoss section).
Add the folder starter from starter/war/target/ to the tomcat/webapps directory. (See the purpose of this file in the JBoss section).
Rename the directory (unzipped folder) starter to starter.war (for more details see the warning below).
Add the jar file exo.portal.sample.portal.config-X.Y.Z.jar from sample/portal/config/target/ to the tomcat/lib directory. (See the purpose of this file in the JBoss section).
Add the jar file exo.portal.sample.portal.jar-X.Y.Z.jar from sample/portal/jar/target/ to the tomcat/lib directory. (See the purpose of this file in the JBoss section).
Define the related realm in your file tomcat/conf/jaas.conf, as below:
... exo-domain-sample-portal { org.exoplatform.web.security.PortalLoginModule required portalContainerName="sample-portal" realmName="exo-domain-sample-portal"; org.exoplatform.services.security.jaas.SharedStateLoginModule required portalContainerName="sample-portal" realmName="exo-domain-sample-portal"; org.exoplatform.services.security.j2ee.TomcatLoginModule required portalContainerName="sample-portal" realmName="exo-domain-sample-portal"; };
Define the context of sample-portal by creating a file called sample-portal.xml into tomcat/conf/Catalina/localhost/ with the following content:
<Context path='/sample-portal' docBase='sample-portal' debug='0' reloadable='true' crossContext='true' privileged='true'> <Logger className='org.apache.catalina.logger.SystemOutLogger' prefix='localhost_portal_log.' suffix='.txt' timestamp='true'/> <Manager className='org.apache.catalina.session.PersistentManager' saveOnRestart='false'/> <Realm className='org.apache.catalina.realm.JAASRealm' appName='exo-domain-sample-portal' userClassNames='org.exoplatform.services.security.jaas.UserPrincipal' roleClassNames='org.exoplatform.services.security.jaas.RolePrincipal' debug='0' cache='false'/> <Valve className='org.apache.catalina.authenticator.FormAuthenticator' characterEncoding='UTF-8'/></Context>
Define the context of rest-sample-portal by creating a file called rest-sample-portal.xml into tomcat/conf/Catalina/localhost/ with the following content:
<Context path="/rest-sample-portal" docBase="rest-sample-portal" reloadable="true" crossContext="false"> <Logger className='org.apache.catalina.logger.SystemOutLogger' prefix='localhost_portal_log.' suffix='.txt' timestamp='true'/> <Manager className='org.apache.catalina.session.PersistentManager' saveOnRestart='false'/> <Realm className='org.apache.catalina.realm.JAASRealm' appName='exo-domain-sample-portal' userClassNames="org.exoplatform.services.security.jaas.UserPrincipal" roleClassNames="org.exoplatform.services.security.jaas.RolePrincipal" debug='0' cache='false'/> </Context>
This can only work if the starter.war is the last war file to be loaded, so don't hesitate to rename it if your war files are loaded following to the alphabetic order.
To fix this issue you need to check if:
The file starter-gatein.ear (which will be starter.war for Tomcat) has been deployed
The file starter-gatein.ear (which will be starter.war for Tomcat) is the last ear file to be launched
With Tomcat to prevent any alphabetic issue, the good way to solve this problem is to:
Unzip the archive starter.war into a directory called starter
Remove the archive starter.war
Rename the folder starter to starter.war
This tip works because folders corresponding to unzipped wars are launched after war files.
Remove all the configuration files from the jar files ( conf/configuration.xml and conf/portal/configuration.xml) and move them to the war file of your extension, otherwise your configuration files will be loaded for all the portal containers which could cause incompatibility issues with other portals.
Each extension should manage independently, its css files, js files, google gadgets and configuration files. If you add configuration files into the jar files of your extension, you brake this law.
This section will show you how to use AS Managed DataSource under JBoss AS.
Checked under Gatein 3.1.0-GA Final
only no-tx-datasource is supported in JCR 1.12
Under JBoss, just put a file XXX-ds.xml in the deploy server (example: \server\default\deploy). In this file, we will configure all datasources which eXo will need. (there should be 4 named: jdbcjcr_portal, jdbcjcr_portal-sample, jdbcidm_portal & jdbcidm_sample-portal).
Example:
<?xml version="1.0" encoding="UTF-8"?> <datasources> <no-tx-datasource> <jndi-name>jdbcjcr_portal</jndi-name> <connection-url>jdbc:hsqldb:${jboss.server.data.dir}/data/jdbcjcr_portal</connection-url> <driver-class>org.hsqldb.jdbcDriver</driver-class> <user-name>sa</user-name> <password></password> </no-tx-datasource> <no-tx-datasource> <jndi-name>jdbcjcr_sample-portal</jndi-name> <connection-url>jdbc:hsqldb:${jboss.server.data.dir}/data/jdbcjcr_sample-portal</connection-url> <driver-class>org.hsqldb.jdbcDriver</driver-class> <user-name>sa</user-name> <password></password> </no-tx-datasource> <no-tx-datasource> <jndi-name>jdbcidm_portal</jndi-name> <connection-url>jdbc:hsqldb:${jboss.server.data.dir}/data/jdbcidm_portal</connection-url> <driver-class>org.hsqldb.jdbcDriver</driver-class> <user-name>sa</user-name> <password></password> </no-tx-datasource> <no-tx-datasource> <jndi-name>jdbcidm_sample-portal</jndi-name> <connection-url>jdbc:hsqldb:${jboss.server.data.dir}/data/jdbcidm_sample-portal</connection-url> <driver-class>org.hsqldb.jdbcDriver</driver-class> <user-name>sa</user-name> <password></password> </no-tx-datasource> </datasources>
Which properties can be set for datasource can be found here: Configuring JDBC DataSources - The non transactional DataSource configuration schema
Edit server/default/conf/gatein/configuration.properties and comment out next rows in JCR section:
#gatein.jcr.datasource.driver=org.hsqldb.jdbcDriver #gatein.jcr.datasource.url=jdbc:hsqldb:file:${gatein.db.data.dir}/data/jdbcjcr_${name} #gatein.jcr.datasource.username=sa #gatein.jcr.datasource.password=
and in IDM section:
#gatein.idm.datasource.driver=org.hsqldb.jdbcDriver #gatein.idm.datasource.url=jdbc:hsqldb:file:${gatein.db.data.dir}/data/jdbcidm_${name} #gatein.idm.datasource.username=sa #gatein.idm.datasource.password=
In jcr-configuration.xml and idm-configuration.xml comment out the plugin InitialContextInitializer.
<!-- Commented because, Datasources are declared and bound by AS, not in eXo --> <!-- <external-component-plugins> [...] </external-component-plugins> -->
Running eXo after these configurations goes well.