JBoss.orgCommunity Documentation

Chapter 3. eXoCore

3.1. Database Creator
3.1.1. API
3.1.2. A configuration examples
3.1.3. An examples of a DDL script
3.2. Security Service
3.2.1. Framework
3.2.2. Usage
3.3. Spring Security Integration
3.3.1. Installation
3.3.2. Configuration
3.3.3. Login portlet example
3.3.4. Integration strategies
3.3.5. Integration with eXo portal
3.3.6. Security context propagation to portlets
3.3.7. Conclusion
3.4. Organization Service
3.4.1. Organizational Model
3.4.2. Custom Organization Service implementation instructions
3.4.3. Related articles and how-tos
3.5. Organization Service Initializer
3.6. Organization Listener
3.6.1. Writing your own listeners
3.6.2. Registering your listeners
3.7. Update ConversationState when user's Membership changed
3.8. DB Schema creator service (JDBC implementation)
3.9. Database Configuration for Hibernate
3.9.1. Generic configuration
3.9.2. Example DB configuration
3.9.3. Registering custom Hibernate XML files into the service
3.10. LDAP Configuration
3.10.1. Quickstart
3.10.2. Configuration
3.10.3. Advanced topics
3.11. Organization Service TCK tests configuration
3.11.1. Maven pom.xml file configuration
3.11.2. Standalone container and Organization Service configuration
3.12. Tika Document Reader Service
3.12.1. Architecture
3.12.2. Configuration
3.12.3. Old-style DocumentReaders and Tika Parsers
3.12.4. TikaDocumentReader features and notes
3.13. Digest Authentication
3.13.1. Server configuration
3.13.2. OrganizationService implementation requirements

The eXo Core is a set of common services, such as Authentication and Security, Organization, Database, Logging, JNDI, LDAP, Document reader, and other services, that are used by eXo products and modules. It also can be used in the business logic.

Database creator DBCreator is responsible for execution DDL script in runtime. A DDL script may contain templates for database name, user name and password which will be replaced by real values at execution time.

Three templates supported:

Service's configuration.

   <component>
      <key>org.exoplatform.services.database.creator.DBCreator</key>
      <type>org.exoplatform.services.database.creator.DBCreator</type>
      <init-params>
      <properties-param>
            <name>db-connection</name>
            <description>database connection properties</description>
            <property name="driverClassName" value="com.mysql.jdbc.Driver" />
            <property name="url" value="jdbc:mysql://localhost/" />
            <property name="username" value="root" />
            <property name="password" value="admin" />
            <property name="additional_property" value="value">
            ...
            <property name="additional_property_n" value="value">
         </properties-param>
         <properties-param>
            <name>db-creation</name>.
            <description>database creation properties</description>.
            <property name="scriptPath" value="script.sql" />
            <property name="username" value="testuser" />
            <property name="password" value="testpwd" />
         </properties-param>
      </init-params>
   </component>

db-connection properties section contains parameters needed for connection to database server

There is four reserved and mandatory properties driverClassName, url, username and password. But db-connection may contain additonal properties.

For example, next additional proprites allows reconnect to MySQL database when connection was refused:

         <properties-param>
            <name>db-connection</name>
            ...
            <property name="validationQuery" value="select 1"/>
            <property name="testOnReturn" value="true"/>
            ...
         </properties-param>

db-creation properties section contains paramaters for database creation using DDL script:

Specific db-connection properties section for different databases.

MySQL:

<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost/" />
<property name="username" value="root" />
<property name="password" value="admin" />

PostgreSQL:

<property name="driverClassName" value="org.postgresql.Driver" />
<property name="url" value="jdbc:postgresql://localhost/" />
<property name="username" value="root" />
<property name="password" value="admin" />

MSSQL:

<property name="driverClassName" value="com.microsoft.sqlserver.jdbc.SQLServerDriver"/>
<property name="url" value="jdbc:sqlserver://localhost:1433;"/>
<property name="username" value="root"/>
<property name="password" value="admin"/>

Sybase:

<property name="driverClassName" value="com.sybase.jdbc3.jdbc.SybDriver" />
<property name="url" value="jdbc:sybase:Tds:localhost:5000/"/>
<property name="username" value="root"/>
<property name="password" value="admin"/>

Oracle:

<property name="driverClassName" value="oracle.jdbc.OracleDriver" />
<property name="url" value="jdbc:oracle:thin:@db2.exoua-int:1521:orclvm" />
<property name="username" value="root" />
<property name="password" value="admin" />

The purpose is to make a simple, unified way for the authentication and the storing/propagation of user sessions through all the eXo components and J2EE containers. JAAS is supposed to be the primary login mechanism but the Security Service framework should not prevent other (custom or standard) mechanisms from being used. You can learn more about JAAS in the Java Tutorial

The central point of this framework is the ConversationState object which stores all information about the state of the current user (very similar to the Session concept). The same ConversationState also stores acquired attributes of an Identity which is a set of principals to identify a user.

The ConversationState has definite lifetime. This object should be created when the user's identity becomes known by eXo (login procedure) and destroyed when the user leaves an eXo based application (logout procedure). Using JAAS it should happen in LoginModule's login() and logout() methods respectively.

An Authenticator is responsible for Identity creation, it consists of two methods:

public interface Authenticator {
  /**
   * Authenticate user and return userId which can be different to username. 
   * @param credentials - list of users credentials (such as name/password, X509 certificate etc)
   * @return userId
   * @throws LoginException
   * @throws Exception
   */
  String validateUser(Credential[] credentials) throws LoginException, Exception;

  /**
   * @param credentials - userId.
   * @return Identity
   * @throws Exception
   */
  Identity createIdentity(String userId) throws Exception;  
}

It is up to the application developer (and deployer) whether to use the Authenticator component(s) and how many implementations of this components should be deployed in eXo container. The developer is free to create an Identity object using a different way, but the Authenticator component is the highly recommended way from architectural considerations.

Typical functionality of the validateUser(Credential\[] credentials) method is the comparison of incoming credentials (username/password, digest etc) with those credentials that are stored in an implementation specific database. Then, validateUser(Credential\[] credentials) returns back the userId or throws a LoginException in a case of wrong credentials.

Default Authenticator implementation is org.exoplatform.services.organization.auth.OrganizationAuthenticatorImpl which compares incoming username/password credentials with the ones stored in OrganizationService. Configuration example:

<component>
  <key>org.exoplatform.services.security.Authenticator</key> 
  <type>org.exoplatform.services.organization.auth.OrganizationAuthenticatorImpl</type>
</component>

The framework described is not coupled with any authentication mechanism but the most logical and implemented by default is the JAAS Login module. The typical sequence looks as follows (see org.exoplatform.services.security.jaas.DefaultLoginModule):

Authenticator authenticator = (Authenticator) container()
          .getComponentInstanceOfType(Authenticator.class); 
// RolesExtractor can be null     
RolesExtractor rolesExtractor = (RolesExtractor) container().
getComponentInstanceOfType(RolesExtractor.class);


Credential[] credentials = new Credential[] {new UsernameCredential(username), new PasswordCredential(password) };
String userId = authenticator.validateUser(credentials);
identity = authenticator.createIdentity(userId);

When initializing the login module, you can set the option parameter "singleLogin". With this option you can disallow the same Identity to login for a second time.

By default singleLogin is disabled, so the same identity can be registered more than one time. Parameter can be passed in this form singleLogin=yes or singleLogin=true.

IdentityRegistry identityRegistry = (IdentityRegistry) getContainer().getComponentInstanceOfType(IdentityRegistry.class);
      
if (singleLogin && identityRegistry.getIdentity(identity.getUserId()) != null) 
  throw new LoginException("User " + identity.getUserId() + " already logined.");

identity.setSubject(subject);
identityRegistry.register(identity);

In the case of using several LoginModules, JAAS allows to place the login() and commit() methods in different REQUIRED modules.

After that, the web application must use SetCurrentIdentityFilter. This filter obtains the ConversationRegistry object and tries to get the ConversationState by sessionId (HttpSession). If there is no ConversationState, then SetCurrentIdentityFilter will create a new one, register it and set it as current one using ConversationState.setCurrent(state).

This listener must be configured in web.xml. The method sessionDestroyed(HttpSessionEvent) is called by the ServletContainer. This method removes the ConversationState from the ConversationRegistry ConversationRegistry.unregister(sesionId) and calls the method LoginModule.logout().

ConversationRegistry conversationRegistry = (ConversationRegistry) getContainer().getComponentInstanceOfType(ConversationRegistry.class);

ConversationState conversationState = conversationRegistry.unregister(sesionId);

if (conversationState != null) {
  log.info("Remove conversation state " + sesionId);
  if (conversationState.getAttribute(ConversationState.SUBJECT) != null) {
    Subject subject = (Subject) conversationState.getAttribute(ConversationState.SUBJECT); 
    LoginContext ctx = new LoginContext("exo-domain",  subject);
    ctx.logout();
} else {
  log.warn("Subject was not found in ConversationState attributes.");
}

This tutorial will guide you through a few steps and show you how easy it is to integrate spring security (or the Spring framework in general) in eXo portal. We will create a login portlet example as a support all along the document reading. The login portlet example has been developed and deployed using the eXo WCM product running on the application server JBoss 4.2.3. But it can easily be adapted to another eXo product (such as ECM) and to other servers such as tomcat. Moreover, the example, claiming to be a real world example, is implemented using JSF 1.2, the JBoss portlet bridge and Spring and can serve as a example project from where you can start your own portlet development targeting the eXo platform.

This tutorial assumes that you have a working eXo WCM installation running under JBoss 4.2.x.

Download the spring framework: spring-framework-2.5.6-with-dependencies.zip.

Download spring-security using thislink.

Unzip the 02portal.war file in the jboss server/default/deploy/exoplatform.sar directory and copy the following jars in WEB-INF/lib:

  • spring.jar

  • spring-security-core.jar

  • aspectjrt-1.5.4.jar

  • exo-spring.jar (contains the filters and event handlers described in this tutorial - see the attachment section of this page)

To enable spring security in exo we need to go through a few configuration steps:

We need to configure the spring security filter chain for our purposes. Create a file named security-context.xml in 02portal.war WEB-INF directory containing the following lines:

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
   xmlns:beans="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
                       http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-2.0.1.xsd">

   <http auto-config="true">
      <intercept-url pattern="/private/**" access="ROLE_USER" />   
      <form-login login-page='/public/classic/Login' default-target-url='/private/classic/home' />
   </http>
  
   <authentication-provider>
      <user-service>
         <user name="rod" password="koala" authorities="ROLE_SUPERVISOR, ROLE_USER, ROLE_TELLER" />
         <user name="root" password="exo" authorities="ROLE_USER" />
      </user-service>
   </authentication-provider>   

</beans:beans>

The file contains two elements. The http node which is responsible of configuring the filter chain. The auto-config mode set to true allows us to do just a minimal configuration, everything else being smartly initialized by default. We just set an intercept URL pointing to '/private/**' with the ROLE_USER authority which corresponds to secured resources in exo. In case of successful auhentication, the user will be redirected to the specified default target URL.

The second element defines a simple authentication provider based on the spring security InMemoryDaoImpl implementation of the UserDetailsService. Note that we define the exo root user in the configuration which will allow us to log in with admin privileges in the exo portal.

Now that we have successfully installed and configured spring security in exo, we need a login portlet example to capture user credentials and serve as an entry point in the authentication process. The login portlet itself is based on JSF 1.2, Jboss portlet bridge and the spring framework, but you can obviously use whatever web framework you want to achieve the same.

So we need a login form to capture user credentials inputs. The portlet login form consists of the following lines of xml:

<f:view xmlns:f="http://java.sun.com/jsf/core"
   xmlns:h="http://java.sun.com/jsf/html"
   xmlns:ice="http://www.icesoft.com/icefaces/component"
   xmlns:liferay-faces="http://liferay.com/tld/faces"
   xmlns:ui="http://java.sun.com/jsf/facelets"
   xmlns:c="http://java.sun.com/jstl/core"
   xmlns:fn="http://java.sun.com/jsp/jstl/functions"
   xmlns:t="http://myfaces.apache.org/tomahawk">   
          
   <style type="text/css" media="screen">   
      @import "/loginportlet/css/starter.css";
      @import "/loginportlet/css/uni-form.css";
   </style>

   <script src="/loginportlet/js/jquery.js" type="text/javascript"></script>
   <script src="/loginportlet/js/uni-form.jquery.js" type="text/javascript"></script>
       
   <h:form styleClass="uniForm" >
      <fieldset class="inlineLabels">
         <legend>Sign in</legend>       

         <div class="ctrlHolder">            
            <h:outputLabel for="login" style="width: 70px"><em>*</em>Login:</h:outputLabel>
            <h:inputText id="login" value="#{loginBean.login}" required="true" styleClass="textInput" />         
            <h:message for="login" styleClass="portlet-msg-error" />
         </div>
         <div class="ctrlHolder">        
            <h:outputLabel for="password" style="width: 70px"><em>*</em>Password:</h:outputLabel>
            <h:inputSecret id="password" value="#{loginBean.passwd}" required="true" styleClass="textInput" />
            <h:message for="password" styleClass="portlet-msg-error" />        
         </div>       
      </fieldset>
   
      <div class="buttonHolder" style="margin-top: 20px; margin-right: 20px">
         <h:commandButton styleClass="primaryAction" value="Submit" action="#{loginBean.login}" />                          
      </div>
   </h:form>
</f:view>

The interesting part resides in the backing bean which implements the login action triggered when the user clicks the login form submit button.

package org.exoplatform.loginportlet;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;

@Controller
@Scope("request")
public class LoginBean {

    String login;

    String passwd;

    public String login() throws Exception {
        String redirect = "/portal/j_spring_security_check?j_username=" + login + "&j_password=" + passwd;
        PortalUtils.sendRedirect(redirect);       
        return null;
    }

    ...
}

The login action simply sends a HTTP redirect to the spring security login URL passing the user login and password as parameters. This URL informs the filter to try to authenticate the supplied user credentials. This is the Spring security authentication process entry point.

Now that we have a login portlet available we need to set it up into a portal page.

In the portal header, there is a login or logout action displayed depending whether you are already logged in or not. We need to customize those actions so that when the user clicks on it she or he will be redirected either to our login page or to the spring security logout url. Edit the article, go to the default.js tab and apply the following changes to the code:

function validateUser() {

    var user = eXo.env.portal.userName;
    var rootObj = document.getElementById("classic-access");
    var loginContentObj = eXo.core.DOMUtil.findFirstDescendantByClass(rootObj, "div", "UIWCMLoginPortlet");
    var welcomeObj = eXo.core.DOMUtil.findFirstDescendantByClass(rootObj, "span", "Welcome");
    var userObj = eXo.core.DOMUtil.findFirstDescendantByClass(rootObj, "span", "LoggedUser");
    var languageObj = eXo.core.DOMUtil.findFirstDescendantByClass(rootObj, "a", "LanguageIcon");
    var logXXXObj = eXo.core.DOMUtil.findPreviousElementByTagName(languageObj, "a");

    if (user != "null") {
        welcomeObj.innerHTML = "Welcome: ";       
        userObj.innerHTML = user;
        logXXXObj.innerHTML = "Logout";
        if (eXo.core.DOMUtil.hasClass(logXXXObj, "LoginIcon")) {
            eXo.core.DOMUtil.removeClass(logXXXObj, "LoginIcon");
            eXo.core.DOMUtil.addClass(logXXXObj, "LogoutIcon");
        }
        logXXXObj.onclick = function() { document.location.href = '/portal/j_spring_security_logout' }
    } else {
        if (eXo.core.DOMUtil.hasClass(logXXXObj, "LogoutIcon")) {
            eXo.core.DOMUtil.removeClass(logXXXObj, "LogoutIcon");
            eXo.core.DOMUtil.addClass(logXXXObj, "LoginIcon");
        }
        logXXXObj.innerHTML = "Login";
        logXXXObj.onclick = function() { document.location.href = '/portal/public/classic/Login' };
    }

    languageObj.onclick = function () { if(document.getElementById('UIMaskWorkspace')) ajaxGet(eXo.env.server.createPortalURL('UIPortal', 'ChangeLanguage', true)); }
}

eXo.core.Browser.addOnLoadCallback("validateUser", validateUser);

As you can see, the two onclick event handler function bodies have been changed to a simple redirect to the login page or the logout URL.

Being successfully authenticated against an external realm is not sufficient by itself. We also need to propagate the newly created security context to the portal own security mechanism. In eXo portal terminology, it means we have to create an Identity object for the user and register it into the Identity Registry.

Spring framework provides a simple notification model where a bean can listen to application events published by other beans. Fortunately, spring security uses this mechanism and publishes an InteractiveAuthenticationSuccessEvent in case of successful authentication. That will allow us to hook up custom code to that event.

Furthermore, we need to replicate the user details from the external realm to the eXo portal one according to the integration strategy defined above.

We create a SpringSecurityEventHandler bean that implements the ApplicationListener interface and listens to the InteractiveAuthenticationSuccessEvent event.

package org.exoplatform.spring.security.web;

...

public class SpringSecurityEventHandler implements ApplicationListener {

    private String portalContainerName = "portal";

    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof InteractiveAuthenticationSuccessEvent) {
            try {
                InteractiveAuthenticationSuccessEvent successEvent = (InteractiveAuthenticationSuccessEvent) event;
                ExoContainer container = getContainer();

                String login = successEvent.getAuthentication().getName();
                String passwd = successEvent.getAuthentication().getCredentials().toString();

                IdentityRegistry identityRegistry = (IdentityRegistry) container.getComponentInstanceOfType(IdentityRegistry.class);
                Authenticator authenticator = (Authenticator) container.getComponentInstanceOfType(Authenticator.class);
                OrganizationService orgService = (OrganizationService) container.getComponentInstanceOfType(OrganizationService.class);

                User user = orgService.getUserHandler().findUserByName(login);
                if (user == null) {
                    user = orgService.getUserHandler().createUserInstance(login);               
                    user.setFirstName(login);
                    user.setLastName(login);
                    orgService.getUserHandler().createUser(user, false);   
                    orgService.getUserHandler().saveUser(user, false);
                    //TODO: put some more integration code here
                }

                Identity identity = authenticator.createIdentity(login);

                Subject subject = new Subject();
                subject.getPrivateCredentials().add(passwd);
                subject.getPublicCredentials().add(new UsernameCredential(login));

                identity.setSubject(subject);
                identityRegistry.register(identity);

            } catch (Exception e) {
                e.getMessage();
            }
        }
    }

    protected ExoContainer getContainer() {
        // TODO set correct current container
        ExoContainer container = ExoContainerContext.getCurrentContainer();
        if (container instanceof RootContainer) {
            container = RootContainer.getInstance().getPortalContainer(portalContainerName);
        }
        return container;
    }

...

}

Basically, the bean retrieves user login and password from the InteractiveAuthenticationSuccessEvent object and tries to get the user from the organization service. In case he cannot find it in the repository, he simply creates it on the fly. In this example the user is created with just a few details, but you can put some custom integration code with the external realm here, and create the user with all the details (email, birth date, roles, etc.) it seems appropriate to you. After that, the bean creates an Identity object with the help of the authenticator service, populates it with a subject containing the user credentials and registers it. That's all we have to do to make the portal aware of the user logging in.

Registering our bean is done the usual way in security-context.xml file:

...
<beans:bean id="myEventHandler" class="org.exoplatform.spring.security.web.SpringSecurityEventHandler" />
...

Part of the problem is the question of security context propagation between on one side the portal webapp and at the other side the portlets webapps. This means that the security context has to be available in the portlet side allowing the application logic to deal the with current user principal and granted authorities. By default, Spring security uses a thread local variable to partially achieve this. But a problem may arise due to the fact that the portal invokes the portlet through a webapp cross context call. This means that it can lead to a class cast exceptions (two different classloaders involved), or that the security context is simply not propagated at all. To accommodate this, we will need to set up two request filters, one at the portal webapp side and the other at the portlet webapp side and use the http request to propagate the context in between.

We will use the spring security extensible filter chain to plug in our filter.

package org.exoplatform.spring.security.web;

...

public class PortalSideSecurityContextFilter extends SpringSecurityFilter {

    @Override
    protected void doFilterHttp(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        //fill request with security context
        SecurityContext context = SecurityContextHolder.getContext();       
         request.setAttribute(HttpSessionContextIntegrationFilter.SPRING_SECURITY_CONTEXT_KEY, context);       
        
         //fill request with security last exception
         Object e = request.getSession().getAttribute(AbstractProcessingFilter.SPRING_SECURITY_LAST_EXCEPTION_KEY);
        request.setAttribute(AbstractProcessingFilter.SPRING_SECURITY_LAST_EXCEPTION_KEY, e);
       
        chain.doFilter(request, response);
    }

    public int getOrder() {
        // TODO Auto-generated method stub
        return 0;
    }
}

The PortalSideSecurityContextFilter simply fills the request with the security context and security last exception using the HttpSessionContextIntegrationFilter.SPRING_SECURITY_CONTEXT_KEY and AbstractProcessingFilter.SPRING_SECURITY_LAST_EXCEPTION_KEY attribute names. The portlet can have a look to the AbstractProcessingFilter.SPRING_SECURITY_LAST_EXCEPTION_KEY attribute to check if a security exception has occured.

The following lines in the security-context file register our custom filter in the chain at the last position.

...
   <beans:bean id="myCustomFilter" class="org.exoplatform.spring.security.web.PortalSideSecurityContextFilter">
      <custom-filter after="LAST" />
   </beans:bean>
...

In the portlet webapp we create a regular filter named PortletSideSecurityContextFilter.

package org.exoplatform.spring.security.web;

...

public class PortletSideSecurityContextFilter implements Filter {

    public void destroy() {       
       
    }

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
        Object object = request.getAttribute(HttpSessionContextIntegrationFilter.SPRING_SECURITY_CONTEXT_KEY);
        SecurityContext context = (SecurityContext) serializeDeserialize(object);
        if (context != null) {
            SecurityContextHolder.setContext(context);
        } else {
            SecurityContextHolder.clearContext();
        }       
       
        filterChain.doFilter(request, response);
    }

    public void init(FilterConfig arg0) throws ServletException {       
       
    }

    private Object serializeDeserialize(Object obj) {
        Object result = null;       
        try {
            ByteArrayOutputStream bout = new ByteArrayOutputStream();
            ObjectOutputStream out = new ObjectOutputStream(bout);

            out.writeObject(obj);

            ByteArrayInputStream bin =    new ByteArrayInputStream(bout.toByteArray());
            ObjectInputStream in = new ObjectInputStream(bin);

            result = in.readObject();           
        } catch (Exception e) {
            //TODO: handle exception
        }
        return result;
    }
   
}

The PortletSideSecurityContextFilter retrieves the security context from the request and proceeds to a serialization/de-serialization of it to avoid a potential class cast exception that may occur when propagating an object across webapps. Then the context is simply set or cleared whether the context is null or not.

To register your filter simply add the following lines to your portlet webapp web.xml file.

...
   <filter>
      <filter-name>portletSideSecurityContextFilter</filter-name>
      <filter-class>org.exoplatform.spring.security.web.PortletSideSecurityContextFilter</filter-class>
   </filter>
     
   <filter-mapping>
      <filter-name>portletSideSecurityContextFilter</filter-name>
      <url-pattern>/*</url-pattern>
      <dispatcher>REQUEST</dispatcher>
      <dispatcher>INCLUDE</dispatcher>
      <dispatcher>FORWARD</dispatcher>
   </filter-mapping>
...

OrganizationService is the service that allows to access the Organization model. This model is composed of:

It is the basis of eXo personalization and authorizations in eXo and is used to all over the platform. The model is abstract and does not rely on any specific storage. Multiple implementations exist in eXo:

To create a custom organization service you need to implement a several interfaces and extend some classes which will be listed below.

First of all you need to create classes implementing the following interfaces (each of which represent a basic unit of organization service):

Note

After each set method is called the developer must call UserHandler.saveUser (GroupHandler.saveGroup, MembershipHandler.saveMembership etc.) method to persist the changes.

You can find examples of the mentioned above implementations at subversion server:

After you created basic organization service unit instances you need to create classess to handle them e.g. to persist changes, to add listener etc. For that purpose you need to implement a several interfaces correspondingly:

You can find examples of the mentioned above implementations at subversion server:

Finally you need to create your main custom organization service class. It must extend org.exoplatform.services.organization.BaseOrganizationService. BaseOrganizationService class contains organization service unit handlers as protected fields, so you can initialize them in accordance to your purposes. It also has org.exoplatform.services.organization.OrganizationService interface methods' implementations. This is the class you need to mention in the configuration file if you want to use your custom organization service.

You can find example of such class at subversion server: JCROrganizationServiceImpl.

Make sure that your custom organization service implementation is fully compliant with Organization Service TCK tests. Tests are available as maven artifact:

groupId - org.exoplatform.core

artifactId - exo.core.component.organization.tests

You can find TCK tests package source code here

Note

In order to be able to run unit tests you may need to configure the following maven plugins:

Check pom.xml file to find out one of the ways to configure maven project object model. More detailed description you can find in the dedicated section called "Organization Service TCK tests configuration"

Use the Organization Service Initializer to create users, groups and membership types by default.

<external-component-plugins>
    <target-component>org.exoplatform.services.organization.OrganizationService</target-component>
    <component-plugin>
      <name>init.service.listener</name>
      <set-method>addListenerPlugin</set-method>
      <type>org.exoplatform.services.organization.OrganizationDatabaseInitializer</type>
      <description>this listener populate organization data for the first launch</description>
      <init-params>
        <value-param>
          <name>checkDatabaseAlgorithm</name>
          <description>check database</description>
          <value>entry</value>
        </value-param>
        <value-param>
          <name>printInformation</name>
          <description>Print information init database</description>
          <value>false</value>
        </value-param>
        <object-param>
          <name>configuration</name>
          <description>description</description>
          <object type="org.exoplatform.services.organization.OrganizationConfig">
            <field name="membershipType">
              <collection type="java.util.ArrayList">
                <value>
                  <object type="org.exoplatform.services.organization.OrganizationConfig$MembershipType">
                    <field name="type">
                      <string>manager</string>
                    </field>
                    <field name="description">
                      <string>manager membership type</string>
                    </field>
                  </object>
                </value>
              </collection>
            </field>
            
            <field name="group">
              <collection type="java.util.ArrayList">
                <value>
                  <object type="org.exoplatform.services.organization.OrganizationConfig$Group">
                    <field name="name">
                      <string>platform</string>
                    </field>
                    <field name="parentId">
                      <string></string>
                    </field>
                    <field name="description">
                      <string>the /platform group</string>
                    </field>
                    <field name="label">
                      <string>Platform</string>
                    </field>
                  </object>
                </value>
                <value>
                  <object type="org.exoplatform.services.organization.OrganizationConfig$Group">
                    <field name="name">
                      <string>administrators</string>
                    </field>
                    <field name="parentId">
                      <string>/platform</string>
                    </field>
                    <field name="description">
                      <string>the /platform/administrators group</string>
                    </field>
                    <field name="label">
                      <string>Administrators</string>
                    </field>
                  </object>
                </value>
               </collection>
            </field>
            
            <field name="user">
              <collection type="java.util.ArrayList">
                <value>
                  <object type="org.exoplatform.services.organization.OrganizationConfig$User">
                    <field name="userName">
                      <string>root</string>
                    </field>
                    <field name="password">
                      <string>exo</string>
                    </field>
                    <field name="firstName">
                      <string>Root</string>
                    </field>
                    <field name="lastName">
                      <string>Root</string>
                    </field>
                    <field name="email">
                      <string>root@localhost</string>
                    </field>
                    <field name="groups">
                      <string>
                        manager:/platform/administrators
                      </string>
                    </field>
                  </object>
                </value>
              </collection>
            </field>
          </object>
        </object-param>
      </init-params>
    </component-plugin>
  </external-component-plugins>

Params for membership type:

Params for group:

Params for user:

The Organization Service provides a mechanism to receive notifications when:

  • A User is created, deleted or modified.

  • A Group is created, deleted or modified.

  • A Membership is created or removed.

This mechanism is very useful to cascade some actions when the organization model is modified. For example, it is currently used to :

  • Initialize the personal portal pages.

  • Initialize the personal calendars, address books and mail accounts in CS.

  • Create drives and personal areas in ECM.

To implement your own listener, you just need to write extend some existing listener classes. These classes define hooks that are invoked before or after operations are performed on organization model.

Registering the listeners is then achieved by using the ExoContainer plugin mechanism. Learn more about it on the Service Configuration for Beginners article.

To effectively register organization service's listeners you simply need to use the addListenerPlugin seer injector.

So, the easiest way to register your listeners is to pack them into a .jar and create a configuration file into it under mylisteners.jar!/conf/portal/configuration.xml

<?xml version="1.0" encoding="ISO-8859-1"?>
<configuration>
 <external-component-plugins>
  <target-component>org.exoplatform.services.organization.OrganizationService</target-component>
   <component-plugin>
    <name>myuserplugin</name>
    <set-method>addListenerPlugin</set-method>
    <type>org.example.MyUserListener</type>
    <description></description>      
   </component-plugin>
   <component-plugin>
    <name>mygroupplugin</name>
    <set-method>addListenerPlugin</set-method>
    <type>org.example.MyGroupListener</type>
    <description></description>      
   </component-plugin>
   <component-plugin>
    <name>mymembershipplugin</name>
    <set-method>addListenerPlugin</set-method>
    <type>org.example.MyMembershipListener</type>
    <description></description>      
   </component-plugin>
  </external-component-plugins>
<configuration>

Now, simply deploy the jar under $TOMCAT_HOME/lib and your listeners are ready!

Note

Be aware that you need to set proper RuntimePermission to be able to add or remove Listeners. To do that you need to grant the following permission for your code

permission java.lang.RuntimePermission "manageListeners"

When a user logged in portal in ConversationRegistry added ConversationSate for this user. ConversationState keeps user's Identity that is actual for logged in time. In this case even user's Membership updated in OrganizationService ConversationState still keeps old (not actual Identity). User must logged out and loggin in again to update Identity. To fix this issue, need add special listener in configuration of OrganizationServicer. This listener is extended MembershipEventListener.

Example of configuration.

<?xml version="1.0" encoding="ISO-8859-1"?>
<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">
  <external-component-plugins>
    <target-component>org.exoplatform.services.organization.OrganizationService</target-component>
.....
.....
    <component-plugin>
      <name>MembershipUpdateListener</name>
      <set-method>addListenerPlugin</set-method>
      <type>org.exoplatform.services.organization.impl.MembershipUpdateListener</type>
    </component-plugin>

   <external-component-plugins>
</configuration>

DB Schema Creator is responsible for creating database schema, using a DDL script inside service configuration or in an external file, calling:

  org.exoplatform.services.database.jdbc.DBSchemaCreator.createTables(String dsName, String script)

via

org.exoplatform.services.database.jdbc.CreateDBSchemaPlugin component plugin

A configuration example:

<component>
   <key>org.exoplatform.services.database.jdbc.DBSchemaCreator</key>
   <type>org.exoplatform.services.database.jdbc.DBSchemaCreator</type>
   <component-plugins>    
      <component-plugin> 
         <name>jcr.dbschema</name>
         <set-method>addPlugin</set-method>
         <type>org.exoplatform.services.database.jdbc.CreateDBSchemaPlugin</type>
         <init-params>
            <value-param>
               <name>data-source</name>
               <value>jdbcjcr</value>
            </value-param>
            <value-param>
               <name>script-file</name>
               <value>conf/storage/jcr-mjdbc.sql</value>
            </value-param>  
         </init-params>    
      </component-plugin>
  ........

An example of a DDL script:

CREATE TABLE JCR_MITEM(
        ID VARCHAR(255) NOT NULL PRIMARY KEY, 
        VERSION INTEGER NOT NULL, 
        PATH VARCHAR(1024) NOT NULL
        );
CREATE INDEX JCR_IDX_MITEM_PATH ON JCR_MITEM(PATH);

As usual, it is quite simple to use our configuration XML syntax to configure and parametrize different Databases for eXo tables but also for your own use.

The default DB configuration uses HSQLDB, a Java Database quite useful for demonstrations.

<component> 
   <key>org.exoplatform.services.database.HibernateService</key>
   <jmx-name>exo-service: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.show_sql" value="false"/>
         <property name="hibernate.cglib.use_reflection_optimizer" value="true"/>
         <property name="hibernate.connection.url" value="jdbc:hsqldb:file:../temp/data/portal"/>
         <property name="hibernate.connection.driver_class" value="org.hsqldb.jdbcDriver"/>
         <property name="hibernate.connection.autocommit" value="true"/>
         <property name="hibernate.connection.username" value="sa"/>
         <property name="hibernate.connection.password" value=""/>
         <property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/>
         <property name="hibernate.c3p0.min_size" value="5"/>
         <property name="hibernate.c3p0.max_size" value="20"/>
         <property name="hibernate.c3p0.timeout" value="1800"/>
         <property name="hibernate.c3p0.max_statements" value="50"/>
      </properties-param>
   </init-params>
</component>

In the init parameter section, we define the default hibernate properties including the DB URL, the driver and the credentials in use.

For any portals that configuration can be overridden, depending on the needs of your environment.

Several databases have been tested and can be used in production....which is not the case of HSQLDB, HSQLDB can only be used for development environments and for demonstrations.

You may decide to make eXo users to be mapped to an existing directory. eXo provides a flexible implementation of its OrganizationService on top of LDAP. It can be used on any LDAP compliant directory and even Active Directory. This page will guide you how to configure eXo Platform to work with your directory.

If you have an existing LDAP server, the eXo predefined settings will likely not match your directory structure. eXo LDAP organization service implementation was written with flexibility in mind and can certainly be configured to meet your requirements.

The configuration is done in ldap-configuration.xml file, and this section will explain the numerous parameters it contains.

Firstly, start by connection settings which will tell eXo how to connect to your directory server. These settings are very close to JNDI API context parameters. This configuration is activated by the init-param ldap.config of service LDAPServiceImpl.

<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>ldap://127.0.0.1:389,10.0.0.1:389</string></field>
        <field name="rootdn"><string>CN=Manager,DC=exoplatform,DC=org</string></field>
        <field name="password"><string>secret</string></field>
        <!-- field  name="authenticationType"><string>simple</string></field-->           
        <field name="version"><string>3</string></field>
        <field  name="referralMode"><string>follow</string></field>            
        <!-- field  name="serverName"><string>active.directory</string></field-->
        <field name="minConnection"><int>5</int></field>
        <field name="maxConnection"><int>10</int></field>
        <field name="timeout"><int>50000</int></field>
      </object>
    </object-param>
  </init-params>
</component>
  • providerURL: LDAP server URL (see PROVIDER_URL). For multiple ldap servers, use comma separated list of host:port (Ex. ldap://127.0.0.1:389,10.0.0.1:389).

  • rootdn: dn of user that will be used by the service to authenticate on the server (see SECURITY_PRINCIPAL).

  • password: password for user rootdn (see SECURITY_CREDENTIALS).

  • authenticationType: type of authentication to be used (see SECURITY_AUTHENTICATION). Use one of none, simple, strong. Default is simple.

  • version: LDAP protocol version (see java.naming.ldap.version). Set to 3 if your server supports LDAP V3.

  • referalMode: one of follow, ignore,throw (see REFERRAL).

  • serverName: you will need to set this to active.directory in order to work with Active Directory servers. Any other value will be ignore and the service will act as on a standard LDAP.

  • maxConnection: the maximum number of connections per connection identity that can be maintained concurrently.

  • minConnection: the number of connections per connection identity to create when initially creating a connection for the identity.

  • timeout: the number of milliseconds that an idle connection may remain in the pool without being closed and removed from the pool.

Next, you need to configure the eXo OrganizationService to tell him how the directory is structured and how to interact with it. This is managed by a couple of of init-params : ldap.userDN.key and ldap.attribute.mapping in file ldap-configuration.xml (by default located at portal.war/WEB-INF/conf/organization)

<component>
  <key>org.exoplatform.services.organization.OrganizationService</key>
  <type>org.exoplatform.services.organization.ldap.OrganizationServiceImpl</type>
  [...]
  <init-params>
    <value-param>
      <name>ldap.userDN.key</name>
      <description>The key used to compose user DN</description>
      <value>cn</value>
    </value-param>
    <object-param>
      <name>ldap.attribute.mapping</name>
      <description>ldap attribute mapping</description>
      <object type="org.exoplatform.services.organization.ldap.LDAPAttributeMapping">
      [...]
    </object-param>
  </init-params>
  [...]
</component>

ldap.attribute.mapping maps your ldap to eXo. At first there are two main parameters to configure in it:

<field name="baseURL"><string>dc=exoplatform,dc=org</string></field>
<field name="ldapDescriptionAttr"><string>description</string></field>

Other parameters are discussed in the following sections.

Here are the main parameters to map eXo users to your directory :

<field name="userURL"><string>ou=users,ou=portal,dc=exoplatform,dc=org</string></field>
<field name="userObjectClassFilter"><string>objectClass=person</string></field>
<field name="userLDAPClasses"><string>top,person,organizationalPerson,inetOrgPerson</string></field>

Example :

uid=john,cn=People,o=MyCompany,c=com

However, if users exist deeply under userURL, eXo will be able to retrieve them.

Example :

uid=tom,ou=France,ou=EMEA,cn=People,o=MyCompany,c=com

Example : john and tom will be recognized as valid eXo users but EMEA and France entries will be ignored in the following subtree :

uid=john,cn=People,o=MyCompany,c=com
  objectClass: person
  …
ou=EMEA,cn=People,o=MyCompany,c=com
  objectClass: organizationalUnit
  …
    ou=France,ou=EMEA,cn=People,o=MyCompany,c=com
      objectClass: organizationalUnit
      …
        uid=tom,ou=EMEA,cn=People,o=MyCompany,c=com
          objectClass: person
          …

When creating a new user, an entry will be created with the given objectClass attributes. The classes must at least define cn and any attribute refernced in the user mapping.

Example : Adding the user Marry Simons could produce :

uid=marry,cn=users,ou=portal,dc=exoplatform,dc=org
  objectclass: top
  objectClass: person
  objectClass: organizationalPerson
  objectClass: inetOrgPerson
  …

eXo groups can be mapped to organizational or applicative groups defined in your directory.

<field name="groupsURL"><string>ou=groups,ou=portal,dc=exoplatform,dc=org</string></field>
<field name="groupLDAPClasses"><string>top,organizationalUnit</string></field>
<field name="groupObjectClassFilter"><string>objectClass=organizationalUnit</string></field>

Groups can be structured hierarchically under groupsURL.

Example: Groups communication, communication/marketing and communication/press would map to :

ou=communication,ou=groups,ou=portal,dc=exoplatform,dc=org
…
  ou=marketing,ou=communication,ou=groups,ou=portal,dc=exoplatform,dc=org
  …            
  ou=press,ou=communication,ou=groups,ou=portal,dc=exoplatform,dc=org                          
  …

When creating a new group, an entry will be created with the given objectClass attributes. The classes must define at least the required attributes: ou, description and l.

Example : Adding the group human-resources could produce:

ou=human-resources,ou=groups,ou=portal,dc=exoplatform,dc=org
  objectclass: top
  objectClass: organizationalunit
  ou: human-resources
  description: The human resources department
  l: Human Resources
  …

Example : groups WebDesign, WebDesign/Graphists and Sales could be retrieved in :

l=Paris,dc=sites,dc=mycompany,dc=com
  …
  ou=WebDesign,l=Paris,dc=sites,dc=mycompany,dc=com
  …
    ou=Graphists,WebDesign,l=Paris,dc=sites,dc=mycompany,dc=com
    …
l=London,dc=sites,dc=mycompany,dc=com
  …
  ou=Sales,l=London,dc=sites,dc=mycompany,dc=com
  …

Membership types are the possible roles that can be assigned to users in groups.

<field name="membershipTypeURL"><string>ou=memberships,ou=portal,dc=exoplatform,dc=org</string></field>          
<field name="membershipTypeLDAPClasses"><string>top,organizationalRole</string></field>
<field name="membershipTypeNameAttr"><string>cn</string></field>

eXo stores membership types in a flat structure under membershipTypeURL.

Example : Roles manager, user, admin and editor could by defined by the subtree :

ou=roles,ou=portal,dc=exoplatform,dc=org
…
  cn=manager,ou=roles,ou=portal,dc=exoplatform,dc=org
  …
  cn=user,ou=roles,ou=portal,dc=exoplatform,dc=org
  …
  cn=admin,ou=roles,ou=portal,dc=exoplatform,dc=org               
  …
  cn=editor,ou=roles,ou=portal,dc=exoplatform,dc=org
  …

When creating a new membership type, an entry will be created with the given objectClass attributes. The classes must define the required attributes : description, cn

Example : Adding membership type validator would produce :

cn=validator,ou=roles,ou=portal,dc=exoplatform,dc=org
  objectclass: top
  objectClass: organizationalRole
  …

Example : If membershipTypeNameAttr is 'cn', then role name is 'manager' for the following membership type entry :

cn=manager,ou=roles,ou=portal,dc=exoplatform,dc=org </pre>

Memberships are used to assign a role within a group. They are entries that are placed under the group entry of their scope group. Users in this role are defined as attributes of the membership entry.

Example: To designate tom as the manager of the group human-resources:

ou=human-resources,ou=groups,ou=portal,dc=exoplatform,dc=org
  …
  cn=manager,ou=human-resources,ou=groups,ou=portal,dc=exoplatform,dc=org
    member: uid=tom,ou=users,ou=portal,dc=exoplatform,dc=org
    …

The parameters to configure memberships are:

<field name="membershipLDAPClasses"><string>top,groupOfNames</string></field>
<field name="membershipTypeMemberValue"><string>member</string></field>                              
<field name="membershipTypeRoleNameAttr"><string>cn</string></field>
<field name="membershipTypeObjectClassFilter"><string>objectClass=organizationalRole</string></field>

When creating a new membership, an entry will be created with the given objectClass attributes. The classes must at least define the attribute designated by membershipTypeMemberValue.

Example : Adding membership validator would produce :

cn=validator,ou=human-resources,ou=groups,ou=portal,dc=exoplatform,dc=org
  objectclass: top
  objectClass: groupOfNames
  …

<pre> cn=validator,ou=human-resources,ou=groups,ou=portal,dc=exoplatform,dc=org objectclass: top objectClass: groupOfNames ... </pre>

Values should be a user dn.

Example: james and root have admin role within the group human-resources, would give:

cn=admin,ou=human-resources,ou=groups,ou=portal,dc=exoplatform,dc=org
  member: cn=james,ou=users,ou=portal,dc=exoplatform,dc=org
  member: cn=root,ou=users,ou=portal,dc=exoplatform,dc=org
  …

Example : In the following membership entry:

<pre> cn=manager,ou=human-resources,ou=groups,ou=portal,dc=exoplatform,dc=org </pre>

'cn' attribute is used to designate the 'manager' membership type. Which could also be said : The name of the role is given by 'cn' the attribute.

You can use rather complex filters.

Example: Here is a filter we used for a customer that needed to trigger a dynlist overlay on openldap.

(&amp;(objectClass=ExoMembership)(membershipURL=*)) 

Note: Pay attention to the xml escaping of the '&' (and) operator

Here is an alternative configuration for active directory that you can find in activedirectory-configuration.xml

here is how to use LDAPS protocol with Active Directory :

1 setup AD to use SSL:

    * add Active Directory Certificate Services role
    * install right certificate for DC machine

2 enable Java VM to use certificate from AD:

    * import root CA used in AD, to keystore, something like

      keytool -importcert -file 2008.cer -keypass changeit -keystore /home/user/java/jdk1.6/jre/lib/security/cacerts

    * set java options

      JAVA_OPTS="${JAVA_OPTS} -Djavax.net.ssl.trustStorePassword=changeit -Djavax.net.ssl.trustStore=/home/user/java/jdk1.6/jre/lib/security/cacerts"
[...]
  <component>
  <key>org.exoplatform.services.ldap.LDAPService</key>
[..]
        <object type="org.exoplatform.services.ldap.impl.LDAPConnectionConfig">         
         <!-- for multiple ldap servers, use comma seperated list of host:port (Ex. ldap://127.0.0.1:389,10.0.0.1:389) -->
    <!-- whether or not to enable ssl, if ssl is used ensure that the javax.net.ssl.keyStore & java.net.ssl.keyStorePassword properties are set -->
    <!-- exo portal default installed javax.net.ssl.trustStore with file is java.home/lib/security/cacerts-->
    <!-- ldap service will check protocol, if protocol is ldaps, ssl is enable (Ex. for enable ssl: ldaps://10.0.0.3:636 ;for disable ssl: ldap://10.0.0.3:389 ) -->
    <!-- when enable ssl, ensure server name is *.directory and port (Ex. active.directory) -->        
    <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>site</string></field>      
    <field  name="version"><string>3</string></field>             
       <field  name="referralMode"><string>ignore</string></field>                      
       <field  name="serverName"><string>active.directory</string></field>                  
         </object>
[..]
  <component>
    <key>org.exoplatform.services.organization.OrganizationService</key>
    [...]
        <object type="org.exoplatform.services.organization.ldap.LDAPAttributeMapping">                
          [...]
          <field  name="userAuthenticationAttr"><string>mail</string></field>
          <field  name="userUsernameAttr"><string>sAMAccountName</string></field>
          <field  name="userPassword"><string>unicodePwd</string></field> 
          <field  name="userLastNameAttr"><string>sn</string></field>
          <field  name="userDisplayNameAttr"><string>displayName</string></field>
          <field  name="userMailAttr"><string>mail</string></field>
          [..]
          <field  name="membershipTypeLDAPClasses"><string>top,group</string></field>
          <field  name="membershipTypeObjectClassFilter"><string>objectClass=group</string></field>
          [..]
          <field  name="membershipLDAPClasses"><string>top,group</string></field>
          <field  name="membershipObjectClassFilter"><string>objectClass=group</string></field>
        </object>
        [...]  
</component>  

If you use OpenLDAP, you may want to use the overlays. Here is how you can use the dynlist overlay to have memberships dynamically populated.

The main idea is to have your memberships populated dynamically by an ldap query. Thus, you no longer have to maintain manually the roles on users.

To configure the dynlist, add the following to your slapd.conf :

dynlist-attrset         ExoMembership membershipURL member

This snipet means : On entries that have ExoMembership class, use the URL defined in the value of attribute membershipURL as a query and populate results under the multivalues attribute member.

Now let's declare the corresponding schema (replace XXXXX to adapt to your own IANA code):

attributeType ( 1.3.6.1.4.1.XXXXX.1.59 NAME 'membershipURL' SUP memberURL )

membershipURL inherits from memberURL.

objectClass ( 1.3.6.1.4.1.XXXXX.2.12  NAME 'ExoMembership' SUP top MUST ( cn ) MAY (membershipURL $ member $ description ) )

ExoMembership must define cn and can have attributes :

  • membershipURL: trigger for the dynlist

  • member : attribute populated by the dynlist

  • description : used by eXo for display

# the TestGroup group
dn: ou=testgroup,ou=groups,ou=portal,o=MyCompany,c=com
objectClass: top
objectClass: organizationalUnit
ou: testgroup
l: TestGroup
description: the Test Group

On this group, we can bind an eXo membership where the overlay will occur:

# the manager membership on group TestGroup
dn: cn=manager, ou=TestGroup,ou=groups,ou=portal,o=MyCompany,c=com
objectClass: top
objectClass: ExoMembership
membershipURL: ldap:///ou=users,ou=portal,o=MyCompany,c=com??sub?(uid=*)
cn: manager

This dynlist assigns the role manager:/testgroup to any user.

The process of launching the Organization Service TCK tests against your Organization Service is quite easy. For instance you may add TCK tests to your maven project and launch them during unit testing phase. To do that you need to complete the next two steps:

Organization Service TCK tests are available as a separate maven artifact, so the first thing you need to do is to add this artifact as a dependency to your pom.xml file

      <dependency>
        <groupId>org.exoplatform.core</groupId>
        <artifactId>exo.core.component.organization.tests</artifactId>
        <version>2.4.3-GA</version>
        <classifier>sources</classifier>
        <scope>test</scope>
      </dependency>

You will also need to unpack tests as they are archieved within jar file. For this purpose you may use maven-dependency-plugin

     <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-dependency-plugin</artifactId>
         <executions>
            <execution>
               <id>unpack</id>
               <phase>generate-test-sources</phase>
               <goals>
                  <goal>unpack</goal>
               </goals>
               <configuration>
                  <artifactItems>
                     <artifactItem>
                        <groupId>org.exoplatform.core</groupId>
                        <artifactId>exo.core.component.organization.tests</artifactId>
                        <classifier>sources</classifier>
                        <type>jar</type>
                        <overWrite>false</overWrite>
                     </artifactItem>
                  </artifactItems>
                  <outputDirectory>${project.build.directory}/org-service-tck-tests</outputDirectory>
               </configuration>
            </execution>
         </executions>
      </plugin>

Note

Remember the value of outputDirectory parameter as you will need it later.

After you have unpacked the tests you need to add the tests sources and resources, use build-helper-maven-plugin

      <plugin>
         <groupId>org.codehaus.mojo</groupId>
         <artifactId>build-helper-maven-plugin</artifactId>
         <version>1.3</version>
         <executions>
            <execution>
               <id>add-test-resource</id>
               <phase>generate-test-sources</phase>
               <goals>
                  <goal>add-test-resource</goal>
               </goals>
               <configuration>
                  <resources>
                     <resource>
                        <directory>${project.build.directory}/org-service-tck-tests</directory>
                     </resource>
                  </resources>
               </configuration>
            </execution> 
            <execution>
               <id>add-test-source</id>
               <phase>generate-test-sources</phase>
               <goals>
                  <goal>add-test-source</goal>
               </goals>
               <configuration>
                  <sources>
                     <source>${project.build.directory}/org-service-tck-tests</source>
                  </sources>
               </configuration>
            </execution>
         </executions>
      </plugin> 

Note

directory and source parameter should point to the location you've specified in outputDirectory parameter just above.

You also need to include all TCK tests using maven-surefire-plugin

      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <configuration>
          ...
          <includes>
              <include>org/exoplatform/services/tck/organization/Test*.java</include>
          </includes>                   
          ...
        </configuration>
      </plugin>

As a result you should have TCK being launched during your next maven clean install. Example of configured pom.xml file you can find at svn server

TCK tests use standalone container, so to launch TCK tests propertly you will also need to add Organization Service as a standalone component. For that purpose use configuration file, which is to be located in 'src/test/java/conf/standalone/test-configuration.xml' by default, but its location can be changed by system property called orgservice.test.configuration.file. Add your Organization Service configuration with all needed components there.

In addition you need to populate your Organization Service with organization data (TCK tests are designed to use this data):

      <external-component-plugins>
        <target-component>org.exoplatform.services.organization.OrganizationService</target-component>
        <component-plugin>
          <name>init.service.listener</name>
          <set-method>addListenerPlugin</set-method>
          <type>org.exoplatform.services.organization.OrganizationDatabaseInitializer</type>
          <description>this listener populate organization data for the first launch</description>
          <init-params>      
            <value-param>
              <name>checkDatabaseAlgorithm</name>
              <description>check database</description>
              <value>entry</value>
            </value-param>      
            <value-param>
              <name>printInformation</name>
              <description>Print information init database</description>
              <value>false</value>
            </value-param> 
            <object-param>
              <name>configuration</name>
              <description>description</description>
              <object type="org.exoplatform.services.organization.OrganizationConfig">
                <field  name="membershipType">
                  <collection type="java.util.ArrayList">
                  	<value>
                      <object type="org.exoplatform.services.organization.OrganizationConfig$MembershipType">
                        <field  name="type"><string>manager</string></field>
                        <field  name="description"><string>manager membership type</string></field>
                      </object>
                    </value>
                    <value>
                      <object type="org.exoplatform.services.organization.OrganizationConfig$MembershipType">
                        <field  name="type"><string>member</string></field>
                        <field  name="description"><string>member membership type</string></field>
                      </object>
                    </value>                
                    <value>
                      <object type="org.exoplatform.services.organization.OrganizationConfig$MembershipType">
                        <field  name="type"><string>validator</string></field>
                        <field  name="description"><string>validator membership type</string></field>
                      </object>
                    </value>
                  </collection>
                </field>

                <field  name="group">
                  <collection type="java.util.ArrayList">             
                    <value>
                      <object type="org.exoplatform.services.organization.OrganizationConfig$Group">
                        <field  name="name"><string>platform</string></field>
                        <field  name="parentId"><string></string></field>
                        <field  name="description"><string>the /platform group</string></field>
                        <field  name="label"><string>Platform</string></field>                    
                      </object>
                    </value>
                    <value>
                      <object type="org.exoplatform.services.organization.OrganizationConfig$Group">
                        <field  name="name"><string>administrators</string></field>
                        <field  name="parentId"><string>/platform</string></field>
                        <field  name="description"><string>the /platform/administrators group</string></field>
                        <field  name="label"><string>Administrators</string></field>
                      </object>
                    </value>
                    <value>
                      <object type="org.exoplatform.services.organization.OrganizationConfig$Group">
                        <field  name="name"><string>users</string></field>
                        <field  name="parentId"><string>/platform</string></field>
                        <field  name="description"><string>the /platform/users group</string></field>
                        <field  name="label"><string>Users</string></field>
                      </object>
                    </value>
                    <value>
                      <object type="org.exoplatform.services.organization.OrganizationConfig$Group">
                        <field  name="name"><string>guests</string></field>
                        <field  name="parentId"><string>/platform</string></field>
                        <field  name="description"><string>the /platform/guests group</string></field>
                        <field  name="label"><string>Guests</string></field>
                      </object>
                    </value>
                    <value>
                      <object type="org.exoplatform.services.organization.OrganizationConfig$Group">
                        <field  name="name"><string>organization</string></field>
                        <field  name="parentId"><string></string></field>
                        <field  name="description"><string>the organization group</string></field>
                        <field  name="label"><string>Organization</string></field>
                      </object>
                    </value>
                    <value>
                      <object type="org.exoplatform.services.organization.OrganizationConfig$Group">
                        <field  name="name"><string>management</string></field>
                        <field  name="parentId"><string>/organization</string></field>
                        <field  name="description"><string>the /organization/management group</string></field>
                        <field  name="label"><string>Management</string></field>
                      </object>
                    </value>
                    <value>
                      <object type="org.exoplatform.services.organization.OrganizationConfig$Group">
                        <field  name="name"><string>executive-board</string></field>
                        <field  name="parentId"><string>/organization/management</string></field>
                        <field  name="description"><string>the /organization/management/executive-board group</string></field>
                        <field  name="label"><string>Executive Board</string></field>
                      </object>
                    </value>
                    <value>
                      <object type="org.exoplatform.services.organization.OrganizationConfig$Group">
                        <field  name="name"><string>human-resources</string></field>
                        <field  name="parentId"><string>/organization/management</string></field>
                        <field  name="description"><string>the /organization/management/human-resource group</string></field>
                        <field  name="label"><string>Human Resources</string></field>
                      </object>
                    </value>
                    <value>
                      <object type="org.exoplatform.services.organization.OrganizationConfig$Group">
                        <field  name="name"><string>communication</string></field>
                        <field  name="parentId"><string>/organization</string></field>
                        <field  name="description"><string>the /organization/communication group</string></field>
                        <field  name="label"><string>Communication</string></field>
                      </object>
                    </value>
                    <value>
                      <object type="org.exoplatform.services.organization.OrganizationConfig$Group">
                        <field  name="name"><string>marketing</string></field>
                        <field  name="parentId"><string>/organization/communication</string></field>
                        <field  name="description"><string>the /organization/communication/marketing group</string></field>
                        <field  name="label"><string>Marketing</string></field>
                      </object>
                    </value>
                    <value>
                      <object type="org.exoplatform.services.organization.OrganizationConfig$Group">
                        <field  name="name"><string>press-and-media</string></field>
                        <field  name="parentId"><string>/organization/communication</string></field>
                        <field  name="description"><string>the /organization/communication/press-and-media group</string></field>
                        <field  name="label"><string>Press and Media</string></field>
                      </object>
                    </value>
                    <value>
                      <object type="org.exoplatform.services.organization.OrganizationConfig$Group">
                        <field  name="name"><string>operations</string></field>
                        <field  name="parentId"><string>/organization</string></field>
                        <field  name="description"><string>the /organization/operations and media group</string></field>
                        <field  name="label"><string>Operations</string></field>
                      </object>
                    </value>
                    <value>
                      <object type="org.exoplatform.services.organization.OrganizationConfig$Group">
                        <field  name="name"><string>sales</string></field>
                        <field  name="parentId"><string>/organization/operations</string></field>
                        <field  name="description"><string>the /organization/operations/sales group</string></field>
                        <field  name="label"><string>Sales</string></field>
                      </object>
                    </value>
                    <value>
                      <object type="org.exoplatform.services.organization.OrganizationConfig$Group">
                        <field  name="name"><string>finances</string></field>
                        <field  name="parentId"><string>/organization/operations</string></field>
                        <field  name="description"><string>the /organization/operations/finances group</string></field>
                        <field  name="label"><string>Finances</string></field>
                      </object>
                    </value>
                    <value>
                      <object type="org.exoplatform.services.organization.OrganizationConfig$Group">
                        <field  name="name"><string>customers</string></field>
                        <field  name="parentId"><string></string></field>
                        <field  name="description"><string>the /customers group</string></field>
                        <field  name="label"><string>Customers</string></field>
                      </object>
                    </value>                
                    <value>
                      <object type="org.exoplatform.services.organization.OrganizationConfig$Group">
                        <field  name="name"><string>partners</string></field>
                        <field  name="parentId"><string></string></field>
                        <field  name="description"><string>the /partners group</string></field>
                        <field  name="label"><string>Partners</string></field>
                      </object>
                    </value>                
                  </collection>
                </field>

                <field  name="user">
                  <collection type="java.util.ArrayList">
                    <value>
                      <object type="org.exoplatform.services.organization.OrganizationConfig$User">
                        <field  name="userName"><string>root</string></field>
                        <field  name="password"><string>exo</string></field>
                        <field  name="firstName"><string>Root</string></field>
                        <field  name="lastName"><string>Root</string></field>
                        <field  name="email"><string>root@localhost</string></field>
                        <field  name="groups">
                          <string>
                          	manager:/platform/administrators,member:/platform/users,
                          	member:/organization/management/executive-board
                          </string>
                        </field>
                      </object>
                    </value>
                    
                    <value>
                      <object type="org.exoplatform.services.organization.OrganizationConfig$User">
                        <field  name="userName"><string>john</string></field>
                        <field  name="password"><string>exo</string></field>
                        <field  name="firstName"><string>John</string></field>
                        <field  name="lastName"><string>Anthony</string></field>
                        <field  name="email"><string>john@localhost</string></field>
                        <field  name="groups">
                          <string>
                          	member:/platform/administrators,member:/platform/users,
                          	manager:/organization/management/executive-board
                          </string>
                        </field>
                      </object>
                    </value>                                                        
                    <value>
                      <object type="org.exoplatform.services.organization.OrganizationConfig$User">
                        <field  name="userName"><string>marry</string></field>
                        <field  name="password"><string>exo</string></field>
                        <field  name="firstName"><string>Marry</string></field>
                        <field  name="lastName"><string>Kelly</string></field>
                        <field  name="email"><string>marry@localhost</string></field>
                        <field  name="groups">
                          <string>member:/platform/users</string>
                        </field>
                      </object>
                    </value>
                    <value>
                      <object type="org.exoplatform.services.organization.OrganizationConfig$User">
                        <field  name="userName"><string>demo</string></field>
                        <field  name="password"><string>exo</string></field>
                        <field  name="firstName"><string>Demo</string></field>
                        <field  name="lastName"><string>exo</string></field>
                        <field  name="email"><string>demo@localhost</string></field>
                        <field  name="groups">
                          <string>member:/platform/guests,member:/platform/users</string>
                        </field>
                      </object>
                    </value>                       
                  </collection>
                </field>
              </object>
            </object-param>
          </init-params>
        </component-plugin>
      </external-component-plugins>

      <external-component-plugins>
        <target-component>org.exoplatform.services.organization.OrganizationService</target-component>
         <component-plugin>
            <name>tester.membership.type.listener</name>
            <set-method>addListenerPlugin</set-method>
            <type>org.exoplatform.services.organization.MembershipTypeEventListener</type>
            <description>Membership type listerner for testing purpose</description>
         </component-plugin>
      </external-component-plugins>

Ultimately you will have a configuration file which determines standalone container and consists of Organization Service configuration and initialization data. You can find prepared test-configuration.xml file at svn

DocumentReaderService provides API to retrieve DocumentReader by mimetype.

DocumentReader lets the user fetch content of document as String or, in case of TikaDocumentReader, as Reader.

How TikaDocumentReaderService Impl configuration looks like:

<component>
      <key>org.exoplatform.services.document.DocumentReaderService</key>
      <type>org.exoplatform.services.document.impl.tika.TikaDocumentReaderServiceImpl</type>

      <!-- Old-style document readers -->
      <component-plugins>
         <component-plugin>
            <name>pdf.document.reader</name>
            <set-method>addDocumentReader</set-method>
            <type>org.exoplatform.services.document.impl.PDFDocumentReader</type>
            <description>to read the pdf inputstream</description>
         </component-plugin>

         <component-plugin>
            <name>document.readerMSWord</name>
            <set-method>addDocumentReader</set-method>
            <type>org.exoplatform.services.document.impl.MSWordDocumentReader</type>
            <description>to read the ms word inputstream</description>
         </component-plugin>

         <component-plugin>
            <name>document.readerMSXWord</name>
            <set-method>addDocumentReader</set-method>
            <type>org.exoplatform.services.document.impl.MSXWordDocumentReader</type>
            <description>to read the ms word inputstream</description>
         </component-plugin>

         <component-plugin>
            <name>document.readerMSExcel</name>
            <set-method>addDocumentReader</set-method>
            <type>org.exoplatform.services.document.impl.MSExcelDocumentReader</type>
            <description>to read the ms excel inputstream</description>
         </component-plugin>

         <component-plugin>
            <name>document.readerMSXExcel</name>
            <set-method>addDocumentReader</set-method>
            <type>org.exoplatform.services.document.impl.MSXExcelDocumentReader</type>
            <description>to read the ms excel inputstream</description>
         </component-plugin>

         <component-plugin>
            <name>document.readerMSOutlook</name>
            <set-method>addDocumentReader</set-method>
            <type>org.exoplatform.services.document.impl.MSOutlookDocumentReader</type>
            <description>to read the ms outlook inputstream</description>
         </component-plugin>

         <component-plugin>
            <name>PPTdocument.reader</name>
            <set-method>addDocumentReader</set-method>
            <type>org.exoplatform.services.document.impl.PPTDocumentReader</type>
            <description>to read the ms ppt inputstream</description>
         </component-plugin>

         <component-plugin>
            <name>MSXPPTdocument.reader</name>
            <set-method>addDocumentReader</set-method>
            <type>org.exoplatform.services.document.impl.MSXPPTDocumentReader</type>
            <description>to read the ms pptx inputstream</description>
         </component-plugin>

         <component-plugin>
            <name>document.readerHTML</name>
            <set-method>addDocumentReader</set-method>
            <type>org.exoplatform.services.document.impl.HTMLDocumentReader</type>
            <description>to read the html inputstream</description>
         </component-plugin>

         <component-plugin>
            <name>document.readerXML</name>
            <set-method>addDocumentReader</set-method>
            <type>org.exoplatform.services.document.impl.XMLDocumentReader</type>
            <description>to read the xml inputstream</description>
         </component-plugin>

         <component-plugin>
            <name>TPdocument.reader</name>
            <set-method>addDocumentReader</set-method>
            <type>org.exoplatform.services.document.impl.TextPlainDocumentReader</type>
            <description>to read the plain text inputstream</description>
            <init-params>
               <!--
                  values-param> <name>defaultEncoding</name> <description>description</description> <value>UTF-8</value>
                  </values-param
               -->
            </init-params>
         </component-plugin>

         <component-plugin>
            <name>document.readerOO</name>
            <set-method>addDocumentReader</set-method>
            <type>org.exoplatform.services.document.impl.OpenOfficeDocumentReader</type>
            <description>to read the OO inputstream</description>
         </component-plugin>

      </component-plugins>

      <init-params>
        <value-param>
          <name>tika-configuration</name>
          <value>jar:/conf/portal/tika-config.xml</value>
        </value-param>
      </init-params>

   </component>
</configuration>

tika-config.xml example:

<properties>

  <mimeTypeRepository magic="false"/>
  <parsers>

    <parser name="parse-dcxml" class="org.apache.tika.parser.xml.DcXMLParser">
      <mime>application/xml</mime>
      <mime>image/svg+xml</mime>
      <mime>text/xml</mime>
      <mime>application/x-google-gadget</mime>
    </parser>

    <parser name="parse-office" class="org.apache.tika.parser.microsoft.OfficeParser">
      <mime>application/excel</mime>
      <mime>application/xls</mime>
      <mime>application/msworddoc</mime>
      <mime>application/msworddot</mime>
      <mime>application/powerpoint</mime>
      <mime>application/ppt</mime>

      <mime>application/x-tika-msoffice</mime>
      <mime>application/msword</mime>
      <mime>application/vnd.ms-excel</mime>
      <mime>application/vnd.ms-excel.sheet.binary.macroenabled.12</mime>
      <mime>application/vnd.ms-powerpoint</mime>
      <mime>application/vnd.visio</mime>
      <mime>application/vnd.ms-outlook</mime>
    </parser>

    <parser name="parse-ooxml" class="org.apache.tika.parser.microsoft.ooxml.OOXMLParser">
      <mime>application/x-tika-ooxml</mime>
      <mime>application/vnd.openxmlformats-package.core-properties+xml</mime>
      <mime>application/vnd.openxmlformats-officedocument.spreadsheetml.sheet</mime>
      <mime>application/vnd.openxmlformats-officedocument.spreadsheetml.template</mime>
      <mime>application/vnd.ms-excel.sheet.macroenabled.12</mime>
      <mime>application/vnd.ms-excel.template.macroenabled.12</mime>
      <mime>application/vnd.ms-excel.addin.macroenabled.12</mime>
      <mime>application/vnd.openxmlformats-officedocument.presentationml.presentation</mime>
      <mime>application/vnd.openxmlformats-officedocument.presentationml.template</mime>
      <mime>application/vnd.openxmlformats-officedocument.presentationml.slideshow</mime>
      <mime>application/vnd.ms-powerpoint.presentation.macroenabled.12</mime>
      <mime>application/vnd.ms-powerpoint.slideshow.macroenabled.12</mime>
      <mime>application/vnd.ms-powerpoint.addin.macroenabled.12</mime>
      <mime>application/vnd.openxmlformats-officedocument.wordprocessingml.document</mime>
      <mime>application/vnd.openxmlformats-officedocument.wordprocessingml.template</mime>
      <mime>application/vnd.ms-word.document.macroenabled.12</mime>
      <mime>application/vnd.ms-word.template.macroenabled.12</mime>
    </parser>

    <parser name="parse-html" class="org.apache.tika.parser.html.HtmlParser">
      <mime>text/html</mime>
    </parser>

    <parser mame="parse-rtf" class="org.apache.tika.parser.rtf.RTFParser">
      <mime>application/rtf</mime>
    </parser>

    <parser name="parse-pdf" class="org.apache.tika.parser.pdf.PDFParser">
      <mime>application/pdf</mime>
    </parser>

    <parser name="parse-txt" class="org.apache.tika.parser.txt.TXTParser">
      <mime>text/plain</mime>
      <mime>script/groovy</mime>
      <mime>application/x-groovy</mime>
      <mime>application/x-javascript</mime>
      <mime>application/javascript</mime>
      <mime>text/javascript</mime>
    </parser>

    <parser name="parse-openoffice" class="org.apache.tika.parser.opendocument.OpenOfficeParser">

      <mime>application/vnd.oasis.opendocument.database</mime>

      <mime>application/vnd.sun.xml.writer</mime>
      <mime>application/vnd.oasis.opendocument.text</mime>
      <mime>application/vnd.oasis.opendocument.graphics</mime>
      <mime>application/vnd.oasis.opendocument.presentation</mime>
      <mime>application/vnd.oasis.opendocument.spreadsheet</mime>
      <mime>application/vnd.oasis.opendocument.chart</mime>
      <mime>application/vnd.oasis.opendocument.image</mime>
      <mime>application/vnd.oasis.opendocument.formula</mime>
      <mime>application/vnd.oasis.opendocument.text-master</mime>
      <mime>application/vnd.oasis.opendocument.text-web</mime>
      <mime>application/vnd.oasis.opendocument.text-template</mime>
      <mime>application/vnd.oasis.opendocument.graphics-template</mime>
      <mime>application/vnd.oasis.opendocument.presentation-template</mime>
      <mime>application/vnd.oasis.opendocument.spreadsheet-template</mime>
      <mime>application/vnd.oasis.opendocument.chart-template</mime>
      <mime>application/vnd.oasis.opendocument.image-template</mime>
      <mime>application/vnd.oasis.opendocument.formula-template</mime>
      <mime>application/x-vnd.oasis.opendocument.text</mime>
      <mime>application/x-vnd.oasis.opendocument.graphics</mime>
      <mime>application/x-vnd.oasis.opendocument.presentation</mime>
      <mime>application/x-vnd.oasis.opendocument.spreadsheet</mime>
      <mime>application/x-vnd.oasis.opendocument.chart</mime>
      <mime>application/x-vnd.oasis.opendocument.image</mime>
      <mime>application/x-vnd.oasis.opendocument.formula</mime>
      <mime>application/x-vnd.oasis.opendocument.text-master</mime>
      <mime>application/x-vnd.oasis.opendocument.text-web</mime>
      <mime>application/x-vnd.oasis.opendocument.text-template</mime>
      <mime>application/x-vnd.oasis.opendocument.graphics-template</mime>
      <mime>application/x-vnd.oasis.opendocument.presentation-template</mime>
      <mime>application/x-vnd.oasis.opendocument.spreadsheet-template</mime>
      <mime>application/x-vnd.oasis.opendocument.chart-template</mime>
      <mime>application/x-vnd.oasis.opendocument.image-template</mime>
      <mime>application/x-vnd.oasis.opendocument.formula-template</mime>
    </parser>

    <parser name="parse-image" class="org.apache.tika.parser.image.ImageParser">
      <mime>image/bmp</mime>
      <mime>image/gif</mime>
      <mime>image/jpeg</mime>
      <mime>image/png</mime>
      <mime>image/tiff</mime>
      <mime>image/vnd.wap.wbmp</mime>
      <mime>image/x-icon</mime>
      <mime>image/x-psd</mime>
      <mime>image/x-xcf</mime>
    </parser>

    <parser name="parse-class" class="org.apache.tika.parser.asm.ClassParser">
      <mime>application/x-tika-java-class</mime>
    </parser>

    <parser name="parse-mp3" class="org.apache.tika.parser.mp3.Mp3Parser">
      <mime>audio/mpeg</mime>
    </parser>

    <parser name="parse-midi" class="org.apache.tika.parser.audio.MidiParser">
      <mime>application/x-midi</mime>
      <mime>audio/midi</mime>
    </parser>

    <parser name="parse-audio" class="org.apache.tika.parser.audio.AudioParser">
      <mime>audio/basic</mime>
      <mime>audio/x-wav</mime>
      <mime>audio/x-aiff</mime>
    </parser>

  </parsers>

</properties>

Digest access authentication is one of the agreed methods a web server can use to negotiate credentials with a web user's browser. It uses encryption to send the password over the network which is safer than the Basic access authentication that sends plaintext.

Technically digest authentication is an application of MD5 cryptographic hashing with usage of nonce values to discourage cryptanalysis. It uses the HTTP protocol.

To configure you server to use DIGEST authentication we need to edit serverside JAAS module implementation configuration file.