Obsolete Pages{{Obsolete}}
The official documentation is at: http://docs.alfresco.com
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.
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.
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.
The main idea was to create a Mule configuration that executes the following steps sequentially:
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
<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>
This solution had two main challenges:
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;
}
}