/*
* JBoss, Home of Professional Open Source
* Copyright 2006, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY 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 along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.internal.soa.esb.services.routing.cbr;
import java.io.ByteArrayInputStream;
import java.io.StringReader;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.apache.log4j.Logger;
import org.jboss.internal.soa.esb.assertion.AssertArgument;
import org.jboss.internal.soa.esb.services.rules.util.RulesContext;
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.body.content.BytesBody;
import org.jboss.soa.esb.util.XPathNamespaceContext;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
/**
* Domain Specific Language helper. Right now this supports the use of XPath, but this class can
* be beefed up upo to use other technologies as well.
* <p/>
*
* @author kstam@redhat.com
* @author <a href="mailto:dbevenius@redhat.com">Daniel Bevenius</a>
*
*/
public class DslHelper
{
private static Logger log = Logger.getLogger(DslHelper.class);
/**
* The name of the boolean map context.
*/
private static final String BOOLEAN_MAP = "DslHelper.BooleanMap" ;
/**
* The name of the number map.
*/
private static final String NUMBER_MAP = "DslHelper.NumberMap" ;
/**
* The name of the string map.
*/
private static final String STRING_MAP = "DslHelper.StringMap" ;
/**
* The name of the node map.
*/
private static final String NODE_MAP = "DslHelper.NodeMap" ;
/**
* The name of the node list map.
*/
private static final String NODE_LIST_MAP = "DslHelper.NodeListMap" ;
/** XPath instance */
private static XPathFactory xpf = XPathFactory.newInstance();
private static MessagePayloadProxy payloadProxy;
static {
payloadProxy = new MessagePayloadProxy(new ConfigTree("config"), new String[] {BytesBody.BYTES_LOCATION}, new String[] {BytesBody.BYTES_LOCATION});
}
/**
* Uses XPath to evalutate if the XPath expression is true or false.
* This is the equivalent of calling selectAsBoolean( message, xpathExp, null).
*
* @param message -
* the ESB Message which body content will be used
* @param xpathExp -
* XPath expression
* @return Boolean
* true if the XPath expression evalutes to true
* @throws XPathExpressionException
* represents an error in an XPath expression
*/
public static Boolean selectAsBoolean(final Message message, final String xpathExp ) throws XPathExpressionException
{
return selectAsBoolean( message, xpathExp, null );
}
/**
* Uses XPath to evalutate if the XPath expression is true or false.
*
* @param message -
* the ESB Message which body content will be used
* @param xpathExp -
* XPath expression
* @return Boolean
* true if the XPath expression evalutes to true
* @param namespaces -
* Map of namespaces to be used in the xpath expression. Key=prefix,value = uri
* @throws XPathExpressionException
* represents an error in an XPath expression
*/
@SuppressWarnings("unchecked")
public static Boolean selectAsBoolean(final Message message, final String xpathExp, final Map<String,String> namespaces ) throws XPathExpressionException
{
Map<String, Boolean> booleanMap = (Map<String, Boolean>)RulesContext.getContext(BOOLEAN_MAP) ;
if (booleanMap != null)
{
final Boolean result = booleanMap.get(xpathExp) ;
if (result != null)
{
return result ;
}
}
else
{
booleanMap = new HashMap<String, Boolean>() ;
RulesContext.setContext(BOOLEAN_MAP, booleanMap) ;
}
XPath xpath = getXPath( namespaces );
Boolean value = (Boolean) xpath.evaluate( xpathExp, getInputSource(message), XPathConstants.BOOLEAN);
booleanMap.put(xpathExp, value) ;
return value;
}
/**
* Uses XPath to select the Number matched by the XPath expression.
* <p/>
* This is the equivalent of calling selectAsNumber( message, xpathExp, null).
*
* @param message -
* the ESB Message which body content will be used
* @param xpathExp -
* XPath expression
* @return Number -
* the Number if XPath found a match, or null if no match was found.
* @throws XPathExpressionException
* represents an error in an XPath expression
*/
public static Number selectAsNumber(final Message message, final String xpathExp ) throws XPathExpressionException
{
return selectAsNumber( message, xpathExp, null );
}
/**
* Uses XPath to select the Number matched by the XPath expression.
*
* @param message -
* the ESB Message which body content will be used
* @param xpathExp -
* XPath expression
* @param namespaces -
* Map of namespaces to be used in the xpath expression. Key=prefix,value = uri
* @return Number -
* the Number if XPath found a match, or null if no match was found.
* @throws XPathExpressionException
* represents an error in an XPath expression
*/
@SuppressWarnings("unchecked")
public static Number selectAsNumber(final Message message, final String xpathExp, final Map<String,String> namespaces ) throws XPathExpressionException
{
Map<String, Number> numberMap = (Map<String, Number>)RulesContext.getContext(NUMBER_MAP) ;
if (numberMap != null)
{
final Number result = numberMap.get(xpathExp) ;
if (result != null)
{
return result ;
}
}
else
{
numberMap = new HashMap<String, Number>() ;
RulesContext.setContext(NUMBER_MAP, numberMap) ;
}
final XPath xpath = getXPath( namespaces );
final Number number = (Number) xpath.evaluate( xpathExp, getInputSource(message), XPathConstants.NUMBER);
numberMap.put(xpathExp, number) ;
return number;
}
/**
* Uses XPath to select the String matched by the XPath expression.
* <p/>
* This is the equivalen of calling selectAsString( message, xpathExp, null).
*
* @param message -
* the ESB Message which body content will be used
* @param xpathExp -
* XPath expression
* @return String -
* the String if XPath found a match, or null if no match was found.
* @throws XPathExpressionException
* represents an error in an XPath expression
*/
public static String selectAsString(final Message message, final String xpathExp ) throws XPathExpressionException
{
return selectAsString( message, xpathExp, null );
}
/**
* Uses XPath to select the String matched by the XPath expression.
*
* @param message -
* the ESB Message which body content will be used
* @param xpathExp -
* XPath expression
* @param namespaces -
* Map of namespaces to be used in the xpath expression. Key=prefix,value = uri
* @return String -
* the String if XPath found a match, or null if no match was found.
* @throws XPathExpressionException
* represents an error in an XPath expression
*/
@SuppressWarnings("unchecked")
public static String selectAsString(final Message message, final String xpathExp, final Map<String,String> namespaces ) throws XPathExpressionException
{
Map<String, String> stringMap = (Map<String, String>)RulesContext.getContext(STRING_MAP) ;
if (stringMap != null)
{
final String result = stringMap.get(xpathExp) ;
if (result != null)
{
return result ;
}
}
else
{
stringMap = new HashMap<String, String>() ;
RulesContext.setContext(STRING_MAP, stringMap) ;
}
final XPath xpath = getXPath( namespaces );
final String string = (String) xpath.evaluate( xpathExp, getInputSource(message), XPathConstants.STRING);
stringMap.put(xpathExp, string) ;
return string;
}
/**
* Uses XPath to select the Node matched by the XPath expression.
* <p/>
* This is the equivalent of calling selectAsNode( message, xpathExp null).
*
* @param message -
* the ESB Message which body content will be used
* @param xpathExp -
* XPath expression
* @return Node -
* the Node if XPath found a match, or null if no match was found.
* @throws XPathExpressionException
* represents an error in an XPath expression
*/
public static Node selectAsNode(final Message message, final String xpathExp ) throws XPathExpressionException
{
return selectAsNode( message, xpathExp, null );
}
/**
* Uses XPath to select the Node matched by the XPath expression.
* <p/>
*
* @param message -
* the ESB Message which body content will be used
* @param xpathExp -
* XPath expression
* @param namespaces -
* Map of namespaces to be used in the xpath expression. Key=prefix,value = uri
* @return Node -
* the Node if XPath found a match, or null if no match was found.
* @throws XPathExpressionException
* represents an error in an XPath expression
*/
@SuppressWarnings("unchecked")
public static Node selectAsNode(final Message message, final String xpathExp, final Map<String,String> namespaces ) throws XPathExpressionException
{
Map<String, Node> nodeMap = (Map<String, Node>)RulesContext.getContext(NODE_MAP) ;
if (nodeMap != null)
{
final Node result = nodeMap.get(xpathExp) ;
if (result != null)
{
return result ;
}
}
else
{
nodeMap = new HashMap<String, Node>() ;
RulesContext.setContext(NODE_MAP, nodeMap) ;
}
final XPath xpath = getXPath( namespaces );
final Node node = (Node) xpath.evaluate( xpathExp, getInputSource(message), XPathConstants.NODE);
nodeMap.put(xpathExp, node) ;
return node;
}
/**
* Uses XPath to select the NodeList matched by the XPath expression.
* <p/>
* This is the equivalent of calling selectAsNodeList( message, xpathExp null).
*
* @param message -
* the ESB Message which body content will be used
* @param xpathExp -
* XPath expression
* @return NodeList -
* the NodeList if XPath found a match, or null if no match was found.
* @throws XPathExpressionException
* represents an error in an XPath expression
*/
public static NodeList selectAsNodeList( final Message message, final String xpathExp ) throws XPathExpressionException
{
return selectAsNodeList( message, xpathExp, null );
}
/**
* Uses XPath to select the NodeList matched by the XPath expression.
* <p/>
*
* @param message -
* the ESB Message which body content will be used
* @param xpathExp -
* XPath expression
* @param namespaces -
* Map of namespaces to be used in the xpath expression. Key=prefix,value = uri
* @return NodeList -
* the NodeList if XPath found a match, or null if no match was found.
* @throws XPathExpressionException
* represents an error in an XPath expression
*/
@SuppressWarnings("unchecked")
public static NodeList selectAsNodeList( final Message message, final String xpathExp, Map<String,String> namespaces ) throws XPathExpressionException
{
Map<String, NodeList> nodeListMap = (Map<String, NodeList>)RulesContext.getContext(NODE_LIST_MAP) ;
if (nodeListMap != null)
{
final NodeList result = nodeListMap.get(xpathExp) ;
if (result != null)
{
return result ;
}
}
else
{
nodeListMap = new HashMap<String, NodeList>() ;
RulesContext.setContext(NODE_LIST_MAP, nodeListMap) ;
}
final XPath xpath = getXPath( namespaces );
final NodeList nodeList = (NodeList) xpath.evaluate(xpathExp, getInputSource(message), XPathConstants.NODESET);
log.info("XPath [" + xpathExp + "], nr of matches : " + nodeList.getLength());
nodeListMap.put(xpathExp, nodeList) ;
return nodeList;
}
/**
* Uses XPath to look for the occurence of a certain node, specified in the XPath expression.
* This can be used to find out if the Message object contains the node specified by the XPath
* expression.
* <p/>
* This is the equivalent of calling xmlContentMatches( message, xpathExp, null).
*
* @param message -
* the ESB Message which body content will be used.
* @param xpathExp -
* XPath expression to find a node.
* @return true
* if the node is found and false in all other cases.
* @throws XPathExpressionException
* represents an error in an XPath expression
*/
public static boolean xmlContentMatches(final Message message, final String xpathExp) throws XPathExpressionException
{
return xmlContentMatches( message, xpathExp, null );
}
/**
* Uses XPath to look for the occurence of a certain node, specified in the XPath expression.
* This can be used to find out if the Message object contains the node specified by the XPath
* expression.
*
* @param message -
* the ESB Message which body content will be used.
* @param xpathExp -
* XPath expression to find a node.
* @param namespaces -
* Map of namespaces to be used in the xpath expression. Key=prefix,value = uri
* @return true
* if the node is found and false in all other cases.
* @throws XPathExpressionException
* represents an error in an XPath expression
*/
public static boolean xmlContentMatches(final Message message, final String xpathExp, final Map<String,String> namespaces ) throws XPathExpressionException
{
return selectAsNode( message, xpathExp, namespaces ) != null ;
}
/**
* Uses XPath to look for any occurence of a certain tag, specific in the xpath expression.
* This can be used to find out if the Message object contains the node specified by the XPath
* Note, that this method cannot be used with a boolean expression, use {@link #selectAsBoolean(Message, String)}
* for that.
* <p/>
* This is the equivalent of calling xmlContentExists( message, xpathExp, null).
*
* @param message -
* the ESB Message which body content will be used.
* @param xpathExp -
* XPath expression to find a node.
* @return true
* if one or more nodes are found and false in all other cases.
* @throws XPathExpressionException
* represents an error in an XPath expression
*/
public static boolean xmlContentExists(final Message message, final String xpathExp) throws XPathExpressionException
{
return xmlContentExists( message, xpathExp, null );
}
/**
* Uses XPath to look for any occurence of a certain tag, specific in the xpath expression.
* This can be used to find out if the Message object contains the node specified by the XPath
* Note, that this method cannot be used with a boolean expression, use {@link #selectAsBoolean(Message, String)}
* for that.
*
* @param message -
* the ESB Message which body content will be used.
* @param xpathExp -
* XPath expression to find a node.
* @param namespaces -
* Map of namespaces to be used in the xpath expression. Key=prefix,value = uri
* @return true
* if one or more nodes are found and false in all other cases.
* @throws XPathExpressionException
* represents an error in an XPath expression
*/
public static boolean xmlContentExists(final Message message, final String xpathExp, final Map<String,String> namespaces ) throws XPathExpressionException
{
final NodeList nodeList = selectAsNodeList( message, xpathExp, namespaces );
return nodeList == null ? false : nodeList.getLength() > 0 ;
}
/**
* Uses XPath to look for the occurence of a certain tag, specific in the xpath expression.
* </p>
* Note that {@link #selectAsBoolean(Message, String)} can be used instead of this method
* and the XPath equality operator can be used in the XPath expression:
* <br>
* <pre>{@code
* String xpathExp = "/Order/OrderLines/OrderLine/Product/@productId = 364";
* }</pre>
* <br>
* <p/>
* This is the equivalent of calling xmlContentEquals( message, xpathExp, null).
*
* @param message -
* the ESB Message which body content will be used.
* @param xpathExp -
* XPath expression to find a node.
* @return true -
* if the node is found and false in all other cases.
* @throws XPathExpressionException
* represents an error in an XPath expression
*/
public static boolean xmlContentEquals(final Message message, final String xpathExp, final String value ) throws XPathExpressionException
{
return xmlContentEquals( message, xpathExp, value, null );
}
/**
* Uses XPath to look for the occurence of a certain tag, specific in the xpath expression.
* </p>
* Note that {@link #selectAsBoolean(Message, String)} can be used instead of this method
* and the XPath equality operator can be used in the XPath expression:
* <br>
* <pre>{@code
* String xpathExp = "/Order/OrderLines/OrderLine/Product/@productId = 364";
* }</pre>
* <br>
*
* @param message -
* the ESB Message which body content will be used.
* @param xpathExp -
* XPath expression to find a node.
* @param namespaces -
* Map of namespaces to be used in the xpath expression. Key=prefix,value = uri
* @return true -
* if the node is found and false in all other cases.
* @throws XPathExpressionException
* represents an error in an XPath expression
*/
public static boolean xmlContentEquals(final Message message, final String xpathExp, final String value, final Map<String,String> namespaces ) throws XPathExpressionException
{
final String xpathResult = selectAsString( message, xpathExp, namespaces );
return xpathResult == null ? false : xpathResult.equals( value );
}
/**
* Uses XPath to look for the occurence of a certain tag, specific in the xpath expression.
* <p/>
* This is the equivalent of calling xmlContentGreaterThan( message, xpathExp, value, null).
*
* @param message -
* the ESB Message which body content will be used.
* @param xpathExp -
* XPath expression to find a node.
* @return true
* if the node is found and false in all other cases.
* @throws XPathExpressionException
* represents an error in an XPath expression
*/
public static boolean xmlContentGreaterThan( final Message message, final String xpathExp, final String value) throws XPathExpressionException
{
return xmlContentGreaterThan( message, xpathExp, value, null );
}
/**
* Uses XPath to look for the occurence of a certain tag, specific in the xpath expression.
*
* @param message -
* the ESB Message which body content will be used.
* @param xpathExp -
* XPath expression to find a node.
* @param namespaces -
* Map of namespaces to be used in the xpath expression. Key=prefix,value = uri
* @return true
* if the node is found and false in all other cases.
* @throws XPathExpressionException
* represents an error in an XPath expression
*/
public static boolean xmlContentGreaterThan( final Message message, final String xpathExp, final String value, final Map<String,String> namespaces) throws XPathExpressionException
{
final String xpathResult = (String) selectAsString( message, xpathExp, namespaces );
if ( xpathResult != null && !"".equals( xpathResult ) )
return parseDouble( xpathResult ) > parseDouble( value );
else
return false;
}
/**
* Uses XPath to look for the occurence of a certain tag, specific in the xpath expression.
* <p/>
* This is the equivalent of calling xmlContentLessThan( message, xpathExp, value, null).
*
* @param message -
* the ESB Message which body content will be used.
* @param xpathExp -
* XPath expression to find a node.
* @return true
* if the node is found and false in all other cases.
* @throws XPathExpressionException
* represents an error in an XPath expression
*/
public static boolean xmlContentLessThan( final Message message, final String xpathExp, final String value) throws XPathExpressionException
{
return xmlContentLessThan( message, xpathExp, value, null );
}
/**
* Uses XPath to look for the occurence of a certain tag, specific in the xpath expression.
*
* @param message -
* the ESB Message which body content will be used.
* @param xpathExp -
* XPath expression to find a node.
* @return true
* if the node is found and false in all other cases.
* @throws XPathExpressionException
* represents an error in an XPath expression
*/
public static boolean xmlContentLessThan( final Message message, final String xpathExp, final String value, final Map<String,String> namespaces) throws XPathExpressionException
{
final String xpathResult = (String) selectAsString( message, xpathExp, namespaces );
if ( xpathResult != null && !"".equals( xpathResult ) )
return parseDouble( xpathResult ) < parseDouble( value );
else
return false;
}
/**
* Will take the passed in string of namespaces in the form "prefix=uri,prefix=uri".
*
* @param namespaces string of namespaces in the form "prefix=uri,prefix=uri"
* @return Map<String,String> where the key will be the namespace prefix and the value the uri
*/
public static Map<String, String> parseNamespaces( final String namespaces )
{
AssertArgument.isNotNullAndNotEmpty( namespaces, "namespaces" );
final String[] namespacesElements = namespaces.split( "," );
final Map<String,String> namespacesMap = new HashMap<String,String>();
for ( String ns : namespacesElements )
{
final String[] pairs = ns.split( "=" );
namespacesMap.put( pairs[0].trim(), pairs[1].trim() );
}
return namespacesMap;
}
private static double parseDouble( final String string ) throws XPathExpressionException
{
try
{
return Double.parseDouble( string );
}
catch (NumberFormatException e)
{
throw new XPathExpressionException("Could not parse value [" + string + "] to double" );
}
}
private static void setNamespaces( final XPath xpath, final Map<String,String> namespaces )
{
if ( namespaces == null )
return;
final XPathNamespaceContext namespaceContext = new XPathNamespaceContext();
for ( Entry<String, String> entry : namespaces.entrySet() )
namespaceContext.setMapping( entry.getKey(), entry.getValue() );
xpath.setNamespaceContext( namespaceContext );
}
private static InputSource getInputSource(Message message) throws XPathExpressionException
{
Object payload;
try
{
payload = payloadProxy.getPayload(message);
}
catch (MessageDeliverException e)
{
throw new XPathExpressionException(e);
}
if(payload instanceof byte[])
{
return new InputSource(new ByteArrayInputStream((byte[]) payload));
}
else if(payload instanceof String)
{
return new InputSource(new StringReader((String) payload));
}
else
{
throw new XPathExpressionException("Unsupport expression input object type: " + payload.getClass().getName());
}
}
private static XPath getXPath( final Map<String,String> namespaces )
{
final XPath xpath = xpf.newXPath();
setNamespaces( xpath, namespaces );
return xpath;
}
}