All eXo services are built around the eXo Kernel, or the service management layer, which manages the configuration and the execution of all the components. The main kernel object is the eXo Container, a micro-container that glues services together through dependency injection. The container is responsible for loading services/components.
This chapter will introduce the concepts of Container and Services, and will give you a starting point for the basic configuration of Services.
A container is always required in order to access a service, because the eXo Kernel relies on dependency injection. This means that the life-cycle of a service (instantiating, opening and closing streams, disposing, etc.) is handled by a dependency provider (i.e. the eXo Container) rather than the consumer. The consumer only needs a reference to an implementation of the requested service, which is provided in the configuration.xml file that comes with every service. You can read more about dependency injection here http://en.wikipedia.org/wiki/Dependency_injection.
eXo has two several kinds of container, the RootContainer and the PortalContainer. The RootContainer holds the lower level components. It is automatically started before the PortalContainer. You will rarely interact directly with it except to activate your own extension (more on this further). The PortalContainer is created at the startup of the portal web application (portal.war). All services started by this container will run embedded in the portal. It also gives access to the components of its parent RootContainer
In your code, if you need to invoke a service of a container, you can use the ExoContainerContext helper from any location. The code below shows you a utility method that you can use to invoke any eXo service.
public class ExoUtils {
/**
* Get a service from the portal container
* @param type : component type
* @return the concrete instance retrieved in the container using the type as key
*/
public <T>T getService(Class<T> type) {
return (T)ExoContainerContext.getCurrentContainer().getComponentInstanceOfType(type);
}
}
Then, invoking becomes as easy as :
OrganizationService orgService = ExoUtils.getService(OrganizationService.class)
Containers are used to gain access to services. Important characteristics of services are:
Because of the Dependency Injection concept, the interface and implementation for a service are usually separate.
Each service has to be implemented as a singleton, which means it is created only once per portal container - in a single instance.
A component = a service. A service doesn't have to be a large application that does big things. A service can be a little component that reads or transforms a document, in which case the term component is often used instead of service.
For example, in the lib/ folder, you can find services for databases, caching, ldap :
exo.core.component.database-x.y.z.jar
exo.kernel.component.cache-x.y.z.jar
exo.core.component.organization.ldap-x.y.z.jar
To declare a service, you must add an xml configuration file in a specific your classpath. A configuration file can specify several services, so there can be several services in one jar file.
Containers configuration files must withe the kernel configuraiton grammar. Thus all configuration will contain an XSD declaration like this :
<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"> </configuration>
The kernel11.xsd file mentionned in the example above can be found inside exo.kernel.container-x.y.z.jar!org/exoplatform/container/configuration/ along with other versions.
The registration of a service within the container is done with the <component> element. For example, open the exo-ecms-core-services-x.y.z.jar file; inside this jar open /conf/portal/configuration.xml. You will see:
<component>
<type>org.exoplatform.services.deployment.ContentInitializerService</type>
</component>
<component>
<key>org.exoplatform.services.cms.CmsService</key>
<type>org.exoplatform.services.cms.impl.CmsServiceImpl</type>
</component>
Each component has a key which matches the qualified java interface name (org.exoplatform.services.cms.CmsService). The specific implementation class of the component (CmsServiceImpl) is defined in the <type> tag.
If a service does not have a separate interface, the <type> will be used as the key in the container. This is the case of ContentInitializerService.
You can provide initial parameters for your service by defining them in the configuration file. There are different kind of parameters:
value-param
properties-param
object-param
collection
map
native-array
You can use the value param to pass values to methods inside the service.
<component>
<key>org.exoplatform.portal.config.UserACL</key>
<type>org.exoplatform.portal.config.UserACL</type>
<init-params>
<value-param>
<name>access.control.workspace</name>
<description>groups with memberships that have the right to access the User Control Workspace</description>
<value>*:/platform/administrators,*:/organization/management/executive-board</value>
</value-param>
</init-params>
</component>
The UserACL service accesses to the value-param in its constructor.
package org.exoplatform.portal.config;
public class UserACL {
public UserACL(InitParams params) {
UserACLMetaData md = new UserACLMetaData();
ValueParam accessControlWorkspaceParam = params.getValueParam("access.control.workspace");
if(accessControlWorkspaceParam != null) md.setAccessControlWorkspace(accessControlWorkspaceParam.getValue());
...
In this case, the UserACL service has the same <key> and <type>. This corresponds to the special case of a single implementation service. The developer may decide not to create an interface if there will not be more than one implementation of the service.
For the object-param component, we can look at the LDAP service:
<component>
<key>org.exoplatform.services.ldap.LDAPService</key>
<type>org.exoplatform.services.ldap.impl.LDAPServiceImpl</type>
<init-params>
<object-param>
<name>ldap.config</name>
<description>Default ldap config</description>
<object type="org.exoplatform.services.ldap.impl.LDAPConnectionConfig">
<field name="providerURL"><string>ldaps://10.0.0.3:636</string></field>
<field name="rootdn"><string>CN=Administrator,CN=Users,DC=exoplatform,DC=org</string></field>
<field name="password"><string>exo</string></field>
<field name="version"><string>3</string></field>
<field name="minConnection"><int>5</int></field>
<field name="maxConnection"><int>10</int></field>
<field name="referralMode"><string>ignore</string></field>
<field name="serverName"><string>active.directory</string></field>
</object>
</object-param>
</init-params>
</component>
An object-param is being used to create an object (which is actually a Java Bean) passed as a parameter to the service. This object-param is defined by a name, a description and exactly one object. The object tag defines the type of the object, while the field tags define parameters for that object.
Let's see how the service accesses the object:
package org.exoplatform.services.ldap.impl;
public class LDAPServiceImpl implements LDAPService {
// ...
public LDAPServiceImpl(InitParams params) {
LDAPConnectionConfig config = (LDAPConnectionConfig) params.getObjectParam("ldap.config").getObject();
// ...
The passed object is LDAPConnectionConfig, which is a classic Java Bean. It contains all fields defined in the configuration files and also the appropriate getters and setters (not listed here). You also can provide default values. The container creates a new instance of your bean and calls all setters whose values are configured in the configuration file.
package org.exoplatform.services.ldap.impl;
public class LDAPConnectionConfig {
private String providerURL = "ldap://127.0.0.1:389";
private String rootdn;
private String password;
private String version;
private String authenticationType = "simple";
private String serverName = "default";
private int minConnection;
private int maxConnection;
private String referralMode = "follow";
// ...
Some components may want to offer some extensibility. For this, they use a plugin mechanism based on method injection. To offer an extension point for plugins, a component needs to provide a public method that takes an instance of org.exoplatform.container.xml.ComponentPlugin as parameter.
Plugins allow you to provide structured configuration from outside the original declaration of the component. This is the main way you will use to customize eXo Platform for your needs.
Let's have a look at the configuration of the TaxonomyPlugin of the TaxonomyService:
<external-component-plugins>
<target-component>org.exoplatform.services.cms.taxonomy.TaxonomyService</target-component>
<component-plugin>
<name>predefinedTaxonomyPlugin</name>
<set-method>addTaxonomyPlugin</set-method>
<type>org.exoplatform.services.cms.taxonomy.impl.TaxonomyPlugin</type>
<init-params><!-- ... --></init-params>
</component-plugin>
</external-component-plugins>
The <target-component> defines the components that hosts the extension point.The configuration is injected by the container using a method that is defined in <set-method> (addTaxonomyPlugin(). The method accepts exactly one argument of the type org.exoplatform.services.cms.categories.impl.TaxonomyPlugin.
The content of <init-params> corresponds to the structure of the TaxonomyPlugin object.
The kernel startup follows a well defined sequence to load configuration. The objects are initialized in the container only after the whole loading sequence is done. Hence, by placing your configuration in an upper location of the sequence, you can override a component declaration by your own. You will typically do this when you want to provide your own implementation of a component, or declare custom init-params.
external-component-plugins declarations are additive, so it is NOT possible to override them.
The loading sequence involves loading successively configurations for the RootContainer then from the PortalContainers:
Services default RootContainer configurations from JAR files /conf/configuration.xml
External RootContainer configuration, will be found at $exo.conf.dir/configuration.xml
Services default PortalContainer configurations from JAR files /conf/$PORTAL/configuration.xml
Web applications configurations from WAR files /WEB-INF/conf/configuration.xml
External configuration for services of the portal will be found at $exo.conf.dir/portal/$PORTAL/configuration.xml
$exo.conf.dir is a system property, that points to a path in filesystem. It is passed to the JVM in the startup script like with Dexo.conf.dir=gatein
$PORTAL is the name of the portal container. By default, there is only one and it is called 'portal'
Gatein extensions are special war files that are recognized by eXo Platform and contribute to custom configurations to the PortalContainer. In order to create your own portal, you will effectively create a Gatein extension.
The extension mechanism makes it possible to extend or even override portal resources in an almost plug-and-play fashion, by simply dropping in a .war archive with the resources and configuring its location in the portal's classpath. Customizing a portal do not involve unpacking and repacking the original portal .war archives. Instead, you create your own .war archive with your own configurations, and eventually modified resources that override the resources in the original archive.
eXo Platform comes with a preconfigured PortalContainer named "portal". This portal container configuration ties together the core and extended services stack. It is started from portal.war and naturally maps to the /portal uri.
The gatein extensions mechanism lets you very easily extend the /portal context. This capacity is fundamental as it shields you from any changes we may want to do in portal.war. You do not need to fear of upgrades anymore as your extension war will be clearly separated from the portal war.
To achieve this magical extensibility, the PortalContainer turns on two advanced features :
a unified classloader : any classpath resource, such as xml configuration files, will be accessible as if it was inside the portal.war
a unified servlet context : any web ressource contained in your extension war will be accessible from /portal/ uri
The next paragraph explains what to do to make a simple extension for "portal" container.
First, you will want eXo to load the WEB-INF/conf/configuration.xml of your extension. For this, declare it as a PortalConfigOwner in web.xml :
<web-app> <display-name>my-portal</display-name> <listener> <listener-class>org.exoplatform.container.web.PortalContainerConfigOwner</listener-class> </listener> <!-- ... --> </web-app>
Then you need to register your extension by appending it to the list of dependencies that already contribute to the portal container. This is done by an xml configuration liek this :
<external-component-plugins>
<target-component>org.exoplatform.container.definition.PortalContainerConfig</target-component>
<component-plugin>
<name>Change PortalContainer Definitions</name>
<set-method>registerChangePlugin</set-method>
<type>org.exoplatform.container.definition.PortalContainerDefinitionChangePlugin</type>
<init-params>
<object-param>
<name>change</name>
<object type="org.exoplatform.container.definition.PortalContainerDefinitionChange$AddDependencies">
<field name="dependencies">
<collection type="java.util.ArrayList">
<value>
<string>my-portal</string>
</value>
<value>
<string>my-portal-resources</string>
</value>
</collection>
</field>
</object>
</object-param>
<value-param>
<name>apply.default</name>
<value>true</value>
</value-param>
</init-params>
</component-plugin>
</external-component-plugins>
We define a PortalContainerDefinitionChange$AddDependencies plugin to the PortalContainerConfig. The plugin declares a list of dependencies that are webapps. The apply.default=true indicates that your extension is acutally extending portal.war.
All data in eXo Platform is stored in a Java Content Repository (JCR). JCR is a Java specification (JSR-170 ) for a type of Document database. It is particularly useful for content management systems, which require storage of objects associated with metadata. A JCR also provides versioning, transactions, observations of changes in data, and import and export of data in XML. The data in a JCR is stored hierarchically in a tree of Nodes with associated Properties.
Also, the JCR is primarily used as an internal storage engine, eXo Content let's you manipulate JCR data directly in several places. This quick intro will tell you how to quickly get your hands on it.
A content repository consists of one or more workspaces, each of which contains a tree of items.
To access a repository's content at a component level :
import javax.jcr.Session; import org.exoplatform.services.jcr.RepositoryService; import org.exoplatform.services.jcr.core.ManageableRepository; import org.exoplatform.services.jcr.ext.common.SessionProvider; import org.exoplatform.services.wcm.utils.WCMCoreUtils; // For example RepositoryService repositoryService = WCMCoreUtils.getService(RepositoryService.class); ManageableRepository manageableRepository = repositoryService.getRepository(repository); SessionProvider sessionProvider = WCMCoreUtils.getSessionProvider(); Session session = sessionProvider.getSession(workspace, manageableRepository);
Every node can only have one primary node type. The primary node type defines the names, types and other characteristics of the properties and child nodes that a node is allowed (or required) to have. Every node has a special property called jcr:primaryType that records the name of its primary node type. A node may also have one or more mixin types. These are node type definitions that can mandate extra characteristics (i.e., more child nodes, properties and their respective names and types).
Data is stored in the Properties, which may hold simple values such as numbers and strings or binary data of arbitrary length.
include sample JCR data
The JCR API provides methods to define node types and node properties, create or delete nodes, and add or delete properties to an existing node.