#5. Mule Variable Transformer

Every now and then, some properties are needed that add extra information like flags, custom values, etc to the Mule Message. These can have different scopes, which we’ll be covering towards the end of this post, and in Mule lingua franca are called “Variable Transformer” or simply “Variable“.

Usage – To set or remove a variable in the mule message or mule flow.
Persistence – Variables set with a variable transformer persist only for the current flow and cannot cross the transport barrier.

Once you have set a variable, you can invoke it using the flowVars map in a Mule expression. For example, if you have set a variable with name “FVname” and value “FVvalue”, you can later invoke that variable using the expression #[flowVars.FVname], which evaluates to FVvalue.

But before all that stuff, let’s first create a variable. Open the Anypoint studio and the config xml file, crate a flow and drag a Variable Transformer into it. We’ll take the following setup as an example:

variabletransofrmer

In the above flow, there is one HTTP connector(a listener), one variable transformer and one payload component. Using above flow we’ll learn how to configure the flow in a way that whatever the value variable has, will be rendered on the browser.

Double clicking on variable transformer, mule component configuration window will appear:

configurationforvariable1

To set the variable name and value check the radio option Set Variable, and enter the name, value for the variable:

configurationforvariable

Description of configuration is given below:

Field Value Description XML
Display Name Variable Customize to display a unique name for the transformer in your flow/application. doc:name=”Variable”
Operation Set Variable Select to set a new variable on your message (as shown in example screenshot above).
Name String or Mule Expression Specify the name for the variable that you are creating or identify the name of the variable that you are removing. variableName=”MuleVariable”
Value String or Mule Expression This field displays only if you are setting a new variable. Specify the value using either a string or a Mule expression. value=”This is mule variable”

The xml for the transformer looks like:
<set-variable variableName="MuleVariable" value="This is mule variable" doc:name="Variable"/>

After the execution of this flow (i.e. running the server, and hitting the port, on which the http listener is configured, by a web-browser), it will show “This is mule variable” message on the browser.

The variable can hold any user defined value like a Boolean, a String, an Integer, or values directly extracted from mule message or payload like #[message.payload] or
#[message.inboundProperties.’http.query.params’.configID].

Now, comes the part that we love the most. The internal working.
For Variable Transformer, it all starts with the method process of AbstractTransformer class. It is a base class for all transformers.

process – This method invokes message processors.

Implementation of method is given below:

public MuleEvent process(MuleEvent event) throws MuleException
{
    if (event != null && event.getMessage() != null)
    {
        try
        {
            MuleMessage message = event.getMessage();
            message.applyTransformers(event, this);
            if (message instanceof DefaultMessageCollection)
            {
                if (((DefaultMessageCollection) message).isInvalidatedPayload())
                {
                    if (logger.isDebugEnabled())
                    {
                        logger.debug("Transformed message is an invalidated message collection. Creating new message with payload: " + event.getMessage().getPayload());
                    }
                    MuleMessage newMessage = new DefaultMuleMessage(message.getPayload(), message, message.getMuleContext());
                    event = new DefaultMuleEvent(newMessage, event);
                }
            }
        }
        catch (Exception e)
        {
            throw new TransformerMessagingException(event, this, e);
        }
    }
    return event;
}

The program flow goes to applyTransformers method.
applyTransformers – It will apply a list of transformers to the payload of the message. This *will* change the payload of the message. This method provides the only way to alter the paylaod of this message without recreating a copy of the message. The implementation for this method is:


public void applyTransformers(MuleEvent event, Transformer... transformers) throws MuleException
{
    applyTransformers(event, Arrays.asList(transformers), null);
}

public void applyTransformers(MuleEvent event, List<? extends Transformer> transformers, Class<?> outputType) throws MuleException
{
    if (!transformers.isEmpty())
        applyAllTransformers(event, transformers);

    if (null != outputType && !getPayload().getClass().isAssignableFrom(outputType))
        setPayload(getPayload(DataTypeFactory.create(outputType)));
}

Above method invokes another method applyAllTransformers of DefaultMessage class.
applyAllTransformers – This method is used to transform message for all the transformers that we used in our mule flow.


protected void applyAllTransformers(MuleEvent event, List<? extends Transformer> transformers) throws MuleException
{
    if (!transformers.isEmpty())
    {
        for (int index = 0; index < transformers.size(); index++)
        {
            Transformer transformer = transformers.get(index);

            Class<?> srcCls = getPayload().getClass();
            DataType<?> originalSourceType = DataTypeFactory.create(srcCls);

            if (transformer.isSourceDataTypeSupported(originalSourceType))
            {
                transformMessage(event, transformer);
            }
            //There are some lines of code which are for logging purposes in case of exception
        }
    }
}

In this, transformMessage method gets invoked.
transformMessage – This method is used to transform the message for the transformer, set transformed message as payload and set data type for that result.


 private void transformMessage(MuleEvent event, Transformer transformer) throws TransformerMessagingException, TransformerException
{
    Object result;
    if (transformer instanceof MessageTransformer)
    {
        result = ((MessageTransformer) transformer).transform(this, event); //Transforms the supplied data and returns the result
    }
    else
        result = transformer.transform(this); //Transforms the supplied data and returns the result
     // Update the RequestContext with the result of the transformation
    RequestContext.internalRewriteEvent(this, false);

    if (originalPayload == null && muleContext.getConfiguration().isCacheMessageOriginalPayload())
    {
        originalPayload = payload;
    }

    if (result instanceof MuleMessage)
    {
        if (!result.equals(this))
        {
            // Only copy the payload and properties of mule message
            // transformer result if the message is a different
            // instance
            synchronized (this)
            {
                MuleMessage resultMessage = (MuleMessage) result;
                setPayload(resultMessage.getPayload());
                originalPayload = resultMessage.getOriginalPayload();
                copyMessageProperties(resultMessage);
                copyAttachments(resultMessage);
            }
        }
    }
    else
    {
        setPayload(result); //set result as a payload for the flow
    }
    setDataType(transformer.getReturnDataType()); //set the datatype for the transformer
}

In this method, transform method gets invoked.
transform –  Transforms the supplied data and returns the result.

src – the data to transform
event – the event currently being processed

Implementation of the method is given below:


public Object transform(Object src, MuleEvent event) throws TransformerMessagingException
{
    return transform(src, getEncoding(src), event); //Transforms the supplied data and returns the result
}

This method again calls transform method with three parameters. where
Second parameter –  is used for the encoding to use by this transformer. many transformations will not need encoding unless dealing with text so you only need to use this method if you wish to customize the encoding.

public final Object transform(Object src, String enc, MuleEvent event) throws TransformerMessagingException
{
    DataType<?> sourceType = DataTypeFactory.create(src.getClass());
    if (!isSourceDataTypeSupported(sourceType))
    {
        if (isIgnoreBadInput())
        {
            logger.debug("Source type is incompatible with this transformer and property 'ignoreBadInput' is set to true, so the transformer chain will continue.");
            return src;
        }
        else
        {
            Message msg = CoreMessages.transformOnObjectUnsupportedTypeOfEndpoint(getName(),
            src.getClass(), endpoint);
            /// FIXME
            throw new TransformerMessagingException(msg, event, this);
        }
    }
    if (logger.isDebugEnabled())
    {
        logger.debug(String.format("Applying transformer %s (%s)", getName(), getClass().getName()));
        logger.debug(String.format("Object before transform: %s", StringMessageUtils.toString(src)));
    }

    MuleMessage message;
    if (src instanceof MuleMessage)
    {
        message = (MuleMessage) src;
    }
    else if (muleContext.getConfiguration().isAutoWrapMessageAwareTransform())
    {
        message = new DefaultMuleMessage(src, muleContext);
    }
    else
    {
        if (event == null)
        {
            throw new TransformerMessagingException(CoreMessages.noCurrentEventForTransformer(), event, this);
        }
        message = event.getMessage();
        if (!message.getPayload().equals(src))
        {
            throw new IllegalStateException("Transform payload does not match current event");
        }
    }

    Object result;
    try
    {
        result = transformMessage(message, enc); // used for transforming the message, set the scope and property for the variable.
    }
    catch (TransformerException e)
    {
        throw new TransformerMessagingException(e.getI18nMessage(), event, this, e);
    }

    if (result == null)
    {
        result = NullPayload.getInstance();
    }

    if (logger.isDebugEnabled())
    {
        logger.debug(String.format("Object after transform: %s", StringMessageUtils.toString(result)));
    }

    result = checkReturnClass(result, event); // method of AbstractMessageTransformer class to check whether the returned class is supported by this transformer
    return result;
}

In this transformMessage(event, src) gets invoked of AbstractAddVariablePropertyTransformer class. This is the final method for the variable transformer.  This method is use to transform the message, set the scope for the variable, and set the property for the variable.

AbstractAddVariablePropertyTransformer class extends AbstractMessageTransformer class. This is a transformer that has a reference to the current message. This message can be used to obtain properties associated with the current message which are useful to the transform.

AbstractMessageTransformer class implements an interface MessageTransformer which is used to intend to transform Mule message rather than arbitrary objects.
Implementation of the method is below:

public Object transformMessage(MuleMessage message, String outputEncoding) throws TransformerException
{
    Object keyValue = identifierEvaluator.resolveValue(message); // used to fetch the key of variable transformer
    String key = (keyValue == null ? null : keyValue.toString());
    if (key == null)
    {
        logger.error("Setting Null variable keys is not supported, this entry is being ignored");
    }
    else
    {
        Object value = valueEvaluator.resolveValue(message); // used to fetch the value of variable transformer
        if (value == null)
        {
            if (logger.isDebugEnabled())
            {
                logger.debug(MessageFormat.format(
                "Variable with key \"{0}\", not found on message using \"{1}\". Since the value was marked optional, nothing was set on the message for this variable",
                key, valueEvaluator.getRawValue()));
            }
        }
        else
        {
            message.setProperty(key, value, getScope()); // used to set the property(INVOCATION, SESSION, INBOUND, OUTBOUND) and scope for the variable
        }
    }
    return message;
}

The implementation for the setProperty(key, value, getScope()) method is given below:

public void setProperty(String key, Object value, PropertyScope scope)
{
    assertAccess(WRITE);
    if (key != null)
    {
        if (value != null)
        {
            properties.setProperty(key, value, scope);
        }
        else
        {
            if (logger.isDebugEnabled())
            {
                logger.debug("setProperty(key, value) called with null value; removing key: " + key);
            }
            properties.removeProperty(key);
        }
    }
    else
    {
        if (logger.isDebugEnabled())
        {
            logger.debug("setProperty(key, value) invoked with null key. Ignoring this entry");
        }
    }
}

It again calls setProperty(key, value, scope)  of the MessagePropertiesContext class, which takes three parameters and used to set the property(INBOUND, OUTBOUND, SESSION, INVOCATION) on the variable transformer according to scope of the variable.

MessagePropertiesContext class maintains a scoped map of properties. This means that certain properties will only be visible under some scopes. The scopes supported by Mule are:

org.mule.api.transport.PropertyScope.INBOUND – Contains properties that were on the message when it was received by Mule. This scope is read-only.
org.mule.api.transport.PropertyScope.INVOCATION – Any properties set on the invocation scope will be available to the current service but will not be attached to any outbound messages.
org.mule.api.transport.PropertyScope.OUTBOUND – Any properties set in this scope will be attached to any outbound messages resulting from this message. This is the default scope.
org.mule.api.transport.PropertyScope.SESSION – Any properties set on this scope will be added to the session. Note Session properties are not stored on the MuleMessage. This scope should only be used once a MuleEvent has been created as there is no MuleSession and therefore Session scope properties before this time

Implementation of the method is given below:

public void setProperty(String key, Object value, PropertyScope scope)
{
    if (!(value instanceof Serializable) && PropertyScope.SESSION.equals(scope))
    {
        logger.warn(CoreMessages.sessionPropertyNotSerializableWarning(key));
    }

    getScopedProperties(scope).put(key, value); //used to get the scope map according to scope

}

After this method it returns message to those class from where it was invoked and then returns back to the flow after setting the variable that can be accessed later at any time using #[flowVars.MuleVariable] or message.getInvocationProperty(“MuleVariable”) in a java component, if its scope was set inside a flow.

Hope, you found it interesting to get to know that how such a small component is handled at the core of Mule. After knowing this, we’re sure you’ll be able to set the variables with any scope right from your java(or other) components wherein you can handle every bit of it as per your needs rather than just using it through the xml and not having control over it.

Stay tuned for next posts.
Tame The Mule

Advertisements

One thought on “#5. Mule Variable Transformer

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s