JBoss.orgCommunity Documentation
eXo Kernel is the basis of all eXo platform products and modules. Any component available in eXo Platform is managed by the Exo Container, our micro container responsible for gluing the services through dependency injection
Therefore, each product is composed of a set of services and plugins registered to the container and configured by XML configuration files.
The Kernel module also contains a set of very low level services.
ExoContainer is the main IoC kernel object. The container is responsible for loading services/components.
This section provides you the basic knowledge about modes, services and containers. You will find out where the service configuration files should be placed, and you will also see the overriding mechanism of configurations.
Finally, you will understand how the container creates the services one after the other and what Inversion of Control really means.
Related documents
By reading this article you are already glancing at the heart of eXo Kernel.
Even you will read in this article to open the directory "exo-tomcat", you may have installed GateIn on any application server, just replace "exo-tomcat" by your folder name.
If you only installed Gatein or eXo Platform, the folder paths are a slightly different. You wil need to replace exo-tomcat with your tomcat home directory.
Nearly everything could be considered a service! To get a better idea, let's look into the exo-tomcat/lib folder where you find all deployed jar files.
For example you find services for databases, caching, ldap and ftp:
exo.core.component.database-2.1.3.jar
exo.kernel.component.cache-2.0.5.jar
exo.core.component.organization.ldap-2.1.3.jar
exo.jcr.component.ftp-1.10.1.jar
Of course, there are many more services, in fact a lot of these jar files are services. To find out you have to open the jar file and then look into its /conf or /conf/portal directory. Only if there is a file named configuration.xml, you are sure to have found a service.
Why are there 2 different places to look for the
configuration.xml? Because the /conf directory is
used by the RootContainer
and the
/conf/portal directory is used by the
PortalContainer
. Later you will see more details
about these containers.
Interface - Implementation It's important to get the idea that you separate the interface and implementation for a service. That is a good concept to reduce dependencies on specific implementations. This concept is well known for JDBC. If you use standard JDBC (=interface), you can connect any database (=implementation) to your application. In a similar way any service in eXo is defined by a java interface and may have many different implementations. The service implementation is then injected by a container into the application.
Singleton Each service has to be implemented as a singleton, which means that each service is created only once - in one single instance.
Service = Component You always read about services, and you imagine a service as a large application which does big things, but that's not true, a service can be just a little component that reads or transforms a document, therefore the term component is often used instead of service - so bear in mind: a service and a component can safely be considered to be the same thing.
The jar file of a service should contain a default configuration, you find this configuration in the configuration.xml file which comes with the jar. A configuration file can specify several services, as well as there can be several services in one jar file.
For example open the exo.kernel.component.cache-2.0.5.jar file and inside this jar open /conf/portal/configuration.xml. You will see:
<component> <key>org.exoplatform.services.cache.CacheService</key> <type>org.exoplatform.services.cache.impl.CacheServiceImpl</type> ...
Here you will note that a service is specified between the
<component>
tags. Each service has got a key,
which defines the kind of service. As you imagine the content of the
<key>
tag matches the qualified
java interface name
(org.exoplatform.services.cache.CacheService
) of
the service. The specific implementation class of the
CacheService
is defined in the
<type>
tag.
Parameters You have already opened
some configuration files and seen that there are more than just
<key>
and <type>
tags. You can provide your service with init parameters. The parameters
can be simple parameters, properties, or object-params. There are also
plugins and they are special because the container
calls the setters of your service in order to inject
your plugin in your service (called setter injection)
see Service
Configuration in Detail. In general your service is free to use
init parameters, they are not required.
If you ever need to create your own service, the minimum is to create an empty interface, an empty class and a constructor for your class - that's all. Ok, you also should put your class and the interface in a jar file and add a default configuration file.
One important thing to understand concerns execution modes. There are only two modes:
Portal mode: The service runs embedded in the GateIn. In
this mode a PortalContainer
is used.
Standalone mode: The service runs without the portal. For
example, the JCR service can run standalone, and also the eXo Portlet
Container. This mode is used by eXo developers for unit tests. As the
name suggests a StandaloneContainer
is
used.
In order to access to a service you need to use a Container. Just open https://github.com/exoplatform/kernel/tree/stable/2.3.x/exo.kernel.container/src/main/java/org/exoplatform/container.
Among the classes you see in this directory, you only will be interested in these three container types:
RootContainer: This is a base container. This container plays an important role during startup, but you should not use it directly.
PortalContainer: Created at the startup of the portal web application (in the init() method of the PortalController servlet)
StandaloneContainer: A context independent eXo Container. The
StandaloneContainer
is also used for unit
tests.
Use only one container Even if there are several container types you always use exactly one. The RootContainer is never directly used and it depends on the execution mode if you use the PortalContainer or the StandaloneContainer. You will ask how to find out the execution mode in my application and how to manage these two modes. It's easy, you don't have to worry about it because the ExoContainerContext class provides a static method that allows you to get the right container from anywhere (see info box).
PicoContainer All containers
inherit from the ExoContainer class which itself inherits from a
PicoContainer
. PicoContainer is a framework
which allows eXo to apply the IoC (Inversion of Control)
principles. The precise implementations of any service is unknown at
compile time. Various implementations can be used, eXo supplies different
implementations but they also may be delivered by other vendors. The
decision which service to use during runtime is made in configuration
files.
These configuration files are read by the container, the container adds all services to a list or more exactly a java HashTable. It's completely correct to suppose that the configuration.xml you already saw plays an important role. But there are more places where a configuration for a service can be defined as you see in the next section.
"In your java code you have to use
ExoContainer myContainer = ExoContainerContext.getCurrentContainer();
in order to access to the current container. It doesn't greatly matter
to your application if the current container is a
PortalContainer
or a
StandaloneContainer
. Once you have your container
you may access to any service registered in this container using
MyService myService = (MyService) myContainer.getComponentInstance(MyService.class);
You easily realize that MyService.class
is the
name of the service interface.
The configuration you find inside the jar file is considered as the default configuration. If you want to override this default configuration you can do it in different places outside the jar. When the container finds several configurations for the same service, the configuration which is found later replaces completely the one found previously. Let's call this the configuration override mechanism.
As both containers, PortalContainer and StandaloneContainer, depend on the RootContainer, we will start by looking into this one.
The retrieval sequence in short:
Services default RootContainer
configurations from JAR files
/conf/configuration.xml
External RootContainer
configuration,
to be found at
exo-tomcat/exo-conf/configuration.xml
Naturally you always have to replace
exo-tomcat
by your own folder name. In case of
a Java Standalone application you have to use the
user.dir
JVM system property value.
HashTable The
RootContainer
creates a java
HashTable
which contains key-value pairs for the
services. The qualified interface name of each service is used as key
for the hashtable. Hopefully you still remember that the
<key>
tag of the configuration file
contains the interface name? The value of each hashtable pair is an
object that contains the service configuration (yes, this means the
whole structure between the <component>
tags of your configuration.xml
file).
The RootContainer
runs over all jar files
you find in exo-tomcat/lib and looks if there is a
configuration file at /conf/configuration.xml, the
services configured in this file are added to the hashtable. That way -
at the end of this process - the default configurations for all services
are stored in the hashtable.
What happens if the same service - recognized by the same qualified interface name - is configured in different jars? As the service only can exist one time the configuration of the jar found later overrides the previous configuration. You know that the loading order of the jars is unpredictable you must not depend on this.
If you wish to provide your own configurations for one or several services, you can do it in a general configuration file that has to be placed at exo-tomcat/exo-conf/configuration.xml. Do not search for such a file on your computer - you won't find one, because this option is not used in the default installation. Here again the same rule applies: The posterior configuration replaces the previous one.
The further configuration retrieval depends on the container type.
The PortalContainer takes the hashtable filled by the RootContainer and continues to look in some more places. Here you get the opportunity to replace RootContainer configurations by those which are specific to your portal. Again, the configurations are overridden whenever necessary.
In short PortalContainer configurations are retrieved in the following lookup sequence :
Take over the configurations of the RootContainer
Default PortalContainer configurations from all JAR files (folder /conf/portal/configuration.xml)
Web application configurations from the portal.war file - or the portal weppapp (folder /WEB-INF/conf/configuration.xml)
External configuration for services of a named portal, it will be found at exo-tomcat/exo-conf/portal/$portal_name/configuration.xml (as of GateIn)
You see, here the /conf/portal/configuration.xml file of each jar enters the game, they are searched at first. Next, there is nearly always a configuration.xml in the portal.war file (or in the portal webapp folder), you find this file at /WEB-INF/conf/configuration.xml. If you open it, you will find a lot of import statements that point to other configuration files in the same portal.war (or portal webapp).
Multiple Portals Be aware that
you might set up several different portals ("admin", "mexico", etc.),
and each of these portals will use a different PortalContainer. And each
of these PortalContainers can be configured separately. As of GateIn
you also will be able to provide configurations from outside the
jars and wars or webapps. Put a configuration file in
exo-tomcat/exo-conf/portal/$portal_name/configuration.xml
where $portal_name
is the name of the portal you
want to configure for . But normally you only have one portal which is
called "portal" so you use
exo-tomcat/exo-conf/portal/portal/configuration.xml.
GateIn you can override the external configuration location with the system property exo.conf.dir. If the property exists its value will be used as path to the eXo configuration directory, that means this is an alternative to exo-tomcat/exo-conf. Just put this property in the command line: java -Dexo.conf.dir=/path/to/exo/conf or use eXo.bat or eXo.sh. In this particular use case, you have no need to use any prefixes in your configuration file to import other files. For example, if your configuration file is exo-tomcat/exo-conf/portal/PORTAL_NAME/configuration.xml and you want to import the configuration file exo-tomcat/exo-conf/portal/PORTAL_NAME/mySubConfDir/myConfig.xml, you can do it by adding <import>mySubConfDir/myConfig.xml</import> to your configuration file.
Under JBoss application server exo-conf will be looked up in directory described by JBoss System property jboss.server.config.url. If the property is not found or empty exo-jboss/exo-conf will be asked (since kernel 2.0.4).
In the same way as the PortalContainer the StandaloneContainer takes over the configuration of the RootContainer. After that our configuration gets a little bit more tricky because standalone containers can be initialized using an URL. This URL contains a link to an external configuration. As you probably never need a standalone configuration you can safely jump over the remaining confusing words of this section.
After taking over RootContainer's configuration, there are three cases which depend on the URL initialization, :
Independent configuration by URL No other configuration file is taken in consideration. The configuration provided by the URL is used without any default configs. That means that the container creates a new empty hashtable and not any bit of previous configuration is used. Apply the following code to do this:
StandaloneContainer.setConfigurationURL(containerConf);
Additional configuration by URL The StandaloneContainer is initialized very similar to the PortalContainer, but the last step is slightly different. A configuration file that is provided by the URL is used to replace some of the service configurations.The code looks like this:
StandaloneContainer.addConfigurationURL(containerConf);
Take over the configurations of the RootContainer
Default StandaloneContainer configurations from JAR files (folder /conf/portal/configuration.xml)
Web application configurations from WAR files (folder /WEB-INF/conf/configuration.xml)
Configuration from added URL containerConf overrides only services configured in the file
File based configuration No URL is involved, in this case the sequence is:
Take over the configurations of the RootContainer
Default StandaloneContainer configurations from JAR files (folder /conf/portal/configuration.xml)
Web applications configurations from WAR files (folder /WEB-INF/conf/configuration.xml)
External configuration for StandaloneContainer services, it will be found at $user_home/exo-configuration.xml. If $user_home/exo-configuration.xml doesn't exist and the StandaloneContainer instance obtained with the dedicated configuration classloader the container will try to retrieve the resource conf/exo-configuration.xml within the given classloader (user_home is your home directory like "C:/Documents and Settings/Smith").
As you have already learned the services are all singletons, so that
the container creates only one single instance of each container. The
services are created by calling the constructors (called
constructor injection). If there are only
zero-arguments constructors (Foo public Foo(){}
) there are no
problems to be expected. That's easy.
But now look at OrganizationServiceImpl.java
This JDBC implementation of BaseOrganizationService interface has only one constructor:
public OrganizationServiceImpl(ListenerService listenerService, DatabaseService dbService);
You see this service depends on two other services. In order to be
able to call this constructor the container first needs a
ListenerService
and a
DatabaseService
. Therefore these services must be
instantiated before BaseOrganizationService
,
because BaseOrganizationService
depends on
them.
For this purpose the container first looks at the constructors of
all services and creates a matrix of service dependencies in order to call
the services in a proper order. If for any reason there are
interdependencies or circular dependencies you will get a java
Exception
. In this way the dependencies
are injected by the container.
What happens if one service has more than one constructor? The container always tries first to use the constructor with a maximum number of arguments, if this is not possible the container continues step by step with constructors that have less arguments until arriving at the zero-argument constructor (if there is any).
Your service can implement the startable interface which defines a start() and a stop() method. These methods are called by the container at the beginning and the end of the container's lifecycle. This way the lifecycle of your service is managed by the container.
Retrospection. Do you remember your last project where you had some small components and several larger services? How was this organized? Some services had their own configuration files, others had static values in the source code. Most components were probably tightly coupled to the main application, or you called static methods whenever you needed a service in your java class. Presumably you even copied the source code of an earlier project in order to adapt the implementation to your needs. In short:
Each of your service had a proprietary configuration mechanism.
The service lifecycles were managed inside of each service or were arbitrary.
The dependencies between your services were implementation-dependent and tightly coupled in your source code.
New Approach. You have seen that eXo uses the Inversion of Control (IoC) pattern which means that the control of the services is given to an independent outside entity, in this case a container. Now the container takes care of everything:
The configuration is injected by external configuration files.
The lifecycle is managed from outside, because the constructors are called by the container. You can achieve an even finer lifecycle management if you use the startable interface.
The dependencies are injected by the service instantiation process.
Dependency Injection. You also saw two types of dependency injections:
Constructor injection: The constructor is called by the container.
Setter injection: Whenever you use external-plugins to provide your service with plugins (see Service Configuration in Detail.
There are two more Containers called
RepositoryContainer
and
WorkspaceContainer
. These are specificities of
eXo JCR, for the sake of simplicity. You don't need them.
In some case the developer of a service does not expect that there will be several implementations for his service. Therefore he does not create an interface. In this case the configuration looks like this:
<key>org.exoplatform.services.database.jdbc.DBSchemaCreator</key> <type>org.exoplatform.services.database.jdbc.DBSchemaCreator</type>
The key and type tags contain equally the qualified class name.
Since kernel 2.0.7 and 2.1, it is possible to use system properties in literal values of component configuration meta data. Thus it is possible to resolve properties at runtime instead of providing a value at packaging time.
<component> ... <init-params> <value-param> <name>simple_param</name> <value>${simple_param_value}</value> </value-param> <properties-param> <name>properties_param</name> <property name="value_1" value="properties_param_value_1"/> <property name="value_2" value="${properties_param_value_2}"/> </properties-param> <object-param> <name>object_param</name> <object type="org.exoplatform.xml.test.Person"> <field name="address"><string>${person_address}</string></field> <field name="male"><boolean>${person_male}</boolean></field> <field name="age"><int>${age_value}</int></field> <field name="size"><double>${size_value}</double></field> </object> </object-param> </init-params> </component>
In case you need to solve problems with your service
configuration, you have to know from which JAR/WAR causes your troubles.
Add the JVM system property
org.exoplatform.container.configuration.debug
to
your eXo.bat or eXo.sh file (exo-tomcat/bin/).
set EXO_CONFIG_OPTS="-Dorg.exoplatform.container.configuration.debug"
If this property is set the container configuration manager reports during startup the configuration retrieval process to the standard output (System.out).
...... Add configuration jar:file:/D:/Projects/eXo/dev/exo-working/exo-tomcat/lib/exo.kernel.container-trunk.jar!/conf/portal/configuration.xml Add configuration jar:file:/D:/Projects/eXo/dev/exo-working/exo-tomcat/lib/exo.kernel.component.cache-trunk.jar!/conf/portal/configuration.xml Add configuration jndi:/localhost/portal/WEB-INF/conf/configuration.xml import jndi:/localhost/portal/WEB-INF/conf/common/common-configuration.xml import jndi:/localhost/portal/WEB-INF/conf/database/database-configuration.xml import jndi:/localhost/portal/WEB-INF/conf/ecm/jcr-component-plugins-configuration.xml import jndi:/localhost/portal/WEB-INF/conf/jcr/jcr-configuration.xml ......
Do you feel yourself to be an expert now? Not yet? Get a deeper look and read this Services Wiring article. You read so much about configuration, that you should wonder what the XML Schema of the configuration file looks like.
If you wish to see examples of service configurations you should study the Core. Where you find descriptions of some eXo's core services. Finally you might wish to read more about PicoContainer.
This section shows you how to set up a sample service with some configurations and how to access the configuration parameters. The later sections describe all details of the configuration file (parameters, object-params, plugins, imports, and more). It also shows how to access the configuration values. You may consider this document as a reference, but you can also use this document as a tutorial and read it from the beginning to the end.
Related documents
You should have read and understood Service Configuration for Beginners. Obviously you should know java and xml. We are working with examples that are created for teaching reasons only and you will see extracts from the eXo Products default installation. When reading this article, you do not forget that the terms service and component are interchangeable in eXo Products.
Imagine that you are working for a publishing company called "La Verdad" that is going to use eXo platform. Your boss asks you be able to calculate the number of sentences of an article.
You remember in eXo product everything is a service so you decide to create a simple class. In the future, you want to be able to plug different implementations of your service, so that you should define an interface that defines your service.
package com.laverdad.services; public interface ArticleStatsService { public abstract int calcSentences(String article); }
A very simple implementation:
public class ArticleStatsServiceImpl implements ArticleStatsService { public int calcSentences(String article) { throw new RuntimeException("Not implemented"); } }
That's it! You see there are no special prerequisites for a service.
You should already have prepared your working environment, where you have a base folder (let's call it our service base folder). If you wish to try out this example create this class in the com/laverdad/services/ArticleStatsService subfolder.
When creating a service, you also should declare its existence to the Container, therefore you create a first simple configuration file. Copy the following code to a file called "configuration.xml" and place this file in a /conf subdirectory of your service base folder. As you already know the container looks for a "/conf/configuration.xml" file in each jar-file.
<?xml version="1.0" encoding="UTF8"?> <configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.exoplatform.org/xml/ns/kernel_1_2.xsd http://www.exoplatform.org/xml/ns/kernel_1_2.xsd" xmlns="http://www.exoplatform.org/xml/ns/kernel_1_2.xsd"> <component> <key>com.laverdad.services.ArticleStatsService</key> <type>com.laverdad.services.ArticleStatsServiceImpl</type> </component> </configuration>
You are correctly using the namespace of the configuration
schema ( http://www.exoplatform.org/xml/ns/kernel_1_2.xsd
).
Most of the configuration schema is explained in this article,
therefore you do not need to open and understand the schema. For
backward compatibility it is not necessary to declare the
schema.
When eXo kernel reads a configuration, it loads the file from the kernel jar using the classloader and does not use an internet connection to resolve the file.
You see your service has a configuration file, but you wonder how the file can gain access to its configuration. Imagine that you are asked to implement two different calculation methods: fast and exact.
You create one init parameter containing the calculation methods. For the exact method, you wish to configure more details for the service. Let's enhance the word service configuration file:
<component> <key>com.laverdad.services.ArticleStatsService</key> <type>com.laverdad.services.ArticleStatsServiceImpl</type> <init-params> <value-param> <name>calc-method</name> <description>calculation method: fast, exact</description> <value>fast</value> </value-param> <properties-param> <name>details-for-exact-method</name> <description>details for exact phrase counting</description> <property name="language" value="English" /> <property name="variant" value="us" /> </properties-param> </init-params> </component>
When configuring your service, you are totally free. You can provide as many value-param, property-param, and properties as you wish, and you can give them any names or values. You only must respect the xml structure.
Now let's see how our service can read this configuration. The implementation of the calcSentences() method serves just as a simple example. It's up to your imagination to implement the exact method.
public class ArticleStatsServiceImpl implements ArticleStatsService { private String calcMethod = "fast"; private String variant = "French"; private String language = "France"; public ArticleStatsServiceImpl(InitParams initParams) { super(); calcMethod = initParams.getValueParam("calc-method").getValue(); PropertiesParam detailsForExactMethod = initParams.getPropertiesParam("details-for-exact-method"); if ( detailsForExactMethod != null) { language = detailsForExactMethod.getProperty("language"); variant = detailsForExactMethod.getProperty("variant"); } } public int calcSentences(String article) { if (calcMethod == "fast") { // just count the number of periods "." int res = 0; int period = article.indexOf('.'); while (period != -1) { res++; article = article.substring(period+1); period = article.indexOf('.'); } return res; } throw new RuntimeException("Not implemented"); } }
You see you just have to declare a parameter of org.exoplatform.container.xml.InitParams in your constructor. The container provides an InitParams object that correspond to the xml tree of init-param.
As you want to follow the principle of Inversion of Control, you must not access the service directly. You need a Container to access the service.
With this command you get your current container:
ExoContainer myContainer = ExoContainerContext.getCurrentContainer();
This might be a PortalContainer or a StandaloneContainer, dependant on the execution mode in which you are running your application.
Whenever you need one of the services that you have configured use the method:
myContainer.getComponentInstance(class)
In our case:
ArticleStatsService statsService = (ArticleStatsService) myContainer.getComponentInstance(ArticleStatsService.class);
Recapitulation:
package com.laverdad.common; import org.exoplatform.container.ExoContainer; import org.exoplatform.container.ExoContainerContext; import com.laverdad.services.*; public class Statistics { public int makeStatistics(String articleText) { ExoContainer myContainer = ExoContainerContext.getCurrentContainer(); ArticleStatsService statsService = (ArticleStatsService) myContainer.getComponentInstance(ArticleStatsService.class); int numberOfSentences = statsService.calcSentences(articleText); return numberOfSentences; } public static void main( String args[]) { Statistics stats = new Statistics(); String newText = "This is a normal text. The method only counts the number of periods. " + "You can implement your own implementation with a more exact counting. " + "Let`s make a last sentence."; System.out.println("Number of sentences: " + stats.makeStatistics(newText)); } }
If you test this sample in standalone mode, you need to put all jars of eXo Kernel in your buildpath, furthermore picoContainer is needed.
There is an value-param example:
<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> ... </component>
The UserACL class 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()); ...
Properties are name-value pairs. Both the name and the value are Java Strings.
Here you see the hibernate configuration example:
<component> <key>org.exoplatform.services.database.HibernateService</key> <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"/> <property name="hibernate.connection.driver_class" value="org.hsqldb.jdbcDriver"/> ... </properties-param> </init-params> </component>
In the org.exoplatform.services.database.impl.HibernateServiceImpl you will find that the name "hibernate.properties" of the properties-param is used to access the properties.
package org.exoplatform.services.database.impl; public class HibernateServiceImpl implements HibernateService, ComponentRequestLifecycle { public HibernateServiceImpl(InitParams initParams, CacheService cacheService) { PropertiesParam param = initParams.getPropertiesParam("hibernate.properties"); ... }
Let's have a look at the configuration of the LDAPService. It's not important to know LDAP, we only discuss the parameters.
<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>
You see here an object-param is being used to pass the parameters inside an object (actually a java bean). It consists of a name, a description and exactly one object. The object defines the type and a number of fields.
Here you 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 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"; ...
You see that the types (String, int) of the fields in the configuration correspond with the bean. A short glance in the kernel_1_0.xsd file let us discover more simple types:
string, int, long, boolean, date, double
Have a look on this type test xml file: object.xml.
You also can use java collections to configure your service. In order to see an example, let's open the database-organization-configuration.xml file. This file defines a default user organization (users, groups, memberships/roles) of your portal. They use component-plugins which are explained later. You wil see that object-param is used again.
There are two collections: The first collection is an ArrayList. This ArrayList contains only one value, but there could be more. The only value is an object which defines the field of the NewUserConfig$JoinGroup bean.
The second collection is a HashSet that is a set of strings.
<component-plugin> <name>new.user.event.listener</name> <set-method>addListenerPlugin</set-method> <type>org.exoplatform.services.organization.impl.NewUserEventListener</type> <description>this listener assign group and membership to a new created user</description> <init-params> <object-param> <name>configuration</name> <description>description</description> <object type="org.exoplatform.services.organization.impl.NewUserConfig"> <field name="group"> <collection type="java.util.ArrayList"> <value> <object type="org.exoplatform.services.organization.impl.NewUserConfig$JoinGroup"> <field name="groupId"><string>/platform/users</string></field> <field name="membership"><string>member</string></field> </object> </value> </collection> </field> <field name="ignoredUser"> <collection type="java.util.HashSet"> <value><string>root</string></value> <value><string>john</string></value> <value><string>marry</string></value> <value><string>demo</string></value> <value><string>james</string></value> </collection> </field> </object> </object-param> </init-params> </component-plugin>
Let's look at the org.exoplatform.services.organization.impl.NewUserConfig bean:
public class NewUserConfig { private List role; private List group; private HashSet ignoredUser; ... public void setIgnoredUser(String user) { ignoredUser.add(user); ... static public class JoinGroup { public String groupId; public String membership; ... }
You see the values of the HashSet are set one by one by the container, and it's the responsibility of the bean to add these values to its HashSet.
The JoinGroup object is just an inner class and implements a bean of its own. It can be accessed like any other inner class using NewUserConfig.JoinGroup.
The External Plugin allows you to add configuration on the fly.
As you have carefully read Service Configuration for Beginners you know that normally newer configurations always replaces previous configurations. An external plugin allows you to add configuration without replacing previous configurations.
That can be interesting if you adapt a service configuration for your project-specific needs (country, language, branch, project, etc.).
Let's have a look at the configuration of the TaxonomyPlugin of the CategoriesService:
<external-component-plugins> <target-component>org.exoplatform.services.cms.categories.CategoriesService</target-component> <component-plugin> <name>predefinedTaxonomyPlugin</name> <set-method>addTaxonomyPlugin</set-method> <type>org.exoplatform.services.cms.categories.impl.TaxonomyPlugin</type> <init-params> <value-param> <name>autoCreateInNewRepository</name> <value>true</value> </value-param> <value-param> <name>repository</name> <value>repository</value> </value-param> <object-param> <name>taxonomy.configuration</name> <description>configuration predefined taxonomies to inject in jcr</description> <object type="org.exoplatform.services.cms.categories.impl.TaxonomyConfig"> <field name="taxonomies"> <collection type="java.util.ArrayList"> <!-- cms taxonomy --> <value> <object type="org.exoplatform.services.cms.categories.impl.TaxonomyConfig$Taxonomy"> <field name="name"><string>cmsTaxonomy</string></field> <field name="path"><string>/cms</string></field> </object> </value> <value> <object type="org.exoplatform.services.cms.categories.impl.TaxonomyConfig$Taxonomy"> <field name="name"><string>newsTaxonomy</string></field> <field name="path"><string>/cms/news</string></field> </object> </value> </field> </object> </object-param> </init-params> </component-plugin> <external-component-plugins>
The <target-component> defines the service for which the plugin is defined. The configuration is injected by the container using a method that is defined in <set-method>. The method has exactly one argument of the type org.exoplatform.services.cms.categories.impl.TaxonomyPlugin:
addTaxonomyPlugin(org.exoplatform.services.cms.categories.impl.TaxonomyPlugin plugin)
The content of <init-params> corresponds to the structure of the TaxonomyPlugin object.
You can configure the component CategoriesService using the addTaxonomyPlugin as often as you wish, you can also call addTaxonomyPlugin in different configuration files. The method addTaxonomyPlugin is then called several times, everything else depends on the implementation of the method.
The import tag allows to import other configuration files using URLs that are configuration manager specific, for more details about what are the supported URLs please refer to the next section about the configuration manager.
See below an example of a configuration file composed of imports:
<import>war:/conf/common/common-configuration.xml</import> <import>war:/conf/common/logs-configuration.xml</import> <import>war:/conf/database/database-configuration.xml</import> <import>war:/conf/jcr/jcr-configuration.xml</import> <import>war:/conf/common/portlet-container-configuration.xml</import> ...
Since kernel 2.0.7 and 2.1, it is possible to use system properties in literal values of component configuration meta data. This makes it possible to resolve properties at runtime instead of providing a value at packaging time.
See below an example of a configuration file based on system properties:
<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.connection.url" value="${connectionUrl}"/> <property name="hibernate.connection.driver_class" value="${driverClass}"/> <property name="hibernate.connection.username" value="${username}"/> <property name="hibernate.connection.password" value="${password}"/> <property name="hibernate.dialect" value="${dialect}"/> ... </properties-param> </init-params> </component>
As these are system properties you use the -D command: java -DconnectionUrl=jdbc:hsqldb:file:../temp/data/exodb -DdriverClass=org.hsqldb.jdbcDriver Or better use the parameters of eXo.bat / eXo.sh when you start GateIn: set EXO_OPTS="-DconnectionUrl=jdbc:hsqldb:file:../temp/data/exodb -DdriverClass=org.hsqldb.jdbcDriver"
The configuration manager allows you to find files using URL with special prefixes that we describe in details below.
war: try to find the file using the Servlet Context of your portal.war or any web applications defined as PortalContainerConfigOwner, so for example in case of the portal.war if the URL is war:/conf/common/portlet-container-configuration.xml it will try to get the file from portal.war/WEB-INF/conf/common/portlet-container-configuration.xml.
jar or classpath: you can use this prefix to find a file that is accessible using the ClassLoader. For example jar:/conf/my-file.xml will be understood as try to find conf/my-file.xml from the ClassLoader.
file: this prefix will indicate the configuration manager that it needs to interprete the URL as an absolute path. For example file:///path/to/my/file.xml will be understood as an obsolute path.
Without prefixes: it will be understood as a relative path from the parent directory of the last processed configuration file. For example, if the configuration manager is processing the file corresonding to the URL file:///path/to/my/configuration.xml and in this file you import dir/to/foo.xml, the configuration manager will try to get the file from file:///path/to/my/dir/to/foo.xml. Please note that it works also for other perfixes. In case you use the configuration manager in a component to get a file like the example below, it will depend on the mode and will be relative to the following directories:
<component> <key>org.exoplatform.services.resources.LocaleConfigService</key> <type>org.exoplatform.services.resources.impl.LocaleConfigServiceImpl</type> <init-params> <value-param> <name>locale.config.file</name> <value>war:/conf/common/locales-config.xml</value> </value-param> </init-params> </component>
In standalone mode: it will be a relative path to where it can find the file exo-configuration.xml knowing that the file is first checked in the user directory, if it cannot be found there, it will check in the exo configuration directory and if it still cannot be found it will try to find conf/exo-configuration.xml in the ClassLoader.
In portal mode: it will be a relative path to the exo configuration directory in case of the RootContainer (assuming that a file configuration.xml exists there otherwise it would be hard to know) and from ${exo-configuration-directory}/portal/${portal-container-name} in case of the PortalContainer (assuming that a file configuration.xml exists there otherwise it would be hard to know).
For more details about the exo configuration directory please refer to the Configuration Retrieval section.
GateIn uses PicoContainer, which implements the Inversion of Control (IoC) design pattern. All eXo containers inherit from a PicoContainer. There are mainly two eXo containers used, each of them can provide one or several services. Each container service is delivered in a JAR file. This JAR file may contain a default configuration. The use of default configurations is recommended and most services provide it.
When a Pico Container searches for services and its configurations, each configurable service may be reconfigured to override default values or set additional parameters. If the service is configured in two or more places the configuration override mechanism will be used.
Confused? - You might be interested in the Service Configuration for Beginners section to understand the basics.
To be effective, the namespace URI
http://www.exoplatform.org/xml/ns/kernel_1_2.xsd
must be target
namespace of the XML configuration file.
<configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.exoplatform.org/xml/ns/kernel_1_2.xsd http://www.exoplatform.org/xml/ns/kernel_1_2.xsd" xmlns="http://www.exoplatform.org/xml/ns/kernel_1_2.xsd"> ... </configuration>
Any values in the configuration files can be created thanks to variables since the eXo kernel resolves them, for example the following configuration will be well interpreted:
<configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.exoplatform.org/xml/ns/kernel_1_2.xsd http://www.exoplatform.org/xml/ns/kernel_1_2.xsd" xmlns="http://www.exoplatform.org/xml/ns/kernel_1_2.xsd"> <import>${db.configuration.path}/db.xml</import> <import>${java.io.tmpdir}/bindfile.xml</import> <import>simple.xml</import> </configuration>
The variables that are supported, are System properties and variables that are specific to your portal container, see next sections for more details.
GateIn uses PicoContainer, which implements the Inversion of Control (IoC) design pattern. All eXo containers inherit from a PicoContainer. There are mainly two eXo containers used, each of them can provide one or several services. Each container service is delivered in a JAR file. This JAR file may contain a default configuration. The use of default configurations is recommended and most of services provide it.
When a Pico Container searches for services and its configurations, each configurable service may be reconfigured to override default values or set additional parameters. If the service is configured in two or more places, the configuration override mechanism will be used.
The container performs the following steps to make eXo Container configuration retrieval, depending on the container type.
The container is initialized by looking into different locations. This container is used by portal applications. Configurations are overloaded in the following lookup sequence:
Services default RootContainer
configurations
from JAR files /conf/configuration.xml
External RootContainer
configuration can be
found at
$AS_HOME/exo-conf/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 named portal can be found at $AS_HOME/exo-conf/portal/$PORTAL_NAME/configuration.xml
The container is initialized by looking into different locations. This container is used by non portal applications. Configurations are overloaded in the following lookup sequence:
Services default RootContainer
configurations
from JAR files /conf/configuration.xml
External RootContainer
configuration can be
found at
$AS_HOME/exo-conf/configuration.xml
Services default StandaloneContainer
configurations from JAR files
/conf/portal/configuration.xml
Web applications configurations from WAR files /WEB-INF/conf/configuration.xml
Then depending on the StandaloneContainer
configuration URL initialization:
if configuration URL was initialized to be added to services defaults, as below:
// add configuration to the default services configurations from JARs/WARs StandaloneContainer.addConfigurationURL(containerConf);
Configuration from added URL containerConf will override only services configured in the file
if configuration URL not initialized at all, it will be
found at $AS_HOME/exo-configuration.xml.
If $AS_HOME/exo-configuration.xml doesn't
exist the container will try find it at
$AS_HOME/exo-conf/exo-configuration.xml
location and if it's still not found and the
StandaloneContainer
instance obtained with the
dedicated configuration ClassLoader
the
container will try to retrieve the resource
conf/exo-configuration.xml within the
given ClassLoader
.
$AS_HOME - application server home directory, or user.dir JVM system property value in case of Java Standalone application. The application server home is:
For Jonas
, the value of the variable
${jonas.base}.
For Jetty
, the value of the variable
${jetty.home}.
For Websphere
, the value of the variable
${was.install.root}.
For Weblogic
, the value of the variable
${wls.home}.
For Glassfish
, the value of the variable
${com.sun.aas.instanceRoot}.
For Tomcat
, the value of the variable
${catalina.home}.
For JBoss AS
, the value of the variable
${jboss.server.config.url} if the exo-conf
directory can be found there otherwise it will be the value of
the variable ${jboss.home.dir}.
$PORTAL_NAME - portal web application name.
External configuration location can be overridden with System property exo.conf.dir. If the property exists, its value will be used as path to eXo configuration directory, i.e. to $AS_HOME/exo-conf alternative. E.g. put property in command line java -Dexo.conf.dir=/path/to/exo/conf. In this particular use case, you do not need to use any prefix to import other files. For instance, if your configuration file is $AS_HOME/exo-conf/portal/PORTAL_NAME/configuration.xml and you want to import the configuration file $AS_HOME/exo-conf/portal/PORTAL_NAME/mySubConfDir/myConfig.xml, you can do it by adding <import>mySubConfDir/myConfig.xml</import> to your configuration file.
The name of the configuration folder that is by default "exo-conf", can be changed thanks to the System property exo.conf.dir.name.
The search looks for a configuration file in each JAR/WAR available from the classpath using the current thread context classloader. During the search these configurations are added to a set. If the service was configured previously and the current JAR contains a new configuration of that service the latest (from the current JAR/WAR) will replace the previous one. The last one will be applied to the service during the services start phase.
Take care to have no dependencies between configurations from JAR files (/conf/portal/configuration.xml and /conf/configuration.xml) since we have no way to know in advance the loading order of those configurations. In other words, if you want to overload some configuration located in the file /conf/portal/configuration.xml of a given JAR file, you must not do it from the file /conf/portal/configuration.xml of another JAR file but from another configuration file loaded after configurations from JAR files /conf/portal/configuration.xml.
After the processing of all configurations available in system, the container will initialize it and start each service in order of the dependency injection (DI).
The user/developer should be careful when configuring the same service in different configuration files. It's recommended to configure a service in its own JAR only. Or, in case of a portal configuration, strictly reconfigure the services in portal WAR files or in an external configuration.
There are services that can be (or should be) configured more than one time. This depends on business logic of the service. A service may initialize the same resource (shared with other services) or may add a particular object to a set of objects (shared with other services too). In the first case, it's critical who will be the last, i.e. whose configuration will be used. In the second case, it's no matter who is the first and who is the last (if the parameter objects are independent).
The effective configuration of the StandaloneContainer, RootContainer and/or PortalContainer can be known thanks to the method getConfigurationXML() that is exposed through JMX at the container's level. This method will give you the effective configuration in XML format that has been really interpreted by the kernel. This could be helpful to understand how a given component or plugin has been initialized.
Since eXo JCR 1.12, we added a set of new features that have been designed to extend portal applications such as GateIn.
A ServletContextListener
called
org.exoplatform.container.web.PortalContainerConfigOwner
has been added in order to notify the application that a given web
application provides some configuration to the portal container, and
this configuration file is the file
WEB-INF/conf/configuration.xml available in the
web application itself.
If your war file contains some configuration to add to the
PortalContainer
simply add the following lines in your
web.xml file.
<?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> ... <!-- ================================================================== --> <!-- LISTENER --> <!-- ================================================================== --> <listener> <listener-class>org.exoplatform.container.web.PortalContainerConfigOwner</listener-class> </listener> ... </web-app>
A ServletContextListener
called
org.exoplatform.container.web.PortalContainerCreator
has been added in order to create the current portal containers that
have been registered. We assume that all the web applications have
already been loaded before calling
PortalContainerCreator.contextInitialized
[.]
In GateIn, the PortalContainerCreator
is
already managed by the file
starter.war/ear.
Now we can define precisely a portal container and its
dependencies and settings thanks to the
PortalContainerDefinition
that currently contains the
name of the portal container, the name of the rest context, the name
of the realm, the web application dependencies ordered by loading
priority (i.e. the first dependency must be loaded at first and so
on..) and the settings.
To be able to define a PortalContainerDefinition
,
we need to ensure first of all that a
PortalContainerConfig
has been defined at the
RootContainer
level, see an example below:
<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> <!-- Indicates whether the unregistered webapps have to be ignored --> <value-param> <name>ignore.unregistered.webapp</name> <value>true</value> </value-param> <!-- The default portal container definition --> <!-- It cans be used to avoid duplicating configuration --> <object-param> <name>default.portal.definition</name> <object type="org.exoplatform.container.definition.PortalContainerDefinition"> <!-- All the dependencies of the portal container ordered by loading priority --> <field name="dependencies"> <collection type="java.util.ArrayList"> <value> <string>foo</string> </value> <value> <string>foo2</string> </value> <value> <string>foo3</string> </value> </collection> </field> <!-- A map of settings tied to the default portal container --> <field name="settings"> <map type="java.util.HashMap"> <entry> <key> <string>foo5</string> </key> <value> <string>value</string> </value> </entry> <entry> <key> <string>string</string> </key> <value> <string>value0</string> </value> </entry> <entry> <key> <string>int</string> </key> <value> <int>100</int> </value> </entry> </map> </field> <!-- The path to the external properties file --> <field name="externalSettingsPath"> <string>classpath:/org/exoplatform/container/definition/default-settings.properties</string> </field> </object> </object-param> </init-params> </component>
Table 2.1. Descriptions of the fields of
PortalContainerConfig
default.portal.container (*) | The name of the default portal container. This field is optional. |
default.rest.context (*) | The name of the default rest
ServletContext . This field is optional. |
default.realm.name (*) | The name of the default realm. This field is optional. |
ignore.unregistered.webapp (*) | Indicates whether the unregistered webapps have to be
ignored. If a webapp has not been registered as a dependency
of any portal container, the application will use the value of
this parameter to know what to do:
|
default.portal.definition | The definition of the default portal container. This
field is optional. The expected type is
org.exoplatform.container.definition.PortalContainerDefinition
that is described below. Allow the parameters defined in this
default PortalContainerDefinition will be the
default values. |
All the value of the parameters marked with a (*) can be defined thanks to System properties like any values in configuration files but also thanks to variables loaded by the PropertyConfigurator. For example in GateIn by default, it would be all the variables defined in the file configuration.properties.
A new PortalContainerDefinition
can be defined at
the RootContainer
level thanks to an external plugin,
see an example below:
<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>myPortal</string> </field> <!-- The name of the context name of the rest web application --> <field name="restContextName"> <string>myRest</string> </field> <!-- The name of the realm --> <field name="realmName"> <string>my-domain</string> </field> <!-- All the dependencies of the portal container ordered by loading priority --> <field name="dependencies"> <collection type="java.util.ArrayList"> <value> <string>foo</string> </value> <value> <string>foo2</string> </value> <value> <string>foo3</string> </value> </collection> </field> <!-- A map of settings tied to the portal container --> <field name="settings"> <map type="java.util.HashMap"> <entry> <key> <string>foo</string> </key> <value> <string>value</string> </value> </entry> <entry> <key> <string>int</string> </key> <value> <int>10</int> </value> </entry> <entry> <key> <string>long</string> </key> <value> <long>10</long> </value> </entry> <entry> <key> <string>double</string> </key> <value> <double>10</double> </value> </entry> <entry> <key> <string>boolean</string> </key> <value> <boolean>true</boolean> </value> </entry> </map> </field> <!-- The path to the external properties file --> <field name="externalSettingsPath"> <string>classpath:/org/exoplatform/container/definition/settings.properties</string> </field> </object> </object-param> </init-params> </component-plugin> </external-component-plugins>
Table 2.2. Descriptions of the fields of a
PortalContainerDefinition
when it is used to define a
new portal container
name (*) | The name of the portal container. This field is mandatory . |
restContextName (*) | The name of the context name of the rest web
application. This field is optional. The default value will be
defined at the PortalContainerConfig
level. |
realmName (*) | The name of the realm. This field is optional. The
default value will be defined at the
PortalContainerConfig level. |
dependencies | All the dependencies of the portal container ordered by
loading priority. This field is optional. The default value
will be defined at the PortalContainerConfig
level. The dependencies are in fact the list of the context
names of the web applications from which the portal container
depends. This field is optional. The dependency order 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 less important than the
second element and so on. It is currently used
to:
|
settings | A java.util.Map of internal parameters
that we would like to tie the portal container. Those
parameters could have any type of value. This field is
optional. If some internal settings are defined at the
PortalContainerConfig level, the two maps of
settings will be merged. If a setting with the same name is
defined in both maps, it will keep the value defined at the
PortalContainerDefinition level. |
externalSettingsPath | The path of the external properties file to load as
default settings to the portal container. This field is
optional. If some external settings are defined at the
PortalContainerConfig level, the two maps of
settings will be merged. If a setting with the same name is
defined in both maps, it will keep the value defined at the
PortalContainerDefinition level. The external
properties files can be either of type "properties" or of type
"xml". The path will be interpreted as follows:
|
Table 2.3. Descriptions of the fields of a
PortalContainerDefinition
when it is used to define
the default portal container
name (*) | The name of the portal container. This field is
optional. The default portal name will be:
|
restContextName (*) | The name of the context name of the rest web
application. This field is optional. The default value wil
be:
|
realmName (*) | The name of the realm. This field is optional. The
default value wil be:
|
dependencies | All the dependencies of the portal container ordered by loading priority. This field is optional. If this field has a non empty value, it will be the default list of dependencies. |
settings | A java.util.Map of internal parameters
that we would like to tie the default portal container. Those
parameters could have any type of value. This field is
optional. |
externalSettingsPath | The path of the external properties file to load as
default settings to the default portal container. This field
is optional. The external properties files can be either of
type "properties" or of type "xml". The path will be
interpreted as follows:
|
All the value of the parameters marked with a (*) can be defined thanks to System properties like any values in configuration files but also thanks to variables loaded by the PropertyConfigurator. For example in GateIn by default, it would be all the variables defined in the file configuration.properties.
Internal and external settings are both optional, but if we give a non empty value for both the application will merge the settings. If the same setting name exists in both settings, we apply the following rules:
The value of the external setting is null, we ignore the value.
The value of the external setting is not
null and the value of the internal setting is
null, the final value will be the external
setting value that is of type String
.
Both values are not null
, we will have to
convert the external setting value into the target type which is
the type of the internal setting value, thanks to the static
method valueOf(String), the following
sub-rules are then applied:
The method cannot be found, the final value will be the
external setting value that is of type
String
.
The method can be found and the external setting value
is an empty String
, we ignore the external
setting value.
The method can be found and the external setting value
is not an empty String
but the method call
fails, we ignore the external setting value.
The method can be found and the external setting value
is not an empty String
and the method call
succeeds, the final value will be the external setting value
that is of type of the internal setting value.
We can inject the value of the portal container settings into the portal container configuration files thanks to the variables which name start with "portal.container.", so to get the value of a setting called "foo", just use the following syntax ${portal.container.foo}. You can also use internal variables, such as:
Table 2.4. Definition of the internal variables
portal.container.name | Gives the name of the current portal container. |
portal.container.rest | Gives the context name of the rest web application of the current portal container. |
portal.container.realm | Gives the realm name of the current portal container. |
You can find below an example of how to use the variables:
<configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.exoplatform.org/xml/ns/kernel_1_2.xsd http://www.exoplatform.org/xml/ns/kernel_1_2.xsd" xmlns="http://www.exoplatform.org/xml/ns/kernel_1_2.xsd"> <component> <type>org.exoplatform.container.TestPortalContainer$MyComponent</type> <init-params> <!-- The name of the portal container --> <value-param> <name>portal</name> <value>${portal.container.name}</value> </value-param> <!-- The name of the rest ServletContext --> <value-param> <name>rest</name> <value>${portal.container.rest}</value> </value-param> <!-- The name of the realm --> <value-param> <name>realm</name> <value>${portal.container.realm}</value> </value-param> <value-param> <name>foo</name> <value>${portal.container.foo}</value> </value-param> <value-param> <name>before foo after</name> <value>before ${portal.container.foo} after</value> </value-param> </init-params> </component> </configuration>
In the properties file corresponding to the external settings, you can reuse variables previously defined (in the external settings or in the internal settings) to create a new variable. In this case, the prefix "portal.container." is not needed, see an example below:
my-var1=value 1 my-var2=value 2 complex-value=${my-var1}-${my-var2}
In the external and internal settings, you can also use create
variables based on value of System paramaters. The System parameters
can either be defined at launch time or thanks to the
PropertyConfigurator
(see next section for more
details). See an example below:
temp-dir=${java.io.tmpdir}${file.separator}my-temp
However, for the internal settings, you can use System
parameters only to define settings of type
java.lang.String
.
It cans be also very usefull to define a generic variable in the settings of the default portal container, the value of this variable will change according to the current portal container. See below an example:
my-generic-var=value of the portal container "${name}"
If this variable is defined at the default portal container level, the value of this variable for a portal container called "foo" will be value of the portal container "foo".
It is possible to use component-plugin
elements
in order to dynamically change a PortalContainerDefinition. In the
example below, we add the dependency foo
to the default
portal container and to the portal containers called
foo1
and foo2
:
<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> <values-param> <name>apply.specific</name> <value>foo1</value> <value>foo2</value> </values-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>foo</string> </value> </collection> </field> </object> </object-param> </init-params> </component-plugin> </external-component-plugins>
Table 2.5. Descriptions of the fields of a
PortalContainerDefinitionChangePlugin
apply.all (*) | Indicates whether the changes have to be applied to all
the portal containers or not. The default value of this field
is false . This field is a
ValueParam and is not mandatory. |
apply.default (*) | Indicates whether the changes have to be applied to the
default portal container or not. The default value of this
field is false . This field is a
ValueParam and is not mandatory. |
apply.specific (*) | A set of specific portal container names to which we
want to apply the changes. This field is a
ValuesParam and is not mandatory. |
Rest of the expected parameters | The rest of the expected paramaters are
ObjectParam of type
PortalContainerDefinitionChange . Those
parameters are in fact the list of changes that we want to
apply to one or several portal containers. If the list of
changes is empty, the component plugin will be ignored. The
supported implementations of PortalContainerDefinitionChange
are described later in this section. |
All the value of the parameters marked with a (*) can be defined thanks to System properties like any values in configuration files but also thanks to variables loaded by the PropertyConfigurator. For example in GateIn by default, it would be all the variables defined in the file configuration.properties.
To identify the portal containers to which the changes have to be applied, we use the follwing algorithm:
The parameter apply.all
has been set to
true
. The corresponding changes will be applied to
all the portal containers. The other parameters will be
ignored.
The parameter apply.default
has been set to
true
and the parameter
apply.specific
is null
. The
corresponding changes will be applied to the default portal
container only.
The parameter apply.default
has been set to
true
and the parameter
apply.specific
is not null
. The
corresponding changes will be applied to the default portal
container and the given list of specific portal containers.
The parameter apply.default
has been set to
false
or has not been set and the parameter
apply.specific
is null
. The
corresponding changes will be applied to the default portal
container only.
The parameter apply.default
has been set to
false
or has not been set and the parameter
apply.specific
is not null
. The
corresponding changes will be applied to the given list of
specific portal containers.
The modifications that can be applied to a
PortalContainerDefinition
must be a class of type
PortalContainerDefinitionChange
. The product proposes
out of the box some implementations that we describe in the next sub
sections.
This modification adds a list of dependencies at the end of
the list of dependencies defined into the
PortalContainerDefinition
. The full qualified name
is
org.exoplatform.container.definition.PortalContainerDefinitionChange$AddDependencies.
Table 2.6. Descriptions of the fields of an
AddDependencies
dependencies | A list of String corresponding to the list of name of the dependencies to add. If the value of this field is empty, the change will be ignored. |
See an example below, that will add foo
at
the end of the dependency list of the default portal
container:
<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>foo</string> </value> </collection> </field> </object> </object-param> </init-params> </component-plugin> </external-component-plugins>
This modification adds a list of dependencies before a given
target dependency defined into the list of dependencies of the
PortalContainerDefinition
. The full qualified name
is
org.exoplatform.container.definition.PortalContainerDefinitionChange$AddDependenciesBefore.
Table 2.7. Descriptions of the fields of an
AddDependenciesBefore
dependencies | A list of String corresponding to the list of name of the dependencies to add. If the value of this field is empty, the change will be ignored. |
target | The name of the dependency before which we would
like to add the new dependencies. If this field is
null or the target dependency cannot be
found in the list of dependencies defined into the
PortalContainerDefinition , the new
dependencies will be added in first position to the
list. |
See an example below, that will add foo
before foo2
in the dependency list of the default
portal container:
<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$AddDependenciesBefore"> <!-- The list of name of the dependencies to add --> <field name="dependencies"> <collection type="java.util.ArrayList"> <value> <string>foo</string> </value> </collection> </field> <!-- The name of the target dependency --> <field name="target"> <string>foo2</string> </field> </object> </object-param> </init-params> </component-plugin> </external-component-plugins>
This modification adds a list of dependencies after a given
target dependency defined into the list of dependencies of the
PortalContainerDefinition
. The full qualified name
is
org.exoplatform.container.definition.PortalContainerDefinitionChange$AddDependenciesAfter.
Table 2.8. Descriptions of the fields of an
AddDependenciesAfter
dependencies | A list of String corresponding to the list of name of the dependencies to add. If the value of this field is empty, the change will be ignored. |
target | The name of the dependency after which we would
like to add the new dependencies. If this field is
null or the target dependency cannot be
found in the list of dependencies defined into the
PortalContainerDefinition , the new
dependencies will be added in last position to the
list. |
See an example below, that will add foo
after
foo2
in the dependency list of the default portal
container:
<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$AddDependenciesAfter"> <!-- The list of name of the dependencies to add --> <field name="dependencies"> <collection type="java.util.ArrayList"> <value> <string>foo</string> </value> </collection> </field> <!-- The name of the target dependency --> <field name="target"> <string>foo2</string> </field> </object> </object-param> </init-params> </component-plugin> </external-component-plugins>
This modification adds new settings to a
PortalContainerDefinition
. The full qualified name
is
org.exoplatform.container.definition.PortalContainerDefinitionChange$AddSettings.
Table 2.9. Descriptions of the fields of an
AddSettings
settings | A map of <String, Object> corresponding to the settings to add. If the value of this field is empty, the change will be ignored. |
See an example below, that will add the settings
string
and stringX
to the settings
of the default portal container:
<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$AddSettings"> <!-- The settings to add to the to the portal containers --> <field name="settings"> <map type="java.util.HashMap"> <entry> <key> <string>string</string> </key> <value> <string>value1</string> </value> </entry> <entry> <key> <string>stringX</string> </key> <value> <string>value1</string> </value> </entry> </map> </field> </object> </object-param> </init-params> </component-plugin> </external-component-plugins>
It is possible to use component-plugin
elements
in order to dynamically disable one or several portal containers. In
the example below, we disable the portal container named
foo
:
<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>Disable a PortalContainer</name> <!-- The name of the method to call on the PortalContainerConfig in order to register the changes on the PortalContainerDefinitions --> <set-method>registerDisablePlugin</set-method> <!-- The full qualified name of the PortalContainerDefinitionDisablePlugin --> <type>org.exoplatform.container.definition.PortalContainerDefinitionDisablePlugin</type> <init-params> <!-- The list of the name of the portal containers to disable --> <values-param> <name>names</name> <value>foo</value> </values-param> </init-params> </component-plugin> </external-component-plugins>
Table 2.10. Descriptions of the fields of a
PortalContainerDefinitionDisablePlugin
names (*) | The list of the name of the portal containers to disable. |
All the value of the parameters marked with a (*) can be defined thanks to System properties like any values in configuration files but also thanks to variables loaded by the PropertyConfigurator. For example in GateIn by default, it would be all the variables defined in the file configuration.properties.
To prevent any accesses to a web application corresponding to
PortalContainer
that has been disabled, you need to
make sure that the following Http Filter (or a sub class of it) has
been added to your web.xml in first position as below:
<filter> <filter-name>PortalContainerFilter</filter-name> <filter-class>org.exoplatform.container.web.PortalContainerFilter</filter-class> </filter> <filter-mapping> <filter-name>PortalContainerFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
It is only possible to disable a portal container when at least one PortalContainerDefinition has been registered.
A new property configurator service has been developed for taking care of configuring system properties from the inline kernel configuration or from specified property files.
The services is scoped at the root container level because it is used by all the services in the different portal containers in the application runtime.
The properties init param takes a property declared to configure various properties.
<component> <key>PropertyManagerConfigurator</key> <type>org.exoplatform.container.PropertyConfigurator</type> <init-params> <properties-param> <name>properties</name> <property name="foo" value="bar"/> </properties-param> </init-params> </component>
The properties URL init param allow to load an external file by
specifying its URL. Both property and XML format are supported, see the
javadoc of the java.util.Properties
class for more information. When a property file is loaded the various
property declarations are loaded in the order in which the properties
are declared sequentially in the file.
<component> <key>PropertyManagerConfigurator</key> <type>org.exoplatform.container.PropertyConfigurator</type> <init-params> <value-param> <name>properties.url</name> <value>classpath:configuration.properties</value> </value-param> </init-params> </component>
In the properties file corresponding to the external properties, you can reuse variables before defining to create a new variable. In this case, the prefix "portal.container." is not needed, see an example below:
my-var1=value 1 my-var2=value 2 complex-value=${my-var1}-${my-var2}
All the variables that we described in the previous sections can be defined thanks to 2 possible syntaxes which are ${variable-name} or ${variable-name:default-value}. The first syntax doesn't define any default value so if the variable has not be set the value will be ${variable-name} to indicate that it could not be resolved. The second syntax allows you to define the default value after the semi colon so if the variable has not be set the value will be the given default value.
The kernel configuration is able to handle configuration profiles at runtime (as opposed to packaging time).
An active profile list is obtained during the boot of the root container and is composed of the system property exo.profiles sliced according the "," delimiter and also a server specific profile value (tomcat for tomcat, jboss for jboss, etc...).
# runs GateIn on Tomcat with the profiles tomcat and foo sh gatein.sh -Dexo.profiles=foo # runs GateIn on JBoss with the profiles jboss, foo and bar sh run.sh -Dexo.profiles=foo,bar
Profiles are configured in the configuration files of the eXo kernel.
Profile activation occurs at XML to configuration object unmarshalling time. It is based on an "profile" attribute that is present on some of the XML element of the configuration files. To enable this, the kernel configuration schema has been upgraded to kernel_1_1.xsd. The configuration is based on the following rules:
Any kernel element with the no profiles attribute will create a configuration object
Any kernel element having a profiles attribute containing at least one of the active profiles will create a configuration object
Any kernel element having a profiles attribute matching none of the active profile will not create a configuration object
Resolution of duplicates (such as two components with same type) is left up to the kernel
A configuration element is profiles capable when it carries a profiles element.
The component element declares a component when activated. It will shadow any element with the same key declared before in the same configuration file:
<component> <key>Component</key> <type>Component</type> </component> <component profiles="foo"> <key>Component</key> <type>FooComponent</type> </component>
The component-plugin element is used to dynamically extend the configuration of a given component. Thanks to the profiles the component-plugins could be enabled or disabled:
<external-component-plugins> <target-component>Component</target-component> <component-plugin profiles="foo"> <name>foo</name> <set-method>addPlugin</set-method> <type>type</type> <init-params> <value-param> <name>param</name> <value>empty</value> </value-param> </init-params> </component-plugin> </external-component-plugins>
The import element imports a referenced configuration file when activated:
<import>empty</import> <import profiles="foo">foo</import> <import profiles="bar">bar</import>
The init param element configures the parameter argument of the construction of a component service:
<component> <key>Component</key> <type>ComponentImpl</type> <init-params> <value-param> <name>param</name> <value>empty</value> </value-param> <value-param profiles="foo"> <name>param</name> <value>foo</value> </value-param> <value-param profiles="bar"> <name>param</name> <value>bar</value> </value-param> </init-params> </component>
The value collection element configures one of the value of collection data:
<object type="org.exoplatform.container.configuration.ConfigParam"> <field name="role"> <collection type="java.util.ArrayList"> <value><string>manager</string></value> <value profiles="foo"><string>foo_manager</string></value> <value profiles="foo,bar"><string>foo_bar_manager</string></value> </collection> </field> </object>
The field configuration element configures the field of an object:
<object-param> <name>test.configuration</name> <object type="org.exoplatform.container.configuration.ConfigParam"> <field name="role"> <collection type="java.util.ArrayList"> <value><string>manager</string></value> </collection> </field> <field name="role" profiles="foo,bar"> <collection type="java.util.ArrayList"> <value><string>foo_bar_manager</string></value> </collection> </field> <field name="role" profiles="foo"> <collection type="java.util.ArrayList"> <value><string>foo_manager</string></value> </collection> </field> </object> </object-param>
The component request life cycle is an interface that defines a contract for a component for being involved into a request:
public interface ComponentRequestLifecycle { /** * Start a request. * @param container the related container */ void startRequest(ExoContainer container); /** * Ends a request. * @param container the related container */ void endRequest(ExoContainer container); }
The container passed is the container to which the component is related. This contract is often used to setup a thread local based context that will be demarcated by a request.
For instance in the GateIn portal context, a component request life cycle is triggered for user requests. Another example is the initial data import in GateIn that demarcates using callbacks made to that interface.
The RequestLifeCycle
class has several statics
methods that are used to schedule the component request life cycle of
components. Its main responsability is to perform scheduling while
respecting the constraint to execute the request life cycle of a
component only once even if it can be scheduled several times.
RequestLifeCycle.begin(component); try { // Do something } finally { RequestLifeCycle.end(); }
Scheduling a container triggers the component request life cyle
of all the components that implement the interface
ComponentRequestLifeCycle
. If one of the component has
already been scheduled before and then that component will not be
scheduled again. When the local value is true, then the looked
components will be those of the container, when it is false then the
scheduler will also look at the components in the ancestor
containers.
RequestLifeCycle.begin(container, local); try { // Do something } finally { RequestLifeCycle.end(); }
Each portal request triggers the life cycle of the associated portal container.
A thread context holder defines a component that holds variables of type ThreadLocal whose value is required by the component to work normally and cannot be recovered. This component is mainly used when we want to do a task asynchronously, in that case to ensure that the task will be executed in the same conditions as if it would be executed synchronously we need to transfer the thread context from the original thread to the executor thread.
public interface ThreadContextHolder { /** * Gives the value corresponding to the context of the thread * @return a new instance of {@link ThreadContext} if there are some * valuable {@link ThreadLocal} variables to share otherwise <code>null</code> * is expected */ ThreadContext getThreadContext(); }
This interface must be used with caution, only the most important components that have ThreadLocal variables whose value cannot be recovered should implement this interface.
To be able to transfer the values of all the ThreadLocal variables (provided thanks to a ThreadContext instance) of all the registered components of type ThreadContextHolder, you can simply use a thread context handler as below:
//////////////////////////////////////////////////////// // Steps to be done in the context of the initial thread //////////////////////////////////////////////////////// // Create a new instance of ThreadContextHandler for a given ExoContainer ThreadContextHandler handler = new ThreadContextHandler(container); // Stores into memory the current values of all the Thread Local variables // of all the registered ThreadContextHolder of the eXo container. handler.store(); ... //////////////////////////////////////////////////////// // Steps to be done in the context of the executor thread //////////////////////////////////////////////////////// try { // Pushes values stored into memory into all the Thread Local variables // of all the registered ThreadContextHolder of the eXo Container handler.push(); ... } finally { // Restores all the Thread Local variables of all the registered ThreadContextHolder // of the eXo Container handler.restore(); }
The services are not responsible for the instantiation of the components on which they depend.
This architecture provides a loosely coupled design where the implementation of dependant services can be transparently exchanged.
This pattern has several names:
Hollywood principle: "don't call me, I will call you"
Inversion of Control
Dependency injection
Don't let the object create itself the instances of the object that it references. This job is delegated to the container (assembler in the picture).
There are two ways to inject a dependency:
Using a constructor:
public ServiceA(ServiceB serviceB)
Using setter methods:
public void setServiceB(ServiceB serviceB)
When a client service can not be stored in the container then the service locator pattern is used:
public ServiceA(){ this.serviceB =Container.getSInstance().getService(ServiceB.class); }
The container package is responsible of building a hierarchy of containers. Each service will then be registered in one container or the other according to the XML configuration file it is defined in. It is important to understand that there can be several PortalContainer instances that all are children of the RootContainer.
The behavior of the hierarchy is similar to a class loader one, hence when you will lookup a service that depends on another one, the container will look for it in the current container and if it cannot be found, then it will look in the parent container. That way you can load all the reusable business logic components in the same container (here the RootContainer) and differentiate the service implementation from one portal instance to the other by just loading different service implementations in two sibling PortalContainers.
Therefore, if you look at the Portal Container as a service repository for all the business logic in a portal instance, then you understand why several PortalContainers allows you to manage several portals (each one deployed as a single war) in the same server by just changing XML configuration files.
The default configuration XML files are packaged in the service jar. There are three configuration.xml files, one for each container type. In that XML file, we define the list of services and their init parameters that will be loaded in the corresponding container.
As there can be several portal container instances per JVM. it is important to be able to configure the loaded services per instance. Therefore all the default configuration files located in the service impl jar can be overridden from the portal war. For more information refer to Service Configuration for Beginners.
After deploying you find the configuration.xml file in webapps/portal/WEB-INF/conf Use component registration tags. Let's look at the key tag that defines the interface and the type tag that defines the implementation. Note that the key tag is not mandatory, but it improves performance.
<!-- Portlet container hooks --> <component> <key>org.exoplatform.services.portletcontainer.persistence.PortletPreferencesPersister</key> <type>org.exoplatform.services.portal.impl.PortletPreferencesPersisterImpl</type> </component>
Register plugins that can act as listeners or external plugin to bundle some plugin classes in other jar modules. The usual example is the hibernate service to which we can add hbm mapping files even if those are deployed in an other maven artifact.
<external-component-plugins> <target-component>org.exoplatform.services.database.HibernateService</target-component> <component-plugin> <name>add.hibernate.mapping</name> <set-method>addPlugin</set-method> <type>org.exoplatform.services.database.impl.AddHibernateMappingPlugin</type> <init-params> <values-param> <name>hibernate.mapping</name> <value>org/exoplatform/services/portal/impl/PortalConfigData.hbm.xml</value> <value>org/exoplatform/services/portal/impl/PageData.hbm.xml</value> <value>org/exoplatform/services/portal/impl/NodeNavigationData.hbm.xml</value> </values-param> </init-params> </component-plugin> </external-component-plugins>
In that sample we target the HibernateService and we will call its addPlugin() method with an argument of the type AddHibernateMappingPlugin. That object will first have been filled with the init parameters.
Therefore, it is possible to define services that will be able to receive plugins without implementing any framework interface.
Another example of use is the case of listeners as in the following code where a listener is added to the OrganisationService and will be called each time a new user is created:
<external-component-plugins> <target-component>org.exoplatform.services.organization.OrganizationService</target-component> <component-plugin> <name>portal.new.user.event.listener</name> <set-method>addListenerPlugin</set-method> <type>org.exoplatform.services.portal.impl.PortalUserEventListenerImpl</type> <description>this listener create the portal configuration for the new user</description> <init-params> <object-param> <name>configuration</name> <description>description</description> <object type="org.exoplatform.services.portal.impl.NewPortalConfig"> <field name="predefinedUser"> <collection type="java.util.HashSet"> <value><string>admin</string></value> <value><string>exo</string></value> <value><string>company</string></value> <value><string>community</string></value> <value><string>portal</string></value> <value><string>exotest</string></value> </collection> </field> <field name="templateUser"><string>template</string></field> <field name="templateLocation"><string>war:/conf/users</string></field> </object> </object-param> </init-params> </component-plugin> ...
In the previous XML configuration, we refer the organization service and we will call its method addListenerPlugin with an object of type PortalUserEventListenerImpl. Each time a new user will be created (apart the predefined ones in the list above) methods of the PortalUserEventListenerImpl will be called by the service.
As you can see, there are several types of init parameters, from a simple value param which binds a key with a value to a more complex object mapping that fills a JavaBean with the info defined in the XML.
Many other examples exist such as for the Scheduler Service where you can add a job with a simple XML configuration or the JCR Service where you can add a NodeType from your own configuration.xml file.
When the RootContainer is starting the configuration retrieval looks for configuration files in each jar available from the classpath at jar path /conf/portal/configuration.xml and from each war at path /WEB-INF/conf/configuration.xml. These configurations are added to a set. If a component was configured in a previous jar and the current jar contains a new configuration of that component the latest (from the current jar) will replace the previous configuration.
After the processing of all configurations available on the system the container will initialize it and start each component in order of the dependency injection (DI).
So, in general the user/developer should be careful when configuring the same components in different configuration files. It's recommended to configure service in its own jar only. Or, in case of a portal configuration, strictly reconfigure the component in portal files.
But, there are components that can be (or should be) configured more than one time. This depends on the business logic of the component. A component may initialize the same resource (shared with other players) or may add a particular object to a set of objects (shared with other players too). In the first case it's critical who will be the last, i.e. whose configuration will be used. In second case it doesn't matter who is the first and who is the last (if the parameter objects are independent).
In case of problems with configuration of component it's important to know from which jar/war it comes. For that purpose user/developer can set JVM system property org.exoplatform.container.configuration.debug, in command line:
java -Dorg.exoplatform.container.configuration.debug ...
With that property container configuration manager will report configuration adding process to the standard output (System.out).
...... Add configuration jar:file:/D:/Projects/eXo/dev/exo-working/exo-tomcat/lib/exo.kernel.container-trunk.jar!/conf/portal/configuration.xml Add configuration jar:file:/D:/Projects/eXo/dev/exo-working/exo-tomcat/lib/exo.kernel.component.cache-trunk.jar!/conf/portal/configuration.xml Add configuration jndi:/localhost/portal/WEB-INF/conf/configuration.xml import jndi:/localhost/portal/WEB-INF/conf/common/common-configuration.xml import jndi:/localhost/portal/WEB-INF/conf/database/database-configuration.xml import jndi:/localhost/portal/WEB-INF/conf/ecm/jcr-component-plugins-configuration.xml import jndi:/localhost/portal/WEB-INF/conf/jcr/jcr-configuration.xml ......
Since kernel version 2.0.6 it is possible to setup order of loading for ComponentPlugin. Use the ' priority' tag to define plugin's load priority. By default all plugins get priority '0'; they will be loaded in the container's natural way. If you want one plugin to be loaded later than the others then just set priority for it higher than zero.
Simple example of fragment of a configuration.xml.
... <component> <type>org.exoplatform.services.Component1</type> </component> <external-component-plugins> <target-component>org.exoplatform.services.Component1</target-component> <component-plugin> <name>Plugin1</name> <set-method>addPlugin</set-method> <type>org.exoplatform.services.plugins.Plugin1</type> <description>description</description> <priority>1</priority> </component-plugin> <component-plugin> <name>Plugin2</name> <set-method>addPlugin</set-method> <type>org.exoplatform.services.plugins.Plugin2</type> <description>description</description> <priority>2</priority> </component-plugin> </external-component-plugins> <external-component-plugins> <target-component>org.exoplatform.services.Component1</target-component> <component-plugin> <name>Plugin3</name> <set-method>addPlugin</set-method> <type>org.exoplatform.services.plugins.Plugin3</type> <description>description</description> </component-plugin> </external-component-plugins> ...
In the above example plugin 'Plugin3' will be loaded first because it has the default priority '0'. Then, plugin 'Plugin1' will be loaded and last one is plugin 'Plugin2'.
This section will first describe how the ListenerService works and then it will show you how to configure the ListenerService.
Related documents
Inside eXo, an event mechanism allows to trigger and listen to events under specific conditions. This mechanism is used in several places in eXo such as login/logout time.
Listeners must be subclasses of org.exoplatform.services.listener.Listener registered by the ListenerService.
To register a listener, you need to call the addListener() method.
/** * This method is used to register a {@link Listener} to the events of the same * name. It is similar to addListener(listener.getName(), listener) * * @param listener the listener to notify any time an even of the same name is * triggered */ public void addListener(Listener listener) /** * This method is used to register a new {@link Listener}. Any time an * event of the given event name has been triggered, the {@link Listener} will be * notified. * This method will: * <ol> * <li>Check if it exists a list of listeners that have been registered for the * given event name, create a new list if no list exists</li> * <li>Add the listener to the list</li> * </ol> * @param eventName The name of the event to listen to * @param listener The Listener to notify any time the event with the given * name is triggered */ public void addListener(String eventName, Listener listener)
By convention, we use the listener name as the name of the event to listen to.
To trigger an event, an application can call one of the broadcast() methods of ListenerService.
/** * This method is used to broadcast an event. This method should: 1. Check if * there is a list of listener that listen to the event name. 2. If there is a * list of listener, create the event object with the given name , source and * data 3. For each listener in the listener list, invoke the method * onEvent(Event) * * @param <S> The type of the source that broadcast the event * @param <D> The type of the data that the source object is working on * @param name The name of the event * @param source The source object instance * @param data The data object instance * @throws Exception */ public <S, D> void broadcast(String name, S source, D data) throws Exception { ... } /** * This method is used when a developer want to implement his own event object * and broadcast the event. The method should: 1. Check if there is a list of * listener that listen to the event name. 2. If there is a list of the * listener, For each listener in the listener list, invoke the method * onEvent(Event) * * @param <T> The type of the event object, the type of the event object has * to be extended from the Event type * @param event The event instance * @throws Exception */ public <T extends Event> void broadcast(T event) throws Exception { ... }
The boadcast() methods retrieve the name of the event and find the registered listeners with the same name and call the method onEvent() on each listener found.
Each listener is a class that extends org.exoplatform.services.listener.Listener, as you can see below:
public abstract class Listener<S, D> extends BaseComponentPlugin { /** * This method should be invoked when an event with the same name is * broadcasted */ public abstract void onEvent(Event<S, D> event) throws Exception; }
As you can see we use generics to limit the source of the event to the type 'S' and the data of the event to the type 'D', so we expect that listeners implement the method onEvent() with the corresponding types
Each listener is also a ComponentPlugin with a name and a description, in other words, the name of the listener will be the name given in the configuration file, for more details see the next section.
public interface ComponentPlugin { public String getName(); public void setName(String name); public String getDescription(); public void setDescription(String description); }
Some events may take a lot of time and you don't necessary need to
process them immediately, in that particular case, you can decide to
make your listener asynchronous. To do so it's very simple, just mark
your Listener implementation with the annotation
@Asynchronous
.
@Asynchronous public class AsynchListenerWithException<S,D> extends Listener<S,D> { @Override public void onEvent(Event<S,D> event) throws Exception { // some expensive operation } }
Now, our AsynchListener will be executed in separate thread thanks
to an ExecutorService
.
By default, the pool size of the
ExecutoreService
is 1, you can change it by
configuration as next:
<component> <key>org.exoplatform.services.listener.ListenerService</key> <type>org.exoplatform.services.listener.ListenerService</type> <init-params> <value-param> <name>asynchPoolSize</name> <value>5</value> </value-param> </init-params> </component>
All listeners are in fact a ComponentPlugin so it must be configured as below:
<?xml version="1.0" encoding="ISO-8859-1"?> <configuration> ... <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>${name-of-the-target-event}</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>${the-FQN-of-the-listener}</type> </component-plugin> </external-component-plugins> </configuration>
The org.exoplatform.services.security.ConversationRegistry uses the ListenerService to notify that a user has just signed in or just left the application. For example, when a new user signs in, the following code is called:
listenerService.broadcast("exo.core.security.ConversationRegistry.register", this, state);
This code will in fact create a new Event which name is "exo.core.security.ConversationRegistry.register", which source is the current instance of ConversationRegistry and which data is the given state. The ListenerService will call the method onEvent(Event<ConversationRegistry, ConversationState> event) on all the listeners which name is "exo.core.security.ConversationRegistry.register".
In the example below, we define a Listener that will listen the event "exo.core.security.ConversationRegistry.register".
<?xml version="1.0" encoding="ISO-8859-1"?> <configuration> ... <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>exo.core.security.ConversationRegistry.register</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.forum.service.AuthenticationLoginListener</type> </component-plugin> </external-component-plugins> </configuration> ...
Initial Context Binder is responsible for binding references at runtime, persisting in file and automatically rebinding. Java temp directory is used to persist references in bind-references.xml file by default. In case when need to definde special file it can be done by add parameter to InitialContextInitializer configuration.
Service provide methods for binding reference:
public void bind(String bindName, String className, String factory, String factoryLocation, Map<String, String> refAddr) throws NamingException, FileNotFoundException, XMLStreamExcept
bindName - name of binding
className - the fully-qualified name of the class of the object to which this Reference refers
factory - the name of the factory class for creating an instance of the object to which this Reference refers
factoryLocation - the location of the factory class
refAddr - object's properties map
public void bind(String bindName, Reference ref) throws NamingException, FileNotFoundException, XMLStreamExcept
Returns reference associated with defined name:
public Reference getReference(String bindName)
Unbind the Reference with defined name:
public void unbind(String bindName) throws NamingException, FileNotFoundException, XMLStreamException
Job scheduler defines a job to execute a given number of times during a given period. It is a service that is in charge of unattended background executions, commonly known for historical reasons as batch processing. It is used to create and run jobs automatically and continuously, to schedule event-driven jobs and reports.
Job Scheduler Service is widely used in many eXo products such as Social, DMS, WCM, eXo Knowledge and eXo Collaboration.
In eXo products, Job Schedulers are used to do some tasks as below:
Automatically send notification, such as task/event reminder in the Calendar application of eXo Collaboration.
Automatically save chat messages from Openfire Server to History in the Chat application of eXo Collaboration.
Inactivate topics in the Forum application of eXo Knowledge.
Calculate the number of active and online users in the Forum application of eXo Knowledge.
Automatically collect RSS items from various RSS resources to post to the activity stream of users and spaces in eXo Social.
Automatically send Newsletters to users in WCM.
Also, it is used in Schedule lifecycle in DMS.
By using Job Scheduler Service in eXo kernel, many kinds of job can be configured to run, such as, addPeriodJob, addCronJob, addGlobalJobListener, addJobListener and many more. Just write a job (a class implements Job interface of quartz library and configures plug-in for JobSchedulerService and you're done.
Jobs are scheduled to run when a given Trigger occurs. Triggers can be created with nearly any combination of the following directives:
at a certain time of day (to the millisecond)
on certain days of the week
on certain days of the month
on certain days of the year
not on certain days listed within a registered Calendar (such as business holidays)
repeated a specific number of times
repeated until a specific time/date
repeated indefinitely
repeated with a delay interval
Jobs are given names by their creator and can also be organized into named groups. Triggers may also be given names and placed into groups, in order to easily organize them within the scheduler. Jobs can be added to the scheduler once, but registered with multiple Triggers. Within a J2EE environment, Jobs can perform their work as part of a distributed (XA) transaction.
(Source: quartz-scheduler.org)
Kernel leverages Quartz for its scheduler
service and wraps org.quartz.Scheduler
in
org.exoplatform.services.scheduler.impl.QuartzSheduler
for easier service wiring and configuration like any other services. To
work with Quartz in Kernel, you will mostly work with
org.exoplatform.services.scheduler.JobSchedulerService
(implemented by
org.exoplatform.services.scheduler.impl.JobSchedulerServiceImpl
.
To use JobSchedulerService
, you can
configure it as a component in the configuration.xml. Because
JobSchedulerService
requires
QuartzSheduler
and
QueueTasks
, you also have to configure these two
components.
<?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_2.xsd http://www.exoplatform.org/xml/ns/kernel_1_2.xsd" xmlns="http://www.exoplatform.org/xml/ns/kernel_1_2.xsd"> <component> <type>org.exoplatform.services.scheduler.impl.QuartzSheduler</type> </component> <component> <type>org.exoplatform.services.scheduler.QueueTasks</type> </component> <component> <key>org.exoplatform.services.scheduler.JobSchedulerService</key> <type>org.exoplatform.services.scheduler.impl.JobSchedulerServiceImpl</type> </component> </configuration>
You can download the project code from here
Work with JobSchedulerService
by
creating a sample project and use GateIn-3.1.0-GA for testing.
Firstly, create a project by using maven archetype plugin:
mvn archetype:generate
For project type: select maven-archetype-quickstart
For groupId: select org.exoplatform.samples
For artifactId: select exo.samples.scheduler
For version: select 1.0.0-SNAPSHOT
For package: select org.exoplatform.samples.scheduler
Edit the pom.xml as follows:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <artifactId>exo.portal.parent</artifactId> <groupId>org.exoplatform.portal</groupId> <version>3.1.0-GA</version> </parent> <groupId>org.exoplatform.samples</groupId> <artifactId>exo.samples.scheduler</artifactId> <version>1.0.0-SNAPSHOT</version> <name>eXo Samples For Scheduler</name> <description>eXo Samples Code For Scheduler</description> </project>
Generate an eclipse project by using maven eclipse plugin and then import into eclipse:
mvn eclipse:eclipse
eXo Kernel makes it easier to work with job scheduler service. All you need is just to define your "job" class to be performed by implementing org.quartz.Job interface and add configuration for it.
To define a job, do as follows:
Define your job to be performed. For example, the job DumbJob is defined as follows:
package org.exoplatform.samples.scheduler.jobs; import org.exoplatform.services.log.ExoLogger; import org.exoplatform.services.log.Log; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; /** * DumbJob for executing a defined dumb job. */ public class DumbJob implements Job { /** * The logger */ private static final Log LOG = ExoLogger.getLogger(DumbJob.class); /** * The job of the DumbJob will be done by executing this method. * * @param context * @throws JobExecutionException */ public void execute(JobExecutionContext context) throws JobExecutionException { LOG.info("DumbJob is executing..."); } }
All jobs are required to implement the method execute from org.quartz.Job interface. This method will be called whenever a job is performed. With DumbJob, you just use logging to see that it will work. By looking at the terminal, you will see the the log message: "DumbJob is executing..."
After defining the "job", the only next step is to configure it by using external-component-plugin configuration for org.exoplatform.services.scheduler.JobSchedulerService. You can use these methods below for setting component plugin:
public void addPeriodJob(ComponentPlugin plugin) throws Exception;
The component plugin for this method must be the type of org.exoplatform.services.scheduler.PeriodJob. This type of job is used to perform actions that are executed in a period of time. You have to define when this job is performed, when it ends, when it performs the first action, how many times it is executed and the period of time to perform the action. See the configuration sample below to understand more clearly:
<external-component-plugins> <target-component>org.exoplatform.services.scheduler.JobSchedulerService</target-component> <component-plugin> <name>PeriodJob Plugin</name> <set-method>addPeriodJob</set-method> <type>org.exoplatform.services.scheduler.PeriodJob</type> <description>period job configuration</description> <init-params> <properties-param> <name>job.info</name> <description>dumb job executed periodically</description> <property name="jobName" value="DumbJob"/> <property name="groupName" value="DumbJobGroup"/> <property name="job" value="org.exoplatform.samples.scheduler.jobs.DumbJob"/> <property name="repeatCount" value="0"/> <property name="period" value="60000"/> <property name="startTime" value="+45"/> <property name="endTime" value=""/> </properties-param> </init-params> </component-plugin> </external-component-plugins>
public void addCronJob(ComponentPlugin plugin) throws Exception;
The component plugin for this method must be the type of org.exoplatform.services.scheduler.CronJob. This type of job is used to perform actions at specified time with Unix 'cron-like' definitions. The plugin uses "expression" field for specifying the 'cron-like' definitions to execute the job. This is considered as the most powerful and flexible job to define when it will execute. For example, at 12pm every day => "0 0 12 * * ?"; or at 10:15am every Monday, Tuesday, Wednesday, Thursday and Friday => "0 15 10 ? * MON-FRI". To see more about Cron expression, please refer to this article:
See the configuration sample below to understand more clearly:
<external-component-plugins> <target-component>org.exoplatform.services.scheduler.JobSchedulerService</target-component> <component-plugin> <name>CronJob Plugin</name> <set-method>addCronJob</set-method> <type>org.exoplatform.services.scheduler.CronJob</type> <description>cron job configuration</description> <init-params> <properties-param> <name>cronjob.info</name> <description>dumb job executed by cron expression</description> <property name="jobName" value="DumbJob"/> <property name="groupName" value="DumbJobGroup"/> <property name="job" value="org.exoplatform.samples.scheduler.jobs.DumbJob"/> <!-- The job will be performed at 10:15am every day --> <property name="expression" value="0 15 10 * * ?"/> </properties-param> </init-params> </component-plugin> </external-component-plugins>
public void addGlobalJobListener(ComponentPlugin plugin) throws Exception;
public void addJobListener(ComponentPlugin plugin) throws Exception;
The component plugin for two methods above must be the type of org.quartz.JobListener. This job listener is used so that it will be informed when a org.quartz.JobDetail executes.
public void addGlobalTriggerListener(ComponentPlugin plugin) throws Exception;
public void addTriggerListener(ComponentPlugin plugin) throws Exception;
The component plugin for two methods above must be the type of org.quartz.TriggerListener. This trigger listener is used so that it will be informed when a org.quartz.Trigger fires.
Create conf.portal package in your sample project. Add the configuration.xml file with the content as follows:
<?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_2.xsd http://www.exoplatform.org/xml/ns/kernel_1_2.xsd" xmlns="http://www.exoplatform.org/xml/ns/kernel_1_2.xsd"> <component> <type>org.exoplatform.services.scheduler.impl.QuartzSheduler</type> </component> <component> <type>org.exoplatform.services.scheduler.QueueTasks</type> </component> <component> <key>org.exoplatform.services.scheduler.JobSchedulerService</key> <type>org.exoplatform.services.scheduler.impl.JobSchedulerServiceImpl</type> </component> <external-component-plugins> <target-component>org.exoplatform.services.scheduler.JobSchedulerService</target-component> <component-plugin> <name>PeriodJob Plugin</name> <set-method>addPeriodJob</set-method> <type>org.exoplatform.services.scheduler.PeriodJob</type> <description>period job configuration</description> <init-params> <properties-param> <name>job.info</name> <description>dumb job executed periodically</description> <property name="jobName" value="DumbJob"/> <property name="groupName" value="DumbJobGroup"/> <property name="job" value="org.exoplatform.samples.scheduler.jobs.DumbJob"/> <property name="repeatCount" value="0"/> <property name="period" value="60000"/> <property name="startTime" value="+45"/> <property name="endTime" value=""/> </properties-param> </init-params> </component-plugin> </external-component-plugins> </configuration>
mvn clean install the project. Copy .jar file to lib in tomcat bundled with GateIn-3.1.0-GA. Run bin/gatein.sh to see the DumbJob to be executed on the terminal when portal containers are initialized. Please look at the terminal to see the log message of DumbJob.
From now on, you can easily create any job to be executed in GateIn's portal by defining your job and configuring it.
To further understand about Job Scheduler, you can refer the following links:
This section will provide you all the basic knowledge about eXo Cache, from basic concepts to advanced concepts, sample codes, and more.
All applications on the top of eXo JCR that need a cache, can rely
on an org.exoplatform.services.cache.ExoCache
instance that
is managed by the
org.exoplatform.services.cache.CacheService
. The main
implementation of this service is
org.exoplatform.services.cache.impl.CacheServiceImpl
which
depends on the
org.exoplatform.services.cache.ExoCacheConfig
in order to
create new ExoCache
instances. See the below example of
org.exoplatform.services.cache.CacheService
definition:
<component> <key>org.exoplatform.services.cache.CacheService</key> <jmx-name>cache:type=CacheService</jmx-name> <type>org.exoplatform.services.cache.impl.CacheServiceImpl</type> <init-params> <object-param> <name>cache.config.default</name> <description>The default cache configuration</description> <object type="org.exoplatform.services.cache.ExoCacheConfig"> <field name="name"><string>default</string></field> <field name="maxSize"><int>300</int></field> <field name="liveTime"><long>600</long></field> <field name="distributed"><boolean>false</boolean></field> <field name="implementation"><string>org.exoplatform.services.cache.concurrent.ConcurrentFIFOExoCache</string></field> </object> </object-param> </init-params> </component>
The ExoCacheConfig
which name is
default
, will be the default configuration of all the
ExoCache
instances that don't have dedicated
configuration.
See the below example about how to define a new
ExoCacheConfig
thanks to a
external-component-plugin:
<external-component-plugins> <target-component>org.exoplatform.services.cache.CacheService</target-component> <component-plugin> <name>addExoCacheConfig</name> <set-method>addExoCacheConfig</set-method> <type>org.exoplatform.services.cache.ExoCacheConfigPlugin</type> <description>Configures the cache for query service</description> <init-params> <object-param> <name>cache.config.wcm.composer</name> <description>The default cache configuration</description> <object type="org.exoplatform.services.cache.ExoCacheConfig"> <field name="name"><string>wcm.composer</string></field> <field name="maxSize"><int>300</int></field> <field name="liveTime"><long>600</long></field> <field name="distributed"><boolean>false</boolean></field> <field name="implementation"><string>org.exoplatform.services.cache.concurrent.ConcurrentFIFOExoCache</string></field> </object> </object-param> </init-params> </component-plugin> </external-component-plugins>
Table 2.11. Descriptions of the fields of
ExoCacheConfig
name | The name of the cache. This field is mandatory since it
will be used to retrieve the ExoCacheConfig
corresponding to a given cache name. |
label | The label of the cache. This field is optional. It is mainly used to indicate the purpose of the cache. |
maxSize | The maximum numbers of elements in cache. This field is mandatory. |
liveTime | The amount of time (in seconds) that an element is not written or read before it is evicted. This field is mandatory. |
implementation | The full qualified name of the cache implementation to use.
This field is optional. This field is only used for simple cache
implementation. The default and main implementation is
org.exoplatform.services.cache.concurrent.ConcurrentFIFOExoCache ,
this implementation only works with local caches with FIFO as
eviction policy. For more complex implementation see the next
sections. |
distributed | Indicates if the cache is distributed. This field is optional. This field is deprecated. |
replicated | Indicates if the cache is replicated. This field is optional. |
logEnabled | Indicates if the log is enabled. This field is optional. This field is used for backward compatibility. |
avoidValueReplication | Indicates whether the values of the cache should be replicated or not in case of a replicated cache. This field is optional. By default it is disabled. Find more details about this field in the next section. |
In case, you have big values or non serializable values and you need a replicated cache to at list invalidate the data when it is needed, you can use the invalidation mode that will work on top of any replicated cache implementations. This is possible thanks to the class InvalidationExoCache which is actually a decorator whose idea is to replicate the the hash code of the value in order to know if it is needed or not to invalidate the local data, if the new hash code of the value is the same as the old value, we assume that it is the same value so we don't invalidate the old value. This is required to avoid the following infinite loop that we will face with invalidation mode proposed out of the box by JBoss Cache for example:
Cluster node #1 puts (key1, value1) into the cache
On cluster node #2 key1 is invalidated by put call in node #1
Node #2 re-loads key1 and puts (key1, value1) into the cache
On cluster node #1 key1 is invalidated, so we get back to step #1
In the use case above, thanks to the InvalidationExoCache since the value loaded at step #3 has the same hash code as the value loaded as step #1, the step #4 won't invalidate the data on the cluster node #1.
It exists 2 ways to use the invalidation mode which are the following:
By configuration: For this you simply need to set the parameter avoidValueReplication to true in your eXo cache configuration, this will indicate the CacheService to wrap your eXo cache instance into an InvalidationExoCache in case the cache is defined as replicated or distributed.
Programmatically; You can wrap your eXo cache instance into an org.exoplatform.services.cache.invalidation.InvalidationExoCache yourself using the public constructors that are available. Please note that if you use CacheListeners add them to the InvalidationExoCache instance instead of the nested eXo Cache because the nested eXo Cache will contain only hash codes so the related listeners will get hash codes instead of the real values.
The invalidation will be efficient if and only if the hash code method is properly implemented, in other words 2 value objects representing the same data need to return the same hash code otherwise the infinite loop described above will still be effective.
If the data that you want to store into your eXo Cache instance takes a lot of time to load and/or you would like to prevent multiple concurrent loading of the same data at the same time, you can use org.exoplatform.services.cache.future.FutureExoCache on top of your eXo Cache instance in order to delegate the loading of your data to a loader that will be called only once whatever the total amount of concurrent threads looking for it. See below an example of how the FutureExoCache can be used:
import org.exoplatform.services.cache.future.Loader; import org.exoplatform.services.cache.future.FutureExoCache; ... // Define first your loader and choose properly your context object in order // to be able to reuse the same loader for different FutureExoCache instances Loader<String, String, String> loader = new Loader<String, String, String>() { public String retrieve(String context, String key) throws Exception { return "Value loaded thanks to the key = '" + key + "' and the context = '" + context + "'"; } }; // Create your FutureExoCache from your eXo cache instance and your loader FutureExoCache<String, String, String> myFutureExoCache = new FutureExoCache<String, String, String>(loader, myExoCache); // Get your data from your future cache instance System.out.println(myFutureExoCache.get("my context", "foo"));
In the previous versions of eXo kernel, it was quite complex to implement your own ExoCache because it was not open enough. Since kernel 2.0.8, it is possible to easily integrate your favorite cache provider in eXo Products.
You just need to implement your own ExoCacheFactory
and register it in an eXo container, as described below:
package org.exoplatform.services.cache; ... public interface ExoCacheFactory { /** * Creates a new instance of {@link org.exoplatform.services.cache.ExoCache} * @param config the cache to create * @return the new instance of {@link org.exoplatform.services.cache.ExoCache} * @exception ExoCacheInitException if an exception happens while initializing the cache */ public ExoCache createCache(ExoCacheConfig config) throws ExoCacheInitException; }
As you can see, there is only one method to implement which can be
seen as a converter of an ExoCacheConfig
to get an instance
of ExoCache
. Once, you created your own implementation, you
can simply register your factory by adding a file
conf/portal/configuration.xml with a content of the
following type:
<configuration> <component> <key>org.exoplatform.services.cache.ExoCacheFactory</key> <type>org.exoplatform.tutorial.MyExoCacheFactoryImpl</type> ... </component> </configuration>
Since kernel 2.3.0-CR1, if the configuration is not a sub class of
ExoCacheConfig
and the implementation given in the
configuration is the full qualified name of an existing implementation
of eXo Cache, we will assume that the user expects to have an instance
of this eXo Cache type so we won't use the configured cache
factory.
When you add, the eXo library in your classpath, the eXo service container will use the default configuration provided in the library itself but of course you can still redefined the configuration if you wish as you can do with any components.
The default configuration of the factory is:
<configuration> <component> <key>org.exoplatform.services.cache.ExoCacheFactory</key> <type>org.exoplatform.services.cache.impl.jboss.ExoCacheFactoryImpl</type> <init-params> <value-param> <name>cache.config.template</name> <value>jar:/conf/portal/cache-configuration-template.xml</value> </value-param> <value-param> <name>allow.shareable.cache</name> <value>true</value> </value-param> </init-params> </component> </configuration>
Table 2.12. Fields description
cache.config.template | This parameter allows you to define the location of the
default configuration template of JBoss Cache. In the default
configuration, we ask the eXo kernel to get the file shipped
into the jar at
/conf/portal/cache-configuration-template.xml.
The default configuration template aims to be the skeleton from
which we will create any type of jboss cache instance, thus it
must be very generic.NoteThe default configuration template provided with the jar aims to work with any application servers, but if you intend to use JBoss AS, you should redefine it in your custom configuration to fit better with your AS. |
allow.shareable.cache | This parameter allows you to Indicate whether the JBoss
Cache instances used can by default be shared between several
eXo caches instances. indeed to consume less resources, you can
allow to use the same instance of JBoss Cache for several eXo
Cache instances, each eXo Cache Instances will have his own
cache region with its own eviction configuration. The default
value of this parameter is false.NoteThis value is only the default value that cans be
redefined at |
If for a given reason, you need to use a specific configuration for a cache, you can register one thanks to an "external plugin", see an example below:
<configuration> ... <external-component-plugins> <target-component>org.exoplatform.services.cache.ExoCacheFactory</target-component> <component-plugin> <name>addConfig</name> <set-method>addConfig</set-method> <type>org.exoplatform.services.cache.impl.jboss.ExoCacheFactoryConfigPlugin</type> <description>add Custom Configurations</description> <init-params> <value-param> <name>myCustomCache</name> <value>jar:/conf/portal/custom-cache-configuration.xml</value> </value-param> </init-params> </component-plugin> </external-component-plugins> ... </configuration>
In the example above, I call the method
addConfig(ExoCacheFactoryConfigPlugin plugin) on
the current implementation of ExoCacheFactory
which is
actually the jboss cache implementation.
In the init-params block, you can define a set of value-param blocks and for each value-param, we expect the name of cache that needs a specific configuration as name and the location of your custom configuration as value.
In this example, we indicates to the factory that we would like that the cache myCustomCache use the configuration available at jar:/conf/portal/custom-cache-configuration.xml.
The factory for jboss cache, delegates the cache creation to
ExoCacheCreator
that is defined as below:
package org.exoplatform.services.cache.impl.jboss; ... public interface ExoCacheCreator { /** * Creates an eXo cache according to the given configuration {@link org.exoplatform.services.cache.ExoCacheConfig} * @param config the configuration of the cache to apply * @param cache the cache to initialize * @exception ExoCacheInitException if an exception happens while initializing the cache */ public ExoCache create(ExoCacheConfig config, Cache<Serializable, Object> cache) throws ExoCacheInitException; /** * Returns the type of {@link org.exoplatform.services.cache.ExoCacheConfig} expected by the creator * @return the expected type */ public Class<? extends ExoCacheConfig> getExpectedConfigType(); /** * Returns the name of the implementation expected by the creator. This is mainly used to be backward compatible * @return the expected by the creator */ public String getExpectedImplementation(); }
The ExoCacheCreator
allows you to define any kind
of jboss cache instance that you would like to have. It has been
designed to give you the ability to have your own type of
configuration and to always be backward compatible.
In an ExoCacheCreator
, you need to implement 3
methods which are:
create: this method is used to create a
new ExoCache
from the ExoCacheConfig
and a jboss cache instance.
getExpectedConfigType: this method is
used to indicate the factory the subtype of
ExoCacheConfig
supported by the creator.
getExpectedImplementation: this method
is used to indicate the factory and the value of field
implementation of ExoCacheConfig
that is supported
by the creator. This is used for backward compatibility, in other
words, you can still configure your cache with a super class
ExoCacheConfig
.
You can register any cache creator that you want thanks to an "external plugin", see an example below:
<external-component-plugins> <target-component>org.exoplatform.services.cache.ExoCacheFactory</target-component> <component-plugin> <name>addCreator</name> <set-method>addCreator</set-method> <type>org.exoplatform.services.cache.impl.jboss.ExoCacheCreatorPlugin</type> <description>add Exo Cache Creator</description> <init-params> <object-param> <name>LRU</name> <description>The lru cache creator</description> <object type="org.exoplatform.services.cache.impl.jboss.lru.LRUExoCacheCreator"> <field name="defaultTimeToLive"><long>1500</long></field> <field name="defaultMaxAge"><long>2000</long></field> </object> </object-param> </init-params> </component-plugin> </external-component-plugins>
In the example above, I call the method
addCreator(ExoCacheCreatorPlugin plugin) on the
current implementation of ExoCacheFactory
which is
actually the jboss cache implementation.
In the init-params block, you can define a
set of object-param blocks and for each
object-param, we expect any object definition of
type ExoCacheCreator
.
In this example, we register the action creator related to the eviction policy LRU.
By default, no cache creator are defined, so you need to define them yourself by adding them in your configuration files.
.. <object-param> <name>LRU</name> <description>The lru cache creator</description> <object type="org.exoplatform.services.cache.impl.jboss.lru.LRUExoCacheCreator"> <field name="defaultTimeToLive"><long>${my-value}</long></field> <field name="defaultMaxAge"><long>${my-value}</long></field> </object> </object-param> ...
Table 2.13. Fields description
defaultTimeToLive | This is the default value of the field timeToLive described in the section dedicated to this cache type. This value is only used when we define a cache of this type with the old configuration. |
defaultMaxAge | This is the default value of the field maxAge described in the section dedicated to this cache type. This value is only used when we define a cache of this type with the old configuration. |
... <object-param> <name>FIFO</name> <description>The fifo cache creator</description> <object type="org.exoplatform.services.cache.impl.jboss.fifo.FIFOExoCacheCreator"></object> </object-param> ...
... <object-param> <name>MRU</name> <description>The mru cache creator</description> <object type="org.exoplatform.services.cache.impl.jboss.mru.MRUExoCacheCreator"></object> </object-param> ...
... <object-param> <name>LFU</name> <description>The lfu cache creator</description> <object type="org.exoplatform.services.cache.impl.jboss.lfu.LFUExoCacheCreator"> <field name="defaultMinNodes"><int>${my-value}</int></field> </object> </object-param> ...
Table 2.14. Fields description
defaultMinNodes | This is the default value of the field minNodes described in the section dedicated to this cache type. This value is only used when we define a cache of this type with the old configuration. |
... <object-param> <name>EA</name> <description>The ea cache creator</description> <object type="org.exoplatform.services.cache.impl.jboss.ea.EAExoCacheCreator"> <field name="defaultExpirationTimeout"><long>2000</long></field> </object> </object-param> ...
Table 2.15. Fields description
defaultExpirationTimeout | This is the default value of the field minNodes described in the section dedicated to this cache type. This value is only used when we define a cache of this type with the old configuration. |
You have 2 ways to define a cache which are:
At CacheService
initialization
With an "external plugin"
... <component> <key>org.exoplatform.services.cache.CacheService</key> <type>org.exoplatform.services.cache.impl.CacheServiceImpl</type> <init-params> ... <object-param> <name>fifocache</name> <description>The default cache configuration</description> <object type="org.exoplatform.services.cache.ExoCacheConfig"> <field name="name"><string>fifocache</string></field> <field name="maxSize"><int>${my-value}</int></field> <field name="liveTime"><long>${my-value}</long></field> <field name="distributed"><boolean>false</boolean></field> <field name="implementation"><string>org.exoplatform.services.cache.FIFOExoCache</string></field> </object> </object-param> ... </init-params> </component> ...
In this example, we define a new cache called fifocache.
... <external-component-plugins> <target-component>org.exoplatform.services.cache.CacheService</target-component> <component-plugin> <name>addExoCacheConfig</name> <set-method>addExoCacheConfig</set-method> <type>org.exoplatform.services.cache.ExoCacheConfigPlugin</type> <description>add ExoCache configuration component plugin </description> <init-params> ... <object-param> <name>fifoCache</name> <description>The fifo cache configuration</description> <object type="org.exoplatform.services.cache.ExoCacheConfig"> <field name="name"><string>fifocache</string></field> <field name="maxSize"><int>${my-value}</int></field> <field name="liveTime"><long>${my-value}</long></field> <field name="distributed"><boolean>false</boolean></field> <field name="implementation"><string>org.exoplatform.services.cache.FIFOExoCache</string></field> </object> </object-param> ... </init-params> </component-plugin> </external-component-plugins> ...
In this example, we define a new cache called fifocache which is in fact the same cache as in previous example but defined in a different manner.
Actually, if you use a custom configuration for your cache as described in a previous section, we will use the cache mode definde in your configuration file.
In case, you decide to use the default configuration template,
we use the field distributed of your
ExoCacheConfig
to decide. In other words, if the value
of this field is false (the default value), the cache will be a local
cache, otherwise it will be the cache mode defined in your default
configuration template that should be distributed.
In order to avoid creating several JBoss Cache instances that consume resources, it is possible to share the same JBoss Cache instance between multiple eXo Cache instances that rely on the same JBoss Cache config. Each eXo Cache instances will then have their own cache region with their own eviction configuration. To allow sharing JBoss Cache instances, you can set the global value at ExoCacheFactory level and if needed set the local value at ExoCacheConfig level knowing that local value will redefine the global value. Each new ExoCacheConfig described below are a sub class of AbstractExoCacheConfig that gives access to the parameter allowShareableCache, if this parameter is set, it will be the value used otherwise it will use the global value. For all the old ExoCacheConfig, only the global value will be used.
New configuration
... <object-param> <name>lru</name> <description>The lru cache configuration</description> <object type="org.exoplatform.services.cache.impl.jboss.lru.LRUExoCacheConfig"> <field name="name"><string>lru</string></field> <field name="maxNodes"><int>${my-value}</int></field> <field name="minTimeToLive"><long>${my-value}</long></field> <field name="maxAge"><long>${my-value}</long></field> <field name="timeToLive"><long>${my-value}</long></field> </object> </object-param> ...
Table 2.16. Fields description
maxNodes | This is the maximum number of nodes allowed in this region. 0 denotes immediate expiry, -1 denotes no limit. |
minTimeToLive | The minimum amount of time (in milliseconds) that a node must be allowed to live after being accessed before it is allowed to be considered for eviction. 0 denotes that this feature is disabled, which is the default value. |
maxAge | Lifespan of a node (in milliseconds) regardless of idle time before the node is swept away. 0 denotes immediate expiry, -1 denotes no limit. |
timeToLive | The amount of time that a node is not written to or read (in milliseconds) before the node is swept away. 0 denotes immediate expiry, -1 denotes no limit. |
Old configuration
... <object-param> <name>lru-with-old-config</name> <description>The lru cache configuration</description> <object type="org.exoplatform.services.cache.ExoCacheConfig"> <field name="name"><string>lru-with-old-config</string></field> <field name="maxSize"><int>${my-value}</int></field> <field name="liveTime"><long>${my-value}</long></field> <field name="implementation"><string>LRU</string></field> </object> </object-param> ...
Table 2.17. Fields description
maxSize | This is the maximum number of nodes allowed in this region. 0 denotes immediate expiry, -1 denotes no limit. |
liveTime | The minimum amount of time (in seconds) that a node must be allowed to live after being accessed before it is allowed to be considered for eviction. 0 denotes that this feature is disabled, which is the default value. |
For the fields maxAge and timeToLive needed by JBoss cache, we will use the default values provided by the creator.
New configuration
... <object-param> <name>fifo</name> <description>The fifo cache configuration</description> <object type="org.exoplatform.services.cache.impl.jboss.fifo.FIFOExoCacheConfig"> <field name="name"><string>fifo</string></field> <field name="maxNodes"><int>${my-value}</int></field> <field name="minTimeToLive"><long>${my-value}</long></field> </object> </object-param> ...
Table 2.18. Fields description
maxNodes | This is the maximum number of nodes allowed in this region. 0 denotes immediate expiry, -1 denotes no limit. |
minTimeToLive | The minimum amount of time (in milliseconds) that a node must be allowed to live after being accessed before it is allowed to be considered for eviction. 0 denotes that this feature is disabled, which is the default value. |
Old configuration
... <object-param> <name>fifo-with-old-config</name> <description>The fifo cache configuration</description> <object type="org.exoplatform.services.cache.ExoCacheConfig"> <field name="name"><string>fifo-with-old-config</string></field> <field name="maxSize"><int>${my-value}</int></field> <field name="liveTime"><long>${my-value}</long></field> <field name="implementation"><string>FIFO</string></field> </object> </object-param> ...
Table 2.19. Fields description
maxSize | This is the maximum number of nodes allowed in this region. 0 denotes immediate expiry, -1 denotes no limit. |
liveTime | The minimum amount of time (in seconds) that a node must be allowed to live after being accessed before it is allowed to be considered for eviction. 0 denotes that this feature is disabled, which is the default value. |
New configuration
... <object-param> <name>mru</name> <description>The mru cache configuration</description> <object type="org.exoplatform.services.cache.impl.jboss.mru.MRUExoCacheConfig"> <field name="name"><string>mru</string></field> <field name="maxNodes"><int>${my-value}</int></field> <field name="minTimeToLive"><long>${my-value}</long></field> </object> </object-param> ...
Table 2.20. Fields description
maxNodes | This is the maximum number of nodes allowed in this region. 0 denotes immediate expiry, -1 denotes no limit. |
minTimeToLive | The minimum amount of time (in milliseconds) that a node must be allowed to live after being accessed before it is allowed to be considered for eviction. 0 denotes that this feature is disabled, which is the default value. |
Old configuration
... <object-param> <name>mru-with-old-config</name> <description>The mru cache configuration</description> <object type="org.exoplatform.services.cache.ExoCacheConfig"> <field name="name"><string>mru-with-old-config</string></field> <field name="maxSize"><int>${my-value}</int></field> <field name="liveTime"><long>${my-value}</long></field> <field name="implementation"><string>MRU</string></field> </object> </object-param> ...
Table 2.21. Fields description
maxSize | This is the maximum number of nodes allowed in this region. 0 denotes immediate expiry, -1 denotes no limit. |
liveTime | The minimum amount of time (in seconds) that a node must be allowed to live after being accessed before it is allowed to be considered for eviction. 0 denotes that this feature is disabled, which is the default value. |
New configuration
... <object-param> <name>lfu</name> <description>The lfu cache configuration</description> <object type="org.exoplatform.services.cache.impl.jboss.lfu.LFUExoCacheConfig"> <field name="name"><string>lfu</string></field> <field name="maxNodes"><int>${my-value}</int></field> <field name="minNodes"><int>${my-value}</int></field> <field name="minTimeToLive"><long>${my-value}</long></field> </object> </object-param> ...
Table 2.22. Fields description
maxNodes | This is the maximum number of nodes allowed in this region. 0 denotes immediate expiry, -1 denotes no limit. |
minNodes | This is the minimum number of nodes allowed in this region. This value determines what the eviction queue should prune down to per pass. e.g. If minNodes is 10 and the cache grows to 100 nodes, the cache is pruned down to the 10 most frequently used nodes when the eviction timer makes a pass through the eviction algorithm. |
minTimeToLive | The minimum amount of time (in milliseconds) that a node must be allowed to live after being accessed before it is allowed to be considered for eviction. 0 denotes that this feature is disabled, which is the default value. |
Old configuration
... <object-param> <name>lfu-with-old-config</name> <description>The lfu cache configuration</description> <object type="org.exoplatform.services.cache.ExoCacheConfig"> <field name="name"><string>lfu-with-old-config</string></field> <field name="maxSize"><int>${my-value}</int></field> <field name="liveTime"><long>${my-value}</long></field> <field name="implementation"><string>LFU</string></field> </object> </object-param> ...
Table 2.23. Fields description
maxSize | This is the maximum number of nodes allowed in this region. 0 denotes immediate expiry, -1 denotes no limit. |
liveTime | The minimum amount of time (in milliseconds) that a node must be allowed to live after being accessed before it is allowed to be considered for eviction. 0 denotes that this feature is disabled, which is the default value. |
For the fields minNodes and timeToLive needed by JBoss cache, we will use the default values provided by the creator.
New configuration
... <object-param> <name>ea</name> <description>The ea cache configuration</description> <object type="org.exoplatform.services.cache.impl.jboss.ea.EAExoCacheConfig"> <field name="name"><string>ea</string></field> <field name="maxNodes"><int>${my-value}</int></field> <field name="minTimeToLive"><long>${my-value}</long></field> <field name="expirationTimeout"><long>${my-value}</long></field> </object> </object-param> ...
Table 2.24. Fields description
maxNodes | This is the maximum number of nodes allowed in this region. 0 denotes immediate expiry, -1 denotes no limit. |
minTimeToLive | The minimum amount of time (in milliseconds) that a node must be allowed to live after being accessed before it is allowed to be considered for eviction. 0 denotes that this feature is disabled, which is the default value. |
expirationTimeout | This is the timeout after which the cache entry must be evicted. |
Old configuration
... <object-param> <name>ea-with-old-config</name> <description>The ea cache configuration</description> <object type="org.exoplatform.services.cache.ExoCacheConfig"> <field name="name"><string>lfu-with-old-config</string></field> <field name="maxSize"><int>${my-value}</int></field> <field name="liveTime"><long>${my-value}</long></field> <field name="implementation"><string>EA</string></field> </object> </object-param> ...
Table 2.25. Fields description
maxSize | This is the maximum number of nodes allowed in this region. 0 denotes immediate expiry, -1 denotes no limit. |
liveTime | The minimum amount of time (in milliseconds) that a node must be allowed to live after being accessed before it is allowed to be considered for eviction. 0 denotes that this feature is disabled, which is the default value. |
For the fields expirationTimeout needed by JBoss cache, we will use the default values provided by the creator.
When you add the related jar file in your classpath, the eXo service container will use the default configuration provided in the library itself but of course you can still redefined the configuration if you wish as you can do with any components.
The default configuration of the factory is:
<configuration> <component> <key>org.exoplatform.services.cache.ExoCacheFactory</key> <type>org.exoplatform.services.cache.impl.infinispan.ExoCacheFactoryImpl</type> <init-params> <value-param> <name>cache.config.template</name> <value>jar:/conf/portal/cache-configuration-template.xml</value> </value-param> </init-params> </component> </configuration>
As you can see the factory requires one single parameter which is cache.config.template, this parameter allows you to define the location of the default configuration template of your infinispan. In the default configuration, we ask the eXo container to get the file shipped into the jar at /conf/portal/cache-configuration-template.xml.
The default configuration template aims to be the skeleton from which we will create any type of infinispan cache instance, thus it must be very generic.
All the cache instances fo which we configure the same cluster
name will also share the same
EmbeddedCacheManager.
If for a given reason, you need to use a specific configuration for a cache, you can register one thanks to an "external plugin", see an example below:
<configuration> ... <external-component-plugins> <target-component>org.exoplatform.services.cache.ExoCacheFactory</target-component> <component-plugin> <name>addConfig</name> <set-method>addConfig</set-method> <type>org.exoplatform.services.cache.impl.infinispan.ExoCacheFactoryConfigPlugin</type> <description>add Custom Configurations</description> <init-params> <value-param> <name>myCustomCache</name> <value>jar:/conf/portal/custom-cache-configuration.xml</value> </value-param> </init-params> </component-plugin> </external-component-plugins> ... </configuration>
In the example above, I call the method
addConfig(ExoCacheFactoryConfigPlugin plugin) on
the current implementation of ExoCacheFactory
which is
actually the infinispan implementation.
In the init-params block, you can define a set of value-param blocks and for each value-param, we expect the name of cache that needs a specific configuration as name and the location of your custom configuration as value.
In this example, we indicates to the factory that we would like that the cache myCustomCache use the configuration available at jar:/conf/portal/custom-cache-configuration.xml.
All the cache instances that will rely on the cache
configuration located at the same location will share the same
EmbeddedCacheManager
.
The factory for infinispan, delegates the cache creation to
ExoCacheCreator
that is defined as below:
package org.exoplatform.services.cache.impl.infinispan; ... public interface ExoCacheCreator { /** * Creates an eXo cache according to the given configuration {@link org.exoplatform.services.cache.ExoCacheConfig} * @param config the configuration of the cache to apply * @param confBuilder the configuration builder of the infinispan cache * @param cacheGetter a {@link Callable} instance from which we can get the cache * @exception ExoCacheInitException if an exception happens while initializing the cache */ public ExoCache<Serializable, Object> create(ExoCacheConfig config, ConfigurationBuilder confBuilder, Callable<Cache<Serializable, Object>> cacheGetter) throws ExoCacheInitException; /** * Returns the type of {@link org.exoplatform.services.cache.ExoCacheConfig} expected by the creator * @return the expected type */ public Class<? extends ExoCacheConfig> getExpectedConfigType(); /** * Returns a set of all the implementations expected by the creator. This is mainly used to be backward compatible * @return the expected by the creator */ public Set<String> getExpectedImplementations(); }
The ExoCacheCreator
allows you to define any kind
of infinispan cache instance that you would like to have. It has been
designed to give you the ability to have your own type of
configuration and to always be backward compatible.
In an ExoCacheCreator
, you need to implement 3
methods which are:
create - this method is used to create
a new ExoCache
from the
ExoCacheConfig
, an inifinispan cache configuration
and a Callable object to allow you to get the cache
instance.
getExpectedConfigType - this method is
used to indicate the factory the subtype of
ExoCacheConfig
supported by the creator.
getExpectedImplementations - this
method is used to indicate the factory the values of the field
implementation of
ExoCacheConfig
that is supported by the creator.
This is used for backward compatibility, in other words you can
still configure your cache with an instance of
ExoCacheConfig
.
You can register any cache creator you want thanks to an "external plugin", see an example below:
<external-component-plugins> <target-component>org.exoplatform.services.cache.ExoCacheFactory</target-component> <component-plugin> <name>addCreator</name> <set-method>addCreator</set-method> <type>org.exoplatform.services.cache.impl.infinispan.ExoCacheCreatorPlugin</type> <description>add Exo Cache Creator</description> <init-params> <object-param> <name>Test</name> <description>The cache creator for testing purpose</description> <object type="org.exoplatform.services.cache.impl.infinispan.TestExoCacheCreator"></object> </object-param> </init-params> </component-plugin> </external-component-plugins>
In the example above, I call the method
addCreator(ExoCacheCreatorPlugin plugin) on the
current implementation of ExoCacheFactory
which is
actually the infinispan implementation.
In the init-params block, you can define a
set of object-param blocks and for each
object-param, we expect any object definition of
type ExoCacheCreator
.
In this example, we register the cache creator related to the eviction policy Test.
By default, no cache creator are defined, so you need to define them yourself by adding them in your configuration files.
This is the generic cache creator that allows you to use any eviction strategies defined by default in Infinispan.
.. <object-param> <name>GENERIC</name> <description>The generic cache creator</description> <object type="org.exoplatform.services.cache.impl.infinispan.generic.GenericExoCacheCreator"> <field name="implementations"> <collection type="java.util.HashSet"> <value> <string>NONE</string> </value> <value> <string>FIFO</string> </value> <value> <string>LRU</string> </value> <value> <string>UNORDERED</string> </value> <value> <string>LIRS</string> </value> </collection> </field> <field name="defaultStrategy"><string>${my-value}</string></field> <field name="defaultMaxIdle"><long>${my-value}</long></field> <field name="defaultWakeUpInterval"><long>${my-value}</long></field> </object> </object-param> ...
Table 2.26. Fields description
implementations | This is the list of all the implementations supported by the cache creator. Actualy, it is a subset of the full list of the eviction strategies supported by infinispan to which you want to give access to. In the configuraton above, you have the full list of all the eviction strategies currently supported by infinispan 4.1. This field is used to manage the backward compatibility. |
defaultStrategy | This is the name of the default eviction strategy to use. By default the value is LRU. This value is only use when we define a cache of this type with the old configuration. |
defaultMaxIdle | This is the default value of the field maxIdle described in the section dedicated to this cache type. By default the value is -1.This value is only use when we define a cache of this type with the old configuration. |
defaultWakeUpInterval | his is the default value of the field wakeUpInterval described in the section dedicated to this cache type. By default the value is 5000.This value is only use when we define a cache of this type with the old configuration |
Actually, if you use a custom configuration for your cache as described in a previous section, we will use the cache mode define in your configuration file.
In case, you decide to use the default configuration template,
we use the fields distributed and
replicated of your ExoCacheConfig
to decide. In other words, if the value of these fields is false (the
default value), the cache will be a local cache otherwise if the field
distributed is set to true, the cache will be
then be considered as distrbuted and will be retrieved from the
DistributedCacheManager (more details about it in
the next section). Finally if the field
replicated is set to true, the cache mode of your
cache will be the one defined in the configuration assuming that it
should be replicated.
All the eviction strategies proposed by default in infinispan rely on the generic cache creator.
New configuration
... <object-param> <name>myCache</name> <description>My cache configuration</description> <object type="org.exoplatform.services.cache.impl.infinispan.generic.GenericExoCacheConfig"> <field name="name"><string>myCacheName</string></field> <field name="strategy"><int>${my-value}</int></field> <field name="maxEntries"><long>${my-value}</long></field> <field name="lifespan"><long>${my-value}</long></field> <field name="maxIdle"><long>${my-value}</long></field> <field name="wakeUpInterval"><long>${my-value}</long></field> </object> </object-param> ...
Table 2.27. Fields description
strategy | The name of the strategy to use such as 'UNORDERED', 'FIFO', 'LRU', 'LIRS' and 'NONE' (to disable eviction). |
maxEntries | Maximum number of entries in a cache instance. If selected value is not a power of two the actual value will default to the least power of two larger than selected value. -1 means no limit which is also the default value. |
lifespan | Maximum lifespan of a cache entry, after which the entry is expired cluster-wide, in milliseconds. -1 means the entries never expire which is also the default value. |
maxIdle | Maximum idle time a cache entry will be maintained in the cache, in milliseconds. If the idle time is exceeded, the entry will be expired cluster-wide. -1 means the entries never expire which is also the default value. |
wakeUpInterval | Interval between subsequent eviction runs, in milliseconds. If you wish to disable the periodic eviction process altogether, set wakeupInterval to -1. The default value is 5000. |
Old configuration
... <object-param> <name>myCache</name> <description>My cache configuration</description> <field name="name"><string>lru-with-old-config</string></field> <field name="maxSize"><int>${my-value}</int></field> <field name="liveTime"><long>${my-value}</long></field> <field name="implementation"><string>${my-value}</string></field> </object> </object-param> ...
Table 2.28. Fields description
maxSize | Maximum number of entries in a cache instance. If selected value is not a power of two the actual value will default to the least power of two larger than selected value. -1 means no limit which is also the default value. |
liveTime | Maximum lifespan of a cache entry, after which the entry is expired cluster-wide, in milliseconds. -1 means the entries never expire which is also the default value. |
implementation | The name of the implementation to use the expected value is one of the eviction strategies defined in the field implementations of the generic cache creator. |
For the fields maxIdle and wakeUpInterval needed by infinispan, we will use the default values provided by the creator.
In order to be able to use infinispan in distributed mode with the ability to launch external JVM instances that will manage a part of the cache, we need to configure the DistributedCacheManager. In the next sections, we will show how to configure the component and how to launch external JVM instances.
The DistributedCacheManager is the component that will manage all the cache instances that we expect to be distributed, it must be unique in the whole JVM which means that it must be declared at RootContainer level in portal mode or at StandaloneContainer in standalone mode. See below an example of configuration.
<component> <type>org.exoplatform.services.ispn.DistributedCacheManager</type> <init-params> <value-param> <name>infinispan-configuration</name> <value>jar:/conf/distributed-cache-configuration.xml</value> </value-param> <properties-param> <name>parameters</name> <description>The parameters of the configuration</description> <property name="configurationFile" value="${gatein.jcr.jgroups.config}"></property> <property name="invalidationThreshold" value="0"></property> <property name="numOwners" value="3"></property> <property name="numVirtualNodes" value="2"></property> </properties-param> </init-params> </component>
Table 2.29. Fields description
infinispan-configuration | Location of the infinispan configuration to use in which all the distributed caches must be configured by name. All paths supported by the ConfigurationManager with prefixes like jar:/..., classpath:/..., etc. This parameter is mandatory. |
parameters | In the configuration file, you can add a set of variables of type ${variable-name}, these variables will be solved using the values of the parameters. The name of the variable in the configuration file must match with the name of the parameter. This parameter is optional. |
As described above, the configuration of infinispan must defined explicitly each cache using the nameCache block no dynamic configuration of cache is supported. Indeed to ensure that the whole cluster is consistent in term of defined cache, it is required to configure all the cache that you will need and register it using its future name.
For now, we have 2 supported cache name which are JCRCache and eXoCache. JCRCache is the name of the cache that we use in case we would like to store the data of the JCR into a distributed cache. eXoCache is the name of the cache that we use in case we would like to store the data of some eXo Cache instances into a distributed cache.
See below an example of infinispan configuration with both eXoCache and JCRCache defined:
<infinispan xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:infinispan:config:5.1 http://www.infinispan.org/schemas/infinispan-config-5.1.xsd" xmlns="urn:infinispan:config:5.1"> <global> <globalJmxStatistics jmxDomain="exo" enabled="true" allowDuplicateDomains="true"/> <transport transportClass="org.infinispan.remoting.transport.jgroups.JGroupsTransport" clusterName="JCR-cluster" distributedSyncTimeout="20000"> <properties> <property name="configurationFile" value="${configurationFile}"/> </properties> </transport> <shutdown hookBehavior="DEFAULT"/> </global> <namedCache name="JCRCache"> <locking isolationLevel="READ_COMMITTED" lockAcquisitionTimeout="120000" writeSkewCheck="false" concurrencyLevel="500" useLockStriping="true" /> <transaction transactionManagerLookupClass="org.infinispan.transaction.lookup.GenericTransactionManagerLookup" syncRollbackPhase="true" syncCommitPhase="true" eagerLockSingleNode="true" transactionMode="TRANSACTIONAL"/> <jmxStatistics enabled="true"/> <clustering mode="distribution"> <l1 enabled="true" invalidationThreshold="${invalidationThreshold}"/> <hash numOwners="${numOwners}" numVirtualNodes="${numVirtualNodes}" rehashRpcTimeout="120000"> <groups enabled="true"/> </hash> <sync replTimeout="180000"/> </clustering> </namedCache> <namedCache name="eXoCache"> <locking isolationLevel="READ_COMMITTED" lockAcquisitionTimeout="120000" writeSkewCheck="false" concurrencyLevel="500" useLockStriping="true" /> <transaction transactionManagerLookupClass="org.infinispan.transaction.lookup.GenericTransactionManagerLookup" syncRollbackPhase="true" syncCommitPhase="true" eagerLockSingleNode="true" transactionMode="TRANSACTIONAL"/> <jmxStatistics enabled="true"/> <clustering mode="distribution"> <l1 enabled="true" invalidationThreshold="${invalidationThreshold}"/> <hash numOwners="${numOwners}" numVirtualNodes="${numVirtualNodes}" rehashRpcTimeout="120000"/> <sync replTimeout="180000"/> </clustering> </namedCache> </infinispan>
In case you intend to use the distribued mode, you can launch external JVM in standalone mode to provide more memory to your current cache. To do so, you will need to get the file of type exo.jcr.component.core.impl.infinispan.v5-binary.zip in which you will find scripts to launch your cache servers. These scripts allow optional arguments that are described below:
help|?|<configuration-file-path>|udp|tcp <initial-hosts>
Table 2.30. Arguments description
help | Print the expected syntax. |
? | Print the expected syntax. |
configuration-file-path | The location of the configuration file to use, we expect an absolute path. It will try to get it using the current class loader, if it cannot be found it will get it from the file system. By default it will use the path /conf/cache-server-configuration.xml that is actually a file bundled into the jar. |
udp | We use this parameter value when we want to use the default configuration file with udp as transport stack which is actually the default stack used which means that it will have the exact same behavior as when we don't provide any parameter. |
tcp | We use this parameter value when we want to use the default configuration file with tcp as transport stack. |
initial-hosts | This parameter is optional and is only allowed in case the tcp stack is enabled, it will allow you to define the set of hosts that will be part of the cluster. The syntax of this parameter is a list of hostname[port] comma-separated. Knowing that the default value is "localhost[7800],localhost[7801]" if this parameter is not set, the bind address will be automatically set to 127.0.0.1 so you need to ensure that your server is configured to have it mapped to localhost. |
If you intend to use the CacheServer in order to manage some of your eXo Cache instances, don't forget to add the jar files that define both the keys and the values in the lib directory of the CacheServer distribution and restarts your CacheServer instances otherwise the unmarshalling will fail with java.lang.ClassNotFoundException.
In case you would like to configure your workspace in order to rely on a distributed cache apart using org.exoplatform.services.jcr.impl.dataflow.persistent.infinispan.ISPNCacheWorkspaceStorageCache as FQN of your cache you will need to set the property use-distributed-cache to true. If you do so the JCR cache will rely on the cache called JCRCache defined in the infinispan configuration provided to the DistributedCacheManager.
TransactionServices provides access to the TransactionManager and the UserTransaction (See JTA specification for details).
Table 2.31. List methods
getTransactionManager() | Get used TransactionManager |
getUserTransaction() | Get UserTransaction on TransactionManager |
getDefaultTimeout() | Return default TimeOut |
setTransactionTimeout(int seconds) | Set TimeOut in second |
enlistResource(XAResource xares) | Enlist XA resource in TransactionManager |
delistResource(XAResource xares) | Delist XA resource from TransactionManager |
eXo JCR proposes out of the box several implementations, they all implement the abstract class org.exoplatform.services.transaction.impl.AbstractTransactionService. This main class implement the biggest part of all the methods proposed by the TransactionService. For each sub-class of AbstractTransactionService, you can set the transaction timeout by configuration using the value parameter timeout that is expressed in seconds.
To use JOTM as TransactionManager in standalone mode, simply add the following component configuration:
<component> <key>org.exoplatform.services.transaction.TransactionService</key> <type>org.exoplatform.services.transaction.impl.jotm.TransactionServiceJotmImpl</type> <!-- Uncomment the lines below if you want to set default transaction timeout that is expressed in seconds --> <!--init-params> <value-param> <name>timeout</name> <value>60</value> </value-param> </init-params--> </component>
If you intend to use JBoss Cache, you can use a generic TransactionService based on its TransactionManagerLookup which is able to automatically find the TransactionManager of several Application Servers thanks to a set of JNDI lookups. This generic TransactionService covers mainly the TransactionManager lookups, the UserTransaction is actually simply the TransactionManager instance that has been wrapped. See below an example of configuration:
<!-- Configuration of the TransactionManagerLookup --> <component> <key>org.jboss.cache.transaction.TransactionManagerLookup</key> <type>org.jboss.cache.transaction.GenericTransactionManagerLookup</type> </component> <!-- Configuration of the TransactionService --> <component> <key>org.exoplatform.services.transaction.TransactionService</key> <type>org.exoplatform.services.transaction.jbosscache.GenericTransactionService</type> <!-- Uncomment the lines below if you want to set default transaction timeout that is expressed in seconds --> <!--init-params> <value-param> <name>timeout</name> <value>60</value> </value-param> </init-params--> </component>
If you intend to use JBoss Cache with Arjuna, you can use a more specific GenericTransactionService, it is mostly interesting in case you want to use the real UserTransaction. See below an example of configuration:
<!-- Configuration of the TransactionManagerLookup --> <component> <key>org.jboss.cache.transaction.TransactionManagerLookup</key> <type>org.jboss.cache.transaction.JBossStandaloneJTAManagerLookup</type> </component> <!-- Configuration of the TransactionService --> <component> <key>org.exoplatform.services.transaction.TransactionService</key> <type>org.exoplatform.services.transaction.jbosscache.JBossTransactionsService</type> <!-- Uncomment the lines below if you want to set default transaction timeout that is expressed in seconds --> <!--init-params> <value-param> <name>timeout</name> <value>60</value> </value-param> </init-params--> </component>
If you intend to use Infinispan, you can use a generic TransactionService based on its TransactionManagerLookup which is able to automatically find the TransactionManager of several Application Servers thanks to a set of JNDI lookups. This generic TransactionService covers mainly the TransactionManager lookups, the UserTransaction is actually simply the TransactionManager instance that has been wrapped. See below an example of configuration:
<!-- Configuration of the TransactionManagerLookup --> <component> <key>org.infinispan.transaction.lookup.TransactionManagerLookup</key> <type>org.infinispan.transaction.lookup.GenericTransactionManagerLookup</type> </component> <!-- Configuration of the TransactionService --> <component> <key>org.exoplatform.services.transaction.TransactionService</key> <type>org.exoplatform.services.transaction.infinispan.GenericTransactionService</type> <!-- Uncomment the lines below if you want to set default transaction timeout that is expressed in seconds --> <!--init-params> <value-param> <name>timeout</name> <value>60</value> </value-param> </init-params--> </component>
If you intend to use Infinispan with Arjuna, you can use a more specific GenericTransactionService, it is mostly interesting in case you want to use the real UserTransaction. See below an example of configuration:
<!-- Configuration of the TransactionManagerLookup --> <component> <key>org.infinispan.transaction.lookup.TransactionManagerLookup</key> <type>org.exoplatform.services.transaction.infinispan.JBossStandaloneJTAManagerLookup</type> </component> <!-- Configuration of the TransactionService --> <component> <key>org.exoplatform.services.transaction.TransactionService</key> <type>org.exoplatform.services.transaction.infinispan.JBossTransactionsService</type> <!-- Uncomment the lines below if you want to set default transaction timeout that is expressed in seconds --> <!--init-params> <value-param> <name>timeout</name> <value>60</value> </value-param> </init-params--> </component>
If you intend to use JBoss AS with JBoss Cache and Infinispan, you can use a very specific TransactionService for JBoss AS. See below an example of configuration:
<component> <key>org.exoplatform.services.transaction.TransactionService</key> <type>org.exoplatform.services.transaction.impl.jboss.JBossTransactionService</type> <!-- Uncomment the lines below if you want to set default transaction timeout that is expressed in seconds --> <!--init-params> <value-param> <name>timeout</name> <value>60</value> </value-param> </init-params--> </component>
To use TransactionsEssentials as TransactionManager in standalone mode, simply add the following component configuration:
<component> <key>org.exoplatform.services.transaction.TransactionService</key> <type>org.exoplatform.services.transaction.impl.atomikos.TransactionsEssentialsTransactionService</type> <!-- Uncomment the lines below if you want to set default transaction timeout that is expressed in seconds --> <!--init-params> <value-param> <name>timeout</name> <value>60</value> </value-param> </init-params--> </component>
The DataSourceProvider is a service used to give access to a data source in an uniform manner in order to be able to support data sources that are managed by the application server.
Table 2.32. List methods
getDataSource(String dataSourceName) | Tries to get the data source from a JNDI lookup. If it can be found and the data source is defined as managed, the service will wrap the original DataSource instance in a new DataSource instance that is aware of its managed state otherwise it will return the original DataSource instance. |
isManaged(String dataSourceName) | Indicates whether or not the given data source is managed. |
The configuration of the DataSourceProvider should be defined only if you use managed data sources since by default all the data sources are considered as not managed. See below the default configuration
<configuration> .... <component> <key>org.exoplatform.services.jdbc.DataSourceProvider</key> <type>org.exoplatform.services.jdbc.impl.DataSourceProviderImpl</type> <init-params> <!-- Indicates that the data source needs to check if a tx is active to decide if the provided connection needs to be managed or not. If it is set to false, the data source will provide only managed connections if the data source itself is managed. --> <!--value-param> <name>check-tx-active</name> <value>true</value> </value-param--> <!-- Indicates that all the data sources are managed If set to true the parameter never-managed and managed-data-sources will be ignored --> <!--value-param> <name>always-managed</name> <value>true</value> </value-param--> <!-- Indicates the list of all the data sources that are managed, each value tag can contain a list of data source names separated by a comma, in the example below we will register ds-foo1, ds-foo2 and ds-foo3 as managed data source. If always-managed and/or never-managed is set true this parameter is ignored --> <!--values-param> <name>managed-data-sources</name> <value>ds-foo1, ds-foo2</value> <value>ds-foo3</value> </values-param--> </init-params> </component> ... </configuration>
Table 2.33. Fields description
check-tx-active | This parameter indicates that the data source needs to check if a transaction is active to decide if the provided connection needs to be managed or not. If it is set to false, the data source will provide only managed connections if the data source itself is managed. By default, this parameter is set to true. If this parameter is set to true, it will need the TransactionService to work propertly, so please ensure that the TransactionService is defined in your configuration. |
always-managed | This parameter indicates that all the data sources are managed. If set to true the parameter never-managed and managed-data-sources will be ignored, so it will consider all the data sources as managed. By default, this parameter is set to false. |
managed-data-sources | This parameter indicates the list of all the data sources that are managed, each value tag can contain a list of data source names separated by a comma. If always-managed and/or never-managed is set true this parameter is ignored. |
This section provides you the basic knowledge about JNDI naming, such as, what it is, how it works and how it is used.
We need to configure JNDI environment properties and Reference binding with the eXo container standard mechanism.
The Naming service covers:
Configuring the current Naming Context Factory implemented as
an ExoContainer Component
org.exoplatform.services.naming.InitialContextInitializer
.
Binding Objects (References) to the current Context using
org.exoplatform.services.naming.BindReferencePlugin
component plugin.
Make sure you understand the Java Naming and Directory InterfaceTM (JNDI) concepts before using this service.
After the start time the Context Initializer
(org.exoplatform.services.naming.InitialContextInitializer) traverses
all initial parameters (that concern the Naming Context) configured in
default-properties
and
mandatory-properties
(see Configuration examples)
and:
For default-properties
: Check if this property
is already set as a System property
(System.getProperty(name)
) and set this property if
it's not found. Using those properties is recommended with a third
party Naming service provider.
For mandatory-properties
: Set the property
without checking.
Standard JNDI properties:
java.naming.factory.initial
java.naming.provider.url
and others (see JNDI docs)
Another responsibility of Context Initializer
org.exoplatform.services.naming.InitialContextInitializer
is binding of preconfigured references to the naming context. For this
purpose, it uses a standard eXo component plugin mechanism and in
particular the
org.exoplatform.services.naming.BindReferencePlugin
component plugin. The configuration of this plugin includes three
mandatory value parameters:
bind-name
: the name of binding
reference.
class-name
: the type of binding
reference.
factory
: the object factory type.
And also ref-addresses
property parameter with a
set of references' properties. (see Configuration examples) Context
Initializer uses those parameters to bind the neccessary reference
automatically.
The InitialContextInitializer
configuration
example:
<component> <type>org.exoplatform.services.naming.InitialContextInitializer</type> <init-params> <value-param>. <name>bindings-store-path</name>. <value>bind-references.xml</value>. </value-param>. <value-param> <name>overload-context-factory</name> <value>true</value> </value-param> <properties-param> <name>default-properties</name> <description>Default initial context properties</description> <property name="java.naming.factory.initial" value="org.exoplatform.services.naming.SimpleContextFactory"/> </properties-param> <properties-param> <name>mandatory-properties</name> <description>Mandatory initial context properties</description> <property name="java.naming.provider.url" value="rmi://localhost:9999"/> </properties-param> </init-params> </component>
where
binding-store-path is file path which stores binded datasources in runtime
overload-context-factory allows to overload the default initial context factory by a context factory that is ExoContainer aware and that is able to delegate to the original initial context factory if it detects that it is not in the eXo scope. By default the feature is disabled since it is only required on AS that don't share the objects by default like tomcat but unlike JBoss AS
The BindReferencePlugin
component plugin
configuration example (for JDBC datasource):
<component-plugins> <component-plugin> <name>bind.datasource</name> <set-method>addPlugin</set-method> <type>org.exoplatform.services.naming.BindReferencePlugin</type> <init-params> <value-param> <name>bind-name</name> <value>jdbcjcr</value> </value-param> <value-param> <name>class-name</name> <value>javax.sql.DataSource</value> </value-param> <value-param> <name>factory</name> <value>org.apache.commons.dbcp.BasicDataSourceFactory</value> </value-param> <properties-param> <name>ref-addresses</name> <description>ref-addresses</description> <property name="driverClassName" value="org.hsqldb.jdbcDriver"/> <property name="url" value="jdbc:hsqldb:file:target/temp/data/portal"/> <property name="username" value="sa"/> <property name="password" value=""/> </properties-param> </init-params> </component-plugin>
In order to accommodate to the different target runtime where it can be deployed, eXo is capable of leveraging several logging systems. eXo lets you choose the underlying logging engine to use and even configure that engine (as a quick alternative to doing it directly in your runtime environment).
The currently supported logging engines are:
Apache Log4J
JDK's logging
Apache Commons logging (which is itself a pluggable logging abstraction)
eXo lets you choose whatever logging engine you want as this is generally influences by the AS runtime or internal policy.
This is done through an eXo component called
LogConfigurationInitializer
.
org.exoplatform.services.log.LogConfigurationInitializer
that reads init parameters and configures logging system according to
them. The parameters:
configurator - an implementation of the
LogConfigurator
interface with one method
configure() that accepts a list of properties (3rd init parameter)
to configure the underlying log system using the concrete mechanism.
Again, there are three configurators for the most known log systems
(commons, log4j, jdk).
properties - properties to configure the concrete log system (system properties for commons, log4j.properties or logging.properties for commons, log4j and jdk respectively) Look at the configuration examples below.
Log4J is a very popular and flexible logging system. It is a good option for JBoss.
<component> <type>org.exoplatform.services.log.LogConfigurationInitializer</type> <init-params> <value-param> <name>configurator</name> <value>org.exoplatform.services.log.impl.Log4JConfigurator</value> </value-param> <properties-param> <name>properties</name> <description>Log4J properties</description> <property name="log4j.rootLogger" value="DEBUG, stdout, file"/> <property name="log4j.appender.stdout" value="org.apache.log4j.ConsoleAppender"/> <property name="log4j.appender.stdout.layout" value="org.apache.log4j.PatternLayout"/> <property name="log4j.appender.stdout.layout.ConversionPattern" value="%d {dd.MM.yyyy HH:mm:ss} %c {1}: %m (%F, line %L) %n"/> <property name="log4j.appender.file" value="org.apache.log4j.FileAppender"/> <property name="log4j.appender.file.File" value="jcr.log"/> <property name="log4j.appender.file.layout" value="org.apache.log4j.PatternLayout"/> <property name="log4j.appender.file.layout.ConversionPattern" value="%d{dd.MM.yyyy HH:mm:ss} %m (%F, line %L) %n"/> </properties-param > </init-params> </component>
You can set logger level for class or group of classes by setting next property:
<property name="log4j.category.{component or class name}" value="DEBUG"/>
For example:
We want to log all debug messages for class
org.exoplatform.services.jcr.impl.core.SessionDataManager
,
that lies in exo.jcr.component.core
component
<property name="log4j.category.exo.jcr.component.core.SessionDataManager" value="DEBUG"/>
Or we want to log all debug messages for all classes in exo.jcr.component.core component
<property name="log4j.category.exo.jcr.component.core" value="DEBUG"/>
Or we want to log all messages for all kernel components
<property name="log4j.category.exo.kernel" value="DEBUG"/>
JDK logging (aka JUL) is the builtin logging framework introduced in JDK 1.4. It is a good option for Tomcat AS.
Edit the variable LOG_OPTS
in your
eXo.sh
or eXo.bat
:
LOG_OPTS="-Dorg.apache.commons.logging.Log=org.apache.commons.logging.impl.Jdk14Logger"
Edit your logs-configuration.xml:
<component> <type>org.exoplatform.services.log.LogConfigurationInitializer</type> <init-params> <value-param> <name>configurator</name> <value>org.exoplatform.services.log.impl.Jdk14Configurator</value> </value-param> <properties-param> <name>properties</name> <description>jdk1.4 Logger properties</description> <property name="handlers" value="java.util.logging.ConsoleHandler"/> <property name=".level" value="FINE"/> <property name="java.util.logging.ConsoleHandler.level" value="FINE"/> </properties-param> </init-params> </component>
SimpleLog is a minimal logging system distributed with Commons Logging. To be used when nothing else is available or when you seek simplicity.
<component> <type>org.exoplatform.services.log.LogConfigurationInitializer</type> <init-params> <value-param> <name>configurator</name> <value>org.exoplatform.services.log.impl.SimpleExoLogConfigurator</value> </value-param> <properties-param> <name>properties</name> <description>SimpleLog properties</description> <property name="org.apache.commons.logging.simplelog.defaultlog" value="debug"/> <property name="org.apache.commons.logging.simplelog.showdatetime" value="true"/> </properties-param> </init-params> </component>
If you use log4j configuration, you can change the log configuration directly at runtime in:
JBOSS_HOME/server/default/conf/jboss-log4j.xml
.
To enable debug logs:
<param name="Threshold" value="DEBUG"/>
To exclude messages from unnecessary classes (server's internal) modify the threshold of these classes to "FATAL".
If you see only ERROR level logs while starting ear on jboss (4.2.2), you have to remove log4j*.jar from your ear and application.xml.
The kernel has a framework for exposing a management view of the various sub systems of the platform. The management view is a lose term for defining how we can access relevant information about the system and how we can apply management operations. JMX is the de facto standard for exposing a management view in the Java Platform but we take in consideration other kind of views such as REST web services. Therefore, the framework is not tied to JMX, yet it provides a JMX part to define more precisely details related to the JMX management view. The legacy framework is still in use but is deprecated in favor of the new framework as it is less tested and less efficient. It will be removed by sanitization in the future.
The managed frameworks defines an API for exposing a management view of objects. The API is targeted for internal use and is not a public API. The framework leverages Java 5 annotations to describe the management view from an object.
The @Managed annotates elements that wants to expose a management view to a management layer.
@Managed for objects
The framework will export a management view for the objects annotated.
@Managed for getter/setter
Defines a managed property. An annotated getter defines a read property, an annotated setter defines a write property and if matching getter/setter are annotated it defines a read/write property.
@Managed on method
Defines a managed operation.
The @ManagedDescription annotation provides a description of a managed element. It is valid to annotated object or methods. It takes as sole argument a string that is the description value.
The @ManagedName annotation provides an alternative name for managed properties. It is used to accomodate legacy methods of an object that can be renamed for compatibility reasons. It takes as sole argument a string that is the name value.
The @Property annotation is used to within other annotations such as @NameTemplate or @NamingContext. It should be seen as a structural way for a list of properties. A property is made of a key and a value. The value can either be a string litteral or it can be surrounded by curly brace to be a dynamic property. A dynamic property is resolved against the instance of the object at runtime.
The @NameTemplate defines a template that is used at registration time of a managed object to create the JMX object name. The template is formed of properties.
@NameTemplate({ @Property(key="container", value="workspace"), @Property(key="name", value="{Name}")})
The @NamingContext annotations defines a set of properties which are used within a management context. It allows to propagate properties down to managed objects which are defined by an object implementing the ManagementAware interface. The goal is to scope different instances of the same class that would have the same object name otherwise.
@NamingContext(@Property(key="workspace", value="{Name}"))
The cache service delegates most of the work to the CacheServiceManaged class by using the @ManagedBy annotation. At runtime when a new cache is created, it calls the CacheServiceManaged class in order to let the CacheServiceManaged object register the cache.
@ManagedBy(CacheServiceManaged.class) public class CacheServiceImpl implements CacheService { CacheServiceManaged managed; ... synchronized private ExoCache createCacheInstance(String region) throws Exception { ... if (managed != null) { managed.registerCache(simple); } ... } }
The ExoCache interface is annotated to define its management view. The @NameTemplate is used to produce object name values when ExoCache instance are registered.
@Managed @NameTemplate({@Property(key="service", value="cache"), @Property(key="name", value="{Name}")}) @ManagedDescription("Exo Cache") public interface ExoCache { @Managed @ManagedName("Name") @ManagedDescription("The cache name") public String getName(); @Managed @ManagedName("Capacity") @ManagedDescription("The maximum capacity") public int getMaxSize(); @Managed @ManagedDescription("Evict all entries of the cache") public void clearCache() throws Exception; ... }
The CacheServiceManaged is the glue code between the CacheService and the management view. The main reason is that only exo services are registered automatically against the management view. Any other managed bean must be registered manually for now. Therefore, it needs to know about the management layer via the management context. The management context allows an object implementing the ManagementAware interface to receive a context to perform further registration of managed objects.
@Managed public class CacheServiceManaged implements ManagementAware { /** . */ private ManagementContext context; /** . */ private CacheServiceImpl cacheService; public CacheServiceManaged(CacheServiceImpl cacheService) { this.cacheService = cacheService; // cacheService.managed = this; } public void setContext(ManagementContext context) { this.context = context; } void registerCache(ExoCache cache) { if (context != null) { context.register(cache); } } }
The RPCService is only needed in a cluser environment, it is used to communicate with the other cluster nodes. It allows to execute a command on all the cluster nodes or on the coordinator i.e. the oldest node in the cluster. The RPCService has been designed to rely on JGroups capabilities and should not be used for heavy load. It can be used, for example, to notify other nodes that something happened or to collect some information from the other nodes.
The RPCService relies on 3 main interfaces which are:
The org.exoplatform.services.rpc.RPCService that defines the service itslef
The org.exoplatform.services.rpc.RemoteCommand that defines the command that we can execute on other nodes.
The org.exoplatform.services.rpc.TopologyChangeListener that defines the listeners that will be notified anytime the topology of the cluster changes.
The arguments that will be given to the RemoteCommand must be Serializable and its return type also in order to prevent any issue due to the serialization. To prevent to execute any RemoteCommand that could be malicious and to allow to use non Serializable command, you need to register the command first before using it. Since the service will keep only one instance of RemoteCommand per command Id, the implementation of the RemoteCommand must be thread safe.
To be usable, all the RemoteCommands must be registered before being used on all the cluster nodes, which means that the command registration must be done in the constructor of your component in other words before that the RPCService is started. If you try to launch a command that has been registered but the RPCService is not yet launched, you will get an RPCException due to an illegal state. This has for consequences that you will be able to execute a command only once your component will be started.
See an example below:
public class MyService implements Startable { private RPCService rpcService; private RemoteCommand sayHelloCommand; public MyService(RPCService rpcService) { this.rpcService = rpcService; // Register the command before that the RPCService is started sayHelloCommand = rpcService.registerCommand(new RemoteCommand() { public Serializable execute(Serializable[] args) throws Throwable { System.out.println("Hello !"); return null; } public String getId() { return "hello-world-command"; } }); } public void start() { // Since the RPCService is a dependency of RPCService, it will be started before // so I can execute my command try { // This will make all the nodes say "Hello !" rpcService.executeCommandOnAllNodes(sayHelloCommand, false); } catch (SecurityException e) { e.printStackTrace(); } catch (RPCException e) { e.printStackTrace(); } } public void stop() { } }
In the previous example, we register the command sayHelloCommand in the constructor of MyService and we execute this command in the start method.
We expect to have one RPCService instance per PortalContainer in a portal mode and only one RPCService instance in a standalone mode
The configuration of the RPCService should be added only in a cluster environment. See below an example of configuration in case you intend to use JGroups 2 (which is mandatory if you use JBoss Cache as underlying cache):
<configuration> .... <component> <key>org.exoplatform.services.rpc.RPCService</key> <type>org.exoplatform.services.rpc.impl.RPCServiceImpl</type> <init-params> <value-param> <name>jgroups-configuration</name> <value>classpath:/udp.xml</value> </value-param> <value-param> <name>jgroups-cluster-name</name> <value>RPCService-Cluster</value> </value-param> <value-param> <name>jgroups-default-timeout</name> <value>0</value> </value-param> <value-param> <name>allow-failover</name> <value>true</value> </value-param> <value-param> <name>retry-timeout</name> <value>20000</value> </value-param> </init-params> </component> ... </configuration>
See below an example of configuration in case you intend to use JGroups 3 (which is mandatory if you use Infinispan as underlying cache):
<configuration> .... <component> <key>org.exoplatform.services.rpc.RPCService</key> <type>org.exoplatform.services.rpc.jgv3.RPCServiceImpl</type> <init-params> <value-param> <name>jgroups-configuration</name> <value>classpath:/udp.xml</value> </value-param> <value-param> <name>jgroups-cluster-name</name> <value>RPCService-Cluster</value> </value-param> <value-param> <name>jgroups-default-timeout</name> <value>0</value> </value-param> <value-param> <name>allow-failover</name> <value>true</value> </value-param> <value-param> <name>retry-timeout</name> <value>20000</value> </value-param> </init-params> </component> ... </configuration>
The implementation for JGroups 3 is available in the library exo.kernel.component.ext.rpc.impl.jgroups.v3-X.Y.Z.jar.
Table 2.34. Fields description
jgroups-configuration | This is the location of the configuration of jgroups. This parameter is mandatory. |
jgroups-cluster-name | This is the name of the cluster. This parameter is optional and its default value is RPCService-Cluster. Since we could have several instances of the RPCService, the final name will be "${jgroups-cluster-name}-${container-name}" |
jgroups-default-timeout | This is the default timeout to use if the timeout is not given, if no response could be get after this timeout an exception will be thrown. This parameter is optional and its default value is 0 which means that we don't use any timeout by default. This parameter is expressed in milliseconds. |
allow-failover | This is parameter indicates whether a command on the coordinator needs to be relaunched or not if the coordintator seems to have left the cluster. This parameter only affects the behavior of the methods executeCommandOnCoordinator. This parameter is optional and its default value is true. |
retry-timeout | This parameter is the maximum amount of time to wait until the new coordinator is elected. This parameter is linked to the parameter allow-failover, and thus used in the exact same conditions. This parameter is optional and its default value is 20000. This parameter is expressed in milliseconds. |
Most of the time we only need to call a method on a given object, this can be done thanks to the org.exoplatform.services.rpc.SingleMethodCallCommand which is the implementation of a RemoteCommand proposed by default. This command will dynamically execute a method on a given object.
// Register the command first (to be done before that the RPCService has been started) RemoteCommand commandGetName = rpcService.registerCommand(new SingleMethodCallCommand(myService, "getName")); ... // Execute the command on the coordinator (can be done only after having started the RPCService) String name = rpcService.executeCommandOnCoordinator(commandGetName, true); // Print the name System.out.println("Name : " + name);
This example:
Register a SingleMethodCallCommand that will call getName() on the Object myService anytime the command will be executed.
Execute the command synchronously on the coordinator, assuming that the same command (with the same id) has already been registered on the coordinator
Print the name got from the coordinator
As any RemoteCommand, it has to be registered before being executed and before the RPCService is launched.
As any RemoteCommand, the command can be executed only once the RPCService is launched.
The SingleMethodCallCommand only allow public methods, if you try to register a non public method an RPCException will be thrown at creation level.