Mule Alfresco Integration

cancel
Showing results for 
Search instead for 
Did you mean: 

Mule Alfresco Integration

resplin
Intermediate
0 0 1,847

Obsolete Pages{{Obsolete}}

The official documentation is at: http://docs.alfresco.com




Motivation


Mule and Alfresco are two leading Open Source products in their different areas. This article describes how these products could be integrated using Alfresco webservices.


Task


The concrete task is in this context:



Create a Mule service that logs to Alfresco in via its webservices and calls one of the security protected webservices.

Required Software


Alfresco 3 Stable (Community Edition) and Mule standalone 2.2.1 are correctly installed on the test machine.
We used Eclipse as editor to develop this service.


Solution Summary


The main idea was to create a Mule configuration that executes the following steps sequentially:



  • Read the Alfresco user name and password from the command line.
  • Send the user name and password to the Alfresco Authentication Webservice.
  • In case the login is successful, get the ticket and store it somewhere.
  • Call another Alfresco webservice and inject the ticket into the SOAP security header.

Solution Details


We created a Mule project in Eclipse based on the stockquote examples that are normally shipped with Mule. Since all examples are distributed with Maven support we just needed to access the project folder and type.



mvn eclipse:eclipse

After this you can just import the project into Eclipse and start working. All needed libraries should be OK in this project now with one exception: the Alfresco Webservice Client classes have still to be imported so that they are available to the Mule services. These you can download from Alfresco's subversion repository (svn://svn.alfresco.com/alfresco/HEAD/root/projects/web-service-client).

The first step was to create the configuration file and to declare the following services:



  • Console I/O - Just used to read the user name and password from the command line and route the output to the other services.
  • Alfresco Authentication - Calls the Alfresco Authentication Service
  • Ticket Extraction - Extracts the Alfresco authentication ticket from the returned Authentication result object
  • Alfresco Repository - Queries some repository service information using the authenticated ticket.

Mule Service Configuration



Console I/O


        <model name='Main'>
                <service name='Console I/O'>
                        <inbound>
                                <stdio:inbound-endpoint system='IN'
                                        synchronous='false' />
                        </inbound>
                        <outbound>
                                <chaining-router>
                                        <vm:outbound-endpoint path='AuthenticationService'
                                                synchronous='true' />
                                        <vm:outbound-endpoint path='DecomposeService'
                                                synchronous='true' />
                                        <vm:outbound-endpoint path='RepositoryService'
                                                synchronous='true' responseTransformer-refs='ObjectToXml'/>
                                        <stdio:outbound-endpoint system='OUT' />
                                </chaining-router>
                        </outbound>
                </service>
        </model>


Alfresco Authentication


        <model name='Authentication Model'>
                <service name='Authentication Service'>
                        <inbound>
                                <vm:inbound-endpoint path='AuthenticationService'
                                        synchronous='true' />
                        </inbound>
                        <component class='com.onepoint.mule.CredentialTransformer' />
                        <outbound>
                                <chaining-router>
                                        <axis:outbound-endpoint
                                                address='http://localhost:8090/alfresco/api/AuthenticationService?method=startSession'
                                                soapAction='#[methodNamespace]#[method]' serviceNamespace='http://www.alfresco.org/ws/service/authentication/1.0'
                                                style='RPC' synchronous='true' name='authService'>
                                                <axis:soap-service
                                                        interface='org.alfresco.webservice.authentication.AuthenticationService_Service' />
                                                <axis:soap-method
                                                        method='qname{startSession:http://www.alfresco.org/ws/service/authentication/1.0/}'>
                                                        <axis:soap-parameter parameter='username'
                                                                type='string' mode='IN' />
                                                        <axis:soap-parameter parameter='password'
                                                                type='string' mode='IN' />
                                                        <axis:soap-parameter parameter='startSessionReturn'
                                                                type='qname{auth:AuthenticationResult:http://www.alfresco.org/ws/service/authentication/1.0/}'
                                                                mode='OUT' />
                                                        <axis:soap-return
                                                                type='qname{AuthenticationResult:http://authentication.webservice.alfresco.org}' />
                                                </axis:soap-method>
                                        </axis:outbound-endpoint>
                                </chaining-router>
                        </outbound>
                </service>
        </model>


Ticket Extraction


        <model name='Decompose Model'>
                <service name='Ticket Extraction Service'>
                        <inbound>
                                <vm:inbound-endpoint path='DecomposeService'
                                        synchronous='true' />
                        </inbound>
                        <component class='com.onepoint.mule.TicketTransformer' />
                        <outbound>
                                <chaining-router>
                                        <stdio:outbound-endpoint system='OUT'
                                                synchronous='true' />
                                </chaining-router>
                        </outbound>
                </service>
        </model>


Alfresco Repository


        <model name='Repository Model'>
                <service name='Repository Service'>
                        <inbound>
                                <vm:inbound-endpoint path='RepositoryService'
                                        synchronous='true' />
                        </inbound>
                        <component class='com.onepoint.mule.TicketCallback' />
                        <outbound>
                                <pass-through-router>
                                        <axis:outbound-endpoint
                                                address='http://localhost:8090/alfresco/api/RepositoryService?method=getStores'
                                                soapAction='#[methodNamespace]#[method]' serviceNamespace='http://www.alfresco.org/ws/service/repository/1.0'
                                                style='RPC' synchronous='true' name='RepositoryService'>
                                                <axis:soap-service
                                                        interface='org.alfresco.webservice.repository.RepositoryService' />
                                                <axis:soap-method
                                                        method='qname{getStores:http://www.alfresco.org/ws/service/repository/1.0/}'>
                                                        <axis:soap-parameter parameter='getStoreResponse'
                                                                type='qname{cms:Store:http://www.alfresco.org/ws/model/content/1.0/}'
                                                                mode='OUT' />
                                                        <axis:soap-return
                                                                type='qname{Store:http://types.webservice.alfresco.org}' />
                                                </axis:soap-method>
                                        </axis:outbound-endpoint>
                                </pass-through-router>
                        </outbound>
                </service>

        </model>




Main Challenges


This solution had two main challenges:



       
  • The configuration of the Axis connector needs to be changed to declare there all the classes of the complex types that are being returned by the Alfresco webservices.
       
  • A new Mule Axis configuration file needs to be created that uses a special AXIS interceptor Java class. This class injects the ticket into the SOAP header that is sent to the Repository service.

This is the Axis connector declaration used to declare the class of the returned complex types and to replace the default class that generates the SOAP headers in Mule:


       <axis:connector name='axisConnector' clientConfig='alfresco-axis-client-config.wsdd'>
               <spring:property name='beanTypes'>
                       <spring:list>
                               <spring:value>org.alfresco.webservice.authentication.AuthenticationResult</spring:value>
                               <spring:value>org.alfresco.webservice.types.Store</spring:value>
                       </spring:list>
               </spring:property>
       </axis:connector>


Here is the content of alfresco-axis-client-config.wsdd:



<deployment xmlns='http://xml.apache.org/axis/wsdd/'
xmlns:java='http://xml.apache.org/axis/wsdd/providers/java'>
<globalConfiguration>
  <requestFlow>
   <handler
    type='java:com.onepoint.mule.alfresco.AlfrescoSoapHeadersHandler' />
  </requestFlow>
  <responseFlow>
   <handler
    type='java:org.mule.transport.soap.axis.extensions.MuleSoapHeadersHandler' />
  </responseFlow>
</globalConfiguration>
<transport name='http'
  pivot='java:org.mule.transport.soap.axis.extensions.UniversalSender' />
<transport name='https'
  pivot='java:org.mule.transport.soap.axis.extensions.UniversalSender' />
<transport name='tcp'
  pivot='java:org.mule.transport.soap.axis.extensions.UniversalSender' />
<transport name='ssl'
  pivot='java:org.mule.transport.soap.axis.extensions.UniversalSender' />
<transport name='vm'
  pivot='java:org.mule.transport.soap.axis.extensions.UniversalSender' />
<transport name='jms'
  pivot='java:org.mule.transport.soap.axis.extensions.UniversalSender' />
<transport name='xmpp'
  pivot='java:org.mule.transport.soap.axis.extensions.UniversalSender' />
<transport name='smtp'
  pivot='java:org.mule.transport.soap.axis.extensions.UniversalSender' />
<transport name='smtps'
  pivot='java:org.mule.transport.soap.axis.extensions.UniversalSender' />
<transport name='pop3'
  pivot='java:org.mule.transport.soap.axis.extensions.UniversalSender' />
<transport name='pop3s'
  pivot='java:org.mule.transport.soap.axis.extensions.UniversalSender' />
<transport name='imap'
  pivot='java:org.mule.transport.soap.axis.extensions.UniversalSender' />
<transport name='imaps'
  pivot='java:org.mule.transport.soap.axis.extensions.UniversalSender' />
</deployment>

Here is the content of the class that changes the SOAP headers so that the Alfresco ticket can be injected into the security elements:



/*
* $Id: $
*
* Copyright (c) 2009
* All rights reserved by One Point Consulting
*/
package com.onepoint.mule.alfresco;


import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Random;

import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPHeaderElement;
import javax.xml.soap.SOAPMessage;

import org.apache.axis.MessageContext;
import org.apache.axis.types.Time;
import org.mule.api.MuleEvent;
import org.mule.api.config.MuleProperties;
import org.mule.transport.soap.axis.extensions.MuleSoapHeadersHandler;

/**
* This is the class that injects a security header into the SOAP message.
* This is only done in case the ticket dispenser has a ticket.
* @author Gil Fernandes (Onepoint Consulting)
*
*/
public class AlfrescoSoapHeadersHandler extends MuleSoapHeadersHandler {

/**
  * Used to prevent serialization problems.
  */
private static final long serialVersionUID = 3919231181796772086L;

/**
  * The Web Service Security extension URI.
  */
private static final String WSE_URI = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd';

/**
  * The Web Service Security utility URI.
  */
private static final String WSU_URI = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd';

/**
  * The Web Service Security extension prefix.
  */
private static final String WS_SECURITY_NS = 'wsse';

/**
  * The Web Service Security utility prefix.
  */
private static final String WSU_SECURITY_NS = 'wsu';

/**
  * Processes the AXIS client request and adds a security header to the envelope in case
  * an Alfresco ticket is available.
  */
protected synchronized void processClientRequest(MessageContext msgContext,
   boolean setMustUnderstand) throws Exception {

  TicketDispender ticketDispender = new TicketDispender();
  String ticket = ticketDispender.get();
  if(!org.springframework.util.StringUtils.hasLength(ticket)) {
   super.processClientRequest(msgContext, setMustUnderstand);
   return;
  }
  ticketDispender.clear();
  SOAPMessage msg = msgContext.getMessage();
  if (msg == null) {
    return;
  }
  MuleEvent event = (MuleEvent) msgContext
   .getProperty(MuleProperties.MULE_EVENT_PROPERTY);
  if (event == null) {
   return;
  } else {
   synchronized (msgContext) {
    SOAPEnvelope sE = msgContext.getMessage().getSOAPPart()
     .getEnvelope();
    sE.addNamespaceDeclaration(WSU_SECURITY_NS,
     WSU_URI);
    sE.addNamespaceDeclaration(WS_SECURITY_NS,
      WSE_URI);
   
    SOAPHeader tt = sE.getHeader();
    SOAPElement securityElem = tt.addChildElement('Security', WS_SECURITY_NS);
    securityElem.removeNamespaceDeclaration(WS_SECURITY_NS);
   
    if(securityElem instanceof SOAPHeaderElement) {
     SOAPHeaderElement headerElement = (SOAPHeaderElement) securityElem;
     headerElement.setMustUnderstand(true);
     headerElement.setActor(null);
    }

    // Timestamp
    SOAPElement timestampElement = securityElem.addChildElement('Timestamp', WSU_SECURITY_NS);
    String timestampId = String.format('Timestamp-%d', genId());
    timestampElement.setAttribute(
      WSU_SECURITY_NS + ':Id',
      timestampId);
    SOAPElement createdElem = timestampElement.addChildElement(WSU_SECURITY_NS + ':Created');
    Calendar calendarNow = Calendar.getInstance();
    SimpleDateFormat sdf = new SimpleDateFormat('yyyy-MM-dd'T'');
    String day = sdf.format(calendarNow.getTime());
    Time axisTime = new Time(calendarNow);
   
    createdElem.addTextNode(day + axisTime.toString());
   
    SOAPElement expiresElem = timestampElement.addChildElement(WSU_SECURITY_NS + ':Expires');
    calendarNow.add(Calendar.MINUTE, 30);
    expiresElem.addTextNode(day + axisTime.toString());
   
    // UsernameToken
    SOAPElement userNameTokenElement = securityElem.addChildElement('UsernameToken', WS_SECURITY_NS);
   
    userNameTokenElement.addNamespaceDeclaration(WSU_SECURITY_NS,
      WSU_URI);
    String tokenId = String.format('UsernameToken-%d', genId());
    userNameTokenElement.setAttribute(
      WSU_SECURITY_NS + ':Id',
      tokenId);
    SOAPElement userNameElem = userNameTokenElement.addChildElement('Username', WS_SECURITY_NS);
    userNameElem.addTextNode('ticket');
    SOAPElement passwordElem = userNameTokenElement.addChildElement('Password', WS_SECURITY_NS);
    passwordElem.setAttribute('Type',
      'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText');
    passwordElem.addTextNode(ticket);
   
   }
  }
}


/**
  * Just generates a random number for the user name and time stamp token.
  * @return an integer with 8 digits.
  */
public static int genId() {
 
  Random r = new Random();
  int val = r.nextInt(10000000);
  val += 10000000;
  return val;
}

}


Integrations