Introduction
eXo Platform 3 includes an extension mechanism that allows you to customize your portal without modifying the source code. This ensures compatibility with future upgrades and simplifies support. The extension mechanism will also help you to reuse eXo components as a foundation for other projects in the future, rather than starting from scratch.
Pre-requisites
Understanding the Concept
Vocabulary
The eXo kernel relies on dependency injection. This means that it has a dependency provider - the Container - which handles the lifecycle of a service (for example, instantiating, opening and closing streams), instead of the consumer. The consumer only needs a reference to an implementation of the requested service which is configured in an .xml configuration file that comes with every service. eXo has two types of Containers: a RootContainer, instantiated once per Application Server, and PortalContainer, which can exist in multiple instances.
PortalContainer: The PortalContainer inherits services from the RootContainer. When you launch eXo Platform 3, the default url is http://localhost:8080/portal/public/default/. The name of the PortalContainer corresponds to the "portal" string. You can have multiple PortalContainers running on your application server. For example you can have http://www.mydomain.com/myportal/ or http://www.mydomain.com/anotherportal/. "myportal" and "anotherportal" are two different PortalContainers.
Services: A service is a software component that lives inside a container. It can perform a wide variety of tasks, such as reading and transforming a document, providing a REST API, managing interaction with a remote system, and many others. When starting eXo Platform, the Containers create all the necessary services. Each service must be implemented as a singleton, meaning only one instance is created for each Container.
Portal Site: Each PortalContainer can have multiple Portal Sites. In http://localhost:8080/portal/public/default/, the site corresponds to the "default" part of the url. When you first start the Platform, the "portal" PortalContainer has 3 demo sites (default, acme, intranet). All the Portal Sites in one PortalContainer share the services that have been registered to that particular PortalContainer (eg: applications, users and groups, etc).
RootContainer Instantiation
The RootContainer instantiates all the PortalContainers that are registered in its configuration. In practice, you will rarely need to interact with the RootContainer, except for cases where you want to have a component shared between all portal containers.
To register components in the RootContainer, a configuration.xml file has to be placed in the conf/ folder of the classpath. In practice, we add a .jar to the lib folder.
If you have more than one conf/configuration.xml file (for example, many .jar files, where each of them contains a conf/configuration.xml file), the configuration files will be loaded one after the other. If the same service is configured in two different files, the last file loaded will override the previous one. Since we have no way of controlling the loading order, we cannot rely on overriding for configuration of the RootContainer.
However, after loading the configuration from the classpath, it will load an external configuration from /tomcat/gatein/conf/configuration.xml. You will know for sure that this file will not be over-ridden.
Registering an extension
The default portal (named "portal") configuration is located in exo.platform.extension.config.jar/conf/platform/configuration.xml. This is were the "portal" PortalContainer is registered to the RootContainer. Here is what this file looks like:
1.Default PortalContainer definition
<configuration xmlns="http://www.exoplaform.org/xml/ns/kernel_1_1.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemalocation="http://www.exoplaform.org/xml/ns/kernel_1_1.xsd http://www.exoplaform.org/xml/ns/kernel_1_1.xsd">
<!-- This declares the default portal container for Platform -->
<component>
<type>org.exoplatform.container.definition.PortalContainerConfig</type>
<init-params>
<value-param>
<name>default.portal.container</name>
<value>portal</value>
</value-param>
<value-param>
<name>default.rest.context</name>
<value>rest</value>
</value-param>
<!-- The name of the default securty realm -->
<value-param>
<name>default.realm.name</name>
<value>gatein-domain</value> <!-- TODO : use a variable -->
</value-param>
<object-param>
<name>default.portal.definition</name>
<object type="org.exoplatform.container.definition.PortalContainerDefinition">
<!-- The path to the external properties file -->
<field name="externalSettingsPath">
<string>configuration.properties</string>
</field>
<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>
....
<!-- Contains shared layout -->
<value>
<string>platform-extension</string>
</value>
</collection>
</field>
<!-- A map of settings tied to the default portal container -->
<field name="settings"><map type="java.util.HashMap">
<!-- TODO check cometd name -->
<entry>
<key>
<string>sample</string>
</key>
<value>
<string>value</string>
</value>
</entry></map>
</field>
</object>
</object-param>
</init-params>
</component>
</configuration>
Refer to the documentation for a description of each field.You will notice that we declared a list of dependencies; these correspond to webapps, war files which we deployed in the server.When creating a new PortalContainer, the RootContainer looks for all the configuration files.
-
First, the RootContainer loads the files in the classpath located at conf/portal/configuration.xml. Once again, we don't know the loading order, so you should not rely on overriding.
-
Next, it loads the configuration from the dependencies (WEBINF/conf/configuration.xml). starting with first one listed and continuing in order.
-
Finally, the RootContainer loads the external configuration from /tomcat/gatein/conf/portal/ /configuration.xml
Once again, if a service is configured twice, the newest configuration will override the previous one. When using the Extension Mechanism, you should always use webapp configuration.
Plugins
The last concept to understand before we we start working is the idea of external-plugins. Registered components provide a way to inject configuration via plugins. This is achieved by the external-component-plugins tag, which allows you to configure a service you have instantiated in another file, without having to completely override the existing configuration. For example, the default PortalContainer was registered to the RootContainer in 1.1, but you can add a dependency using the following plugin:
2.PortalContainer plugin
<!--?xml version="1.0" encoding="UTF-8"?--><!--?xml version="1.0" encoding="UTF-8"?--> <configuration xmlns="http://www.exoplaform.org/xml/ns/kernel_1_0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemalocation="http://www.exoplaform.org/xml/ns/kernel_1_0.xsd http://www.exoplaform.org/xml/ns/kernel_1_0.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>Change PortalContainer Definitions</name>
<!-- The name of the method to call on the PortalContainerConfig in order to register the changes on the PortalContainerDefinitions -->
<set-method>registerChangePlugin</set-method>
<!-- The full qualified name of the PortalContainerDefinitionChangePlugin -->
<type>org.exoplatform.container.definition.PortalContainerDefinitionChangePlugin</type>
<init-params>
<value-param>
<name>apply.default</name>
<value>true</value>
</value-param>
<object-param>
<name>change</name>
<object type="org.exoplatform.container.definition.PortalContainerDefinitionChange$AddDependencies">
<!-- The list of name of the dependencies to add -->
<field name="dependencies">
<collection type="java.util.ArrayList">
<value>
<string>my-extension</string>
</value>
</collection>
</field>
</object>
</object-param>
</init-params>
</component-plugin>
</external-component-plugins>
</configuration>
<configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemalocation="http://www.exoplaform.org/xml/ns/kernel_1_0.xsd http://www.exoplaform.org/xml/ns/kernel_1_0.xsd">
</configuration>
The result is the same as declaring the "my-extension" dependency initially, but this allows you to reuse older components without rewriting them. The comprehensive list of plugins is documented in the reference guide.
Part 2: The Extension Mechanism in Action
Now that we have an understanding of the core concepts, we will use the extension mechanism to edit the Acme website demo app.
Create the project
To create your extension, you will need to do two things:
-
Add a .jar to the lib folder with conf/configuration.xml to add your webapp as a dependency as described in 1.2.
-
Create your webapp with your own configuration to override the existing one.
To make development easier, you can use a Maven-artifact from the eXo repository which which will create your maven project for you. Type the following command:
mvn archetype:generate -DarchetypeCatalog=http://repository.exoplatform.org/public/
-DarchetypeGroupId=org.exoplatform.extension-tutorial
-DarchetypeArtifactId=extension-tutorial-archetype
-DarchetypeVersion=1.0
Choose the first archetype. Enter a groupId, for example "org.exoplatform.extension-tutorial", and artifactId, such as "extension-tutorial". For version and package, simply press enter. This will create your folder structure and configuration files as shown below.
In config/src/main/resources/conf/, you will find the configuration.xml file with the external plugin to register your webapp. As you can see, we registered the "my-extension" webapp as a dependency.
In war/src/main/webapp/WEB-INF/conf/, you will find the configuration for your PortalContainer.
Add your configuration
In the template we created, try to modify the sharedlayout.xml file to override the existing sharedlayout.xml. The file is located in the war/src/main/webapp/WEB-INF/conportal/portal/ folder. For example, exchange the SpacesToolbarPortlet with the UserToolbarSitePortlet to get the following:
You can modify the default order of the container elements to change the menu bar layout. For example:
<!--?xml version="1.0" encoding="ISO-8859-1"?--> <container template="system:/groovy/portal/webui/container/UIContainer.gtmpl">
<container template="system:/groovy/portal/webui/container/UIToolbarContainer.gtmpl">
<access-permissions>*:/platform/users</access-permissions>
<container id="SpacesToolbarPortlet" template="system:/groovy/portal/webui/container/UIContainer.gtmpl">
<portlet-application>
<portlet>
<application-ref>platformNavigation</application-ref>
<portlet-ref>UIMySpacePlatformToolBarPortlet</portlet-ref>
</portlet>
<access-permissions>Everyone</access-permissions>
<show-info-bar>false</show-info-bar>
</portlet-application>
</container>
<container id="UserToolBarDashboardPortlet" template="system:/groovy/portal/webui/container/UIContainer.gtmpl">
<portlet-application>
<portlet>
<application-ref>exoadmin</application-ref>
<portlet-ref>UserToolbarDashboardPortlet</portlet-ref>
</portlet>
<access-permissions>*:/platform/users</access-permissions>
<show-info-bar>false</show-info-bar>
</portlet-application>
</container>
<container id="AdminToolBarPortlet" template="system:/groovy/portal/webui/container/UIContainer.gtmpl">
<portlet-application>
<portlet>
<application-ref>presentation</application-ref>
<portlet-ref>WCMAdminToolbarPortlet</portlet-ref>
</portlet>
<access-permissions>*:/platform/administrators; editor:/platform/web-contributors</access-permissions>
<show-info-bar>false</show-info-bar>
</portlet-application>
</container>
<container id="EditingPortlet" template="system:/groovy/portal/webui/container/UIContainer.gtmpl">
<portlet-application>
<portlet>
<application-ref>presentation</application-ref>
<portlet-ref>EditingPortlet</portlet-ref>
</portlet>
<access-permissions>*:/platform/administrators; editor:/platform/web-contributors</access-permissions>
<show-info-bar>false</show-info-bar>
</portlet-application>
</container>
<container id="UserInfoPortlet" template="system:/groovy/portal/webui/container/UIContainer.gtmpl">
<portlet-application>
<portlet>
<application-ref>platformNavigation</application-ref>
<portlet-ref>UIUserPlatformToolBarPortlet</portlet-ref>
</portlet>
<access-permissions>*:/platform/users</access-permissions>
<show-info-bar>false</show-info-bar>
</portlet-application>
</container>
<container id="UserToolBarSitePortlet" template="system:/groovy/portal/webui/container/UIContainer.gtmpl">
<portlet-application>
<portlet>
<application-ref>platformNavigation</application-ref>
<portlet-ref>UIUserPlatformToolBarSitePortlet</portlet-ref>
</portlet>
<access-permissions>*:/platform/users</access-permissions>
<show-info-bar>false</show-info-bar>
</portlet-application>
</container>
</container>
<site-body>
</site-body></container>
It is up to you to over-ride other configuration points. Looking at the existing webapps can help you understand the different configuration files. It is now time to build our project and add it to the Platform.
Build your project
Open a terminal window and browse to the location of your project. If you have followed the instruction above it should be "extension-tutorial". Type the following command:
mvn clean install
After building successfully you will find a .jar file in the config/target folder. Drop this jar in eXoPlatform3.x/tomcat/lib/.You will also find a war file in the war/target/ folder. Drop this file in the eXoPlatform-3.x/tomcat/webapps/ folder.Go to the bin folder and type the following command to launch the platform.
./gatein.sh run
You will see that the menu bar has changed according to your edits.
Conclusion
The extension mechanism is an advanced feature that allows eXo Platform developers to build reusable components and ensures compatibility with the platform’s default configuration. Learning how to use this feature can make your project development more productive, and result in better apps that are easier to use and maintain.