/*
* JBoss, Home of Professional Open Source
* Copyright 2006, JBoss Inc., and others contributors as indicated
* by the @authors tag. All rights reserved.
* See the copyright.txt in the distribution for a
* full listing of individual contributors.
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU Lesser General Public License, v. 2.1.
* This program is distributed in the hope that it will be useful, but WITHOUT A
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public License,
* v.2.1 along with this distribution; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
* (C) 2005-2006, JBoss Inc.
*/
package org.jboss.soa.esb.smooks;
import org.jboss.soa.esb.ConfigurationException;
import org.jboss.soa.esb.actions.AbstractActionPipelineProcessor;
import org.jboss.soa.esb.actions.ActionLifecycleException;
import org.jboss.soa.esb.actions.ActionProcessingException;
import org.jboss.soa.esb.helpers.ConfigTree;
import org.jboss.soa.esb.listeners.message.MessageDeliverException;
import org.jboss.soa.esb.message.Message;
import org.jboss.soa.esb.message.MessagePayloadProxy;
import org.jboss.soa.esb.message.Properties;
import org.jboss.soa.esb.smooks.resource.SmooksResource;
import org.milyn.Smooks;
import org.milyn.routing.file.FileListAccessor;
import org.milyn.util.CollectionsUtil;
import org.milyn.profile.Profile;
import org.milyn.container.ExecutionContext;
import org.milyn.container.plugin.PayloadProcessor;
import org.milyn.container.plugin.ResultType;
import org.milyn.event.report.HtmlReportGenerator;
import java.io.IOException;
import java.io.Serializable;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* <a href="http://milyn.codehaus.org/Smooks">Smooks</a> action pipeline processor.
* <p/>
* Usage:
* <pre>
* <action name="transform" class="org.jboss.soa.esb.smooks.SmooksAction">
* <property name="smooksConfig" value="smooks-config.xml" />
* </action>
* </pre>
*
* <u>Optional properties:</u>
* <pre>
* <property name="get-payload-location" value="input" />
* <property name="set-payload-location" value="ouput" />
* <property name="mappedContextObjects" value="object1,object2" />
* <property name="resultType" value="STRING" />
* <property name="reportPath" value="/tmp/smooks-report.html" />
* <property name="messageProfile" value="fromServiceA" />
* </pre>
*
* Description of configuration properties:
* <ul>
* <li><i>smooksConfig</i> - the Smooks configuration file. Can be a path on the file system or on the classpath.</li>
* <li><i>get-payload-location</i> - the body location which contains the object to be transformed. See {@link MessagePayloadProxy}.</li>
* <li><i>set-payload-location</i> - the body location where the transformed object will be placed. See {@link MessagePayloadProxy}.</li>
* <li><i>mappedContextObjects</i> - comma separated list of Smooks {@link ExecutionContext} objects to be mapped into the {@link #EXECUTION_CONTEXT_ATTR_MAP_KEY} Map on the ESB Message. Default is an empty list.
* See <a href="#exposing-smooks-exec">Exposing the Smooks ExecutionContext to other ESB Actions</a>.</li>
* <li><i>resultType</i> - type of result expected from Smooks ("STRING", "BYTES", "JAVA", "NORESULT"). Default is "STRING". For more
* on specifying and controlling the Smooks filtering result, see <a href="#specify-result">Specifying the Source and Result Types</a>.</li>
* <li><i>javaResultBeanId</i> - specifies the Smooks bean context beanId to be mapped as the result when the resultType is "JAVA". If not specified,
* the whole bean context bean Map is mapped as the result.</li>
* <li><i>reportPath</i> - specifies the path and file name for generating a Smooks Execution Report. This is a development tool.</li>
* <li><i>messageProfile</i> - specifies the default message "profile" name to be used in {@link Smooks#createExecutionContext(String) creation of the Smooks ExecutionContext}.
* See <a href="#profiling">Message Profiling</a>.</li>
* </ul>
*
* <h3 id="exposing-smooks-exec">Exposing the Smooks {@link ExecutionContext} to other ESB Actions</h3>
* After Smooks has performed the filtering operation on the message payload, it can map the contents of the
* {@link ExecutionContext} onto a Map on the the ESB message, making it available to other actions in the ESB.
* This Map can be accessed by using the {@link #EXECUTION_CONTEXT_ATTR_MAP_KEY} key as follows:
* <pre>
* message.getBody().get( SmooksAction.EXECUTION_CONTEXT_ATTR_MAP_KEY );
* </pre>
* The {@link ExecutionContext} objects to be mapped must be specified in the "mappedContextObjects" action property.
* The objects must also be {@link Serializable}.
*
* <h3 id="specify-result">Specifying the Source and Result Types</h3>
* From the ESB Message data type, this action is able to automatically determine the type of
* {@link javax.xml.transform.Source} to use (via the Smooks {@link org.milyn.container.plugin.PayloadProcessor}). The
* {@link javax.xml.transform.Result} type to be used can be specified via the "resultType"
* property, as outlined above.
* <p/>
* It is expected that the above mechanism will be satisfactory for most usecase, but not all.
* For the other usecases, this action supports {@link org.milyn.container.plugin.SourceResult}
* payloads on the ESB Message. This allows you to manually specify other Source and Result
* types, which is of particular interest with respect to the Result type e.g. for streaming
* the Result to a file etc.
*
* <h3 id="profiling">Message Profiling</h3>
* Smooks Profiling allows you to use a single Smooks instance to transform multiple
* source messages. As an example, imagine a situation where messages of different formats
* are delivered to a Service. Before consuming the messages, the Service needs to transform
* these message payloads to a common format. To accomplish this, you can use profiling.
* <p/>
* The action can have the default profile name configured through the "messageProfile"
* property. Each incoming ESB message can specify it's profile name through the
* message property of the same name ("messageProfile"). For more on profiling, see
* the <a href="http://milyn.codehaus.org/Smooks+Example+-+profiling">profiling example</a>.
*
* @author <a href="mailto:tom.fennelly@jboss.com">tom.fennelly@jboss.com</a>
* @author <a href="mailto:daniel.bevenius@gmail.com">daniel.bevenius@gmail.com</a>
*/
public class SmooksAction extends AbstractActionPipelineProcessor
{
public static final String EXECUTION_CONTEXT_ATTR_MAP_KEY = "SmooksExecutionContext";
private Smooks smooks;
private String defaultMessageProfile;
private PayloadProcessor payloadProcessor;
private MessagePayloadProxy payloadProxy;
private String reportPath;
private Set<String> mappedContextObjects;
// public
public SmooksAction( final ConfigTree configTree ) throws ConfigurationException
{
final String smooksConfig = configTree.getRequiredAttribute("smooksConfig");
try
{
smooks = SmooksResource.createSmooksResource(smooksConfig);
}
catch (Exception e)
{
throw new ConfigurationException("Failed to create Smooks instance for config '" + smooksConfig + "'.", e);
}
// Get the default profile from the config...
defaultMessageProfile = configTree.getAttribute(Properties.MESSAGE_PROFILE, Profile.DEFAULT_PROFILE);
// Create the Smooks PayloadProcessor...
String resultTypeConfig = configTree.getAttribute("resultType", "STRING");
ResultType resultType;
try {
resultType = ResultType.valueOf(resultTypeConfig);
} catch(IllegalArgumentException e) {
throw new ConfigurationException("Invalid 'resultType' config value '" + resultTypeConfig + "'. Valid values are: " + Arrays.asList(ResultType.values()));
}
payloadProcessor = new PayloadProcessor( smooks, resultType );
if(resultType == ResultType.JAVA) {
String javaResultBeanId = configTree.getAttribute("javaResultBeanId");
if(javaResultBeanId != null) {
payloadProcessor.setJavaResultBeanId(javaResultBeanId);
}
}
payloadProxy = new MessagePayloadProxy( configTree );
reportPath = configTree.getAttribute("reportPath");
String mappedContextObjectsConfig = configTree.getAttribute("mappedContextObjects");
String[] configuredMappedContextObjects;
if(mappedContextObjectsConfig != null) {
configuredMappedContextObjects = mappedContextObjectsConfig.split(",");
} else {
configuredMappedContextObjects = new String[0];
}
// Convert to a Set...
mappedContextObjects = CollectionsUtil.toSet(configuredMappedContextObjects);
// Add the default mapped objects to the set...
mappedContextObjects.add(FileListAccessor.class.getName() + "#allListFileName"); // Constant is private on FileListAccessor (Grrrr!!!)
}
/**
* Executes the actual Smooks tranformation.
*
* @param message The ESB Message object
*
* @return The ESB Message object with the output of the transformation.
*
*/
public Message process( final Message message) throws ActionProcessingException
{
// Create Smooks ExecutionContext.
final String messageProfofile = (String) message.getProperties().getProperty(Properties.MESSAGE_PROFILE, defaultMessageProfile);
final ExecutionContext executionContext = smooks.createExecutionContext(messageProfofile);
if(reportPath != null) {
try {
executionContext.setEventListener(new HtmlReportGenerator(reportPath));
} catch (IOException e) {
throw new ActionProcessingException("Failed to create HtmlReportGenerator instance.", e);
}
}
// Use the Smooks PayloadProcessor to execute the transformation....
final Object payload;
try {
payload = payloadProxy.getPayload(message);
} catch (MessageDeliverException e) {
throw new ActionProcessingException("MessgeDeliveryException while trying to retrieve the message payload:", e);
}
final Object newPayload = payloadProcessor.process( payload, executionContext );
// Set the ExecutionContext's attributes on the message instance so other actions can access them.
message.getBody().add( EXECUTION_CONTEXT_ATTR_MAP_KEY, getSerializableObjectsMap( executionContext.getAttributes() ) );
try {
payloadProxy.setPayload( message, newPayload );
} catch (MessageDeliverException e) {
throw new ActionProcessingException("MessgeDeliveryException while trying to retrieve the message payload:", e);
}
return message;
}
@Override
public void destroy() throws ActionLifecycleException
{
SmooksResource.closeSmooksResource(smooks);
super.destroy();
}
// protected
/**
* Will return a Map containing only the Serializable objects
* listed in the "mappedContextObjects" action property.
*
* @param smooksAttribuesMap - Map containing attributes from the Smooks ExecutionContext
* @return Map - Map containing only the Serializable ExecutionContext objects listed in
* the "mappedContextObjects" action property.
*/
@SuppressWarnings( "unchecked" )
protected Map getSerializableObjectsMap( final Map smooksAttribuesMap )
{
Map smooksExecutionContextMap = new HashMap();
for(String mappedContextObject : mappedContextObjects) {
Object contextObject = smooksAttribuesMap.get(mappedContextObject);
if(contextObject instanceof Serializable) {
smooksExecutionContextMap.put( mappedContextObject, contextObject );
}
}
return smooksExecutionContextMap;
}
}