JBoss.orgCommunity Documentation
Related documents
This article shows how to setup a sample service with some configurations and how to access to the configuration parameters. The later chapters describe all details of the configuration file (parameters, object-params, plugins, imports, etc.), it also shows how to access the configuration values. You may consider this article as a reference, but you can also use this article as a tutorial and read it from the beginning to the end.
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 could possibly 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 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: https://anonsvn.jboss.org/repos/exo-jcr/kernel/trunk/exo.kernel.container/src/test/resources/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 eXo Portal: 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 chapter Configuration Retrieval.