eXo Platform 3.5

## Chapter 54. Service Configuration in Detail

Related documents

- Service Configuration for Beginners

- Services Wiring

- Kernel Configuration File

## Objectives

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.

## Requirements

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.

## Sample Service

### Java Class

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.

### First configuration file

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>

### Note

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.

### Init Parameters

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>

### Note

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.

### Service Access

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.

## Parameters

### Value-Param

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-Param

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");

    ...

    }

### Object-Param

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.

### Collection

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.

## External Plugin

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.

### Note

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.

## Import

The import tag allows to link to other configuration files. These imported files can be placed anywhere. If you write a default configuration which is part of your jar file you should not import files from outside your jar.

- war: Imports from portal.war/WEB-INF

- jar or classpath: Uses the classloader, you can use this prefix in the default configuration for importing an other configuration file which is accessible by the classloader.

- file: Uses an absolute path, you also can put a URL.

- without any prefix:

  - Standalone mode: user directory

  - Portal mode: $AS-HOME, that means the application server home, for example " exo-tomcat".

If you open the "portal/trunk/web/portal/src/main/webapp/WEB-INF/conf.configuration.xml" you will see that it consists only 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>

    ...

## System properties

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.

In portal/trunk/web/portal/src/main/webapp/WEB-INF/conf/database/database-configuration.tmpl.xml you find an example for 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"