View Javadoc

1   /*
2    * $Id: FilteringXmlMessageSplitter.java 12273 2008-07-10 13:25:32Z tcarlson $
3    * --------------------------------------------------------------------------------------
4    * Copyright (c) MuleSource, Inc.  All rights reserved.  http://www.mulesource.com
5    *
6    * The software in this package is published under the terms of the CPAL v1.0
7    * license, a copy of which has been included with this distribution in the
8    * LICENSE.txt file.
9    */
10  
11  package org.mule.module.xml.routing;
12  
13  import org.mule.DefaultMuleMessage;
14  import org.mule.api.MuleMessage;
15  import org.mule.api.endpoint.OutboundEndpoint;
16  import org.mule.module.xml.util.XMLUtils;
17  import org.mule.routing.outbound.AbstractMessageSplitter;
18  import org.mule.util.ExceptionUtils;
19  import org.mule.util.StringUtils;
20  
21  import java.util.Collections;
22  import java.util.HashMap;
23  import java.util.Iterator;
24  import java.util.LinkedList;
25  import java.util.List;
26  import java.util.Map;
27  
28  import org.dom4j.Document;
29  import org.dom4j.DocumentHelper;
30  import org.dom4j.Element;
31  import org.dom4j.Node;
32  import org.dom4j.XPath;
33  
34  /**
35   * <code>FilteringXmlMessageSplitter</code> will split a DOM4J document into nodes
36   * based on the "splitExpression" property. <p/> Optionally, you can specify a
37   * <code>namespaces</code> property map that contain prefix/namespace mappings.
38   * Mind if you have a default namespace declared you should map it to some namespace,
39   * and reference it in the <code>splitExpression</code> property. <p/> The splitter
40   * can optionally validate against an XML schema. By default schema validation is
41   * turned off. <p/> You may reference an external schema from the classpath by using
42   * the <code>externalSchemaLocation</code> property. <p/> Note that each part
43   * returned is actually returned as a new Document.
44   */
45  public class FilteringXmlMessageSplitter extends AbstractMessageSplitter
46  {
47      protected final ThreadLocal propertiesContext = new ThreadLocal();
48      protected final ThreadLocal nodesContext = new ThreadLocal();
49  
50      protected volatile String splitExpression = "";
51      protected volatile Map namespaces = null;
52      protected volatile boolean validateSchema = false;
53      protected volatile String externalSchemaLocation = "";
54  
55      public void setSplitExpression(String splitExpression)
56      {
57          this.splitExpression = StringUtils.trimToEmpty(splitExpression);
58      }
59  
60      public void setNamespaces(Map namespaces)
61      {
62          this.namespaces = namespaces;
63      }
64  
65      public Map getNamespaces()
66      {
67          return Collections.unmodifiableMap(namespaces);
68      }
69  
70      public String getSplitExpression()
71      {
72          return splitExpression;
73      }
74  
75      public boolean isValidateSchema()
76      {
77          return validateSchema;
78      }
79  
80      public void setValidateSchema(boolean validateSchema)
81      {
82          this.validateSchema = validateSchema;
83      }
84  
85      public String getExternalSchemaLocation()
86      {
87          return externalSchemaLocation;
88      }
89  
90      /**
91       * Set classpath location of the XSD to check against. If the resource cannot be
92       * found, an exception will be thrown at runtime.
93       * 
94       * @param externalSchemaLocation location of XSD
95       */
96      public void setExternalSchemaLocation(String externalSchemaLocation)
97      {
98          this.externalSchemaLocation = externalSchemaLocation;
99      }
100 
101     /**
102      * Template method can be used to split the message up before the getMessagePart
103      * method is called .
104      * 
105      * @param message the message being routed
106      */
107     // @Override
108     protected void initialise(MuleMessage message)
109     {
110         if (logger.isDebugEnabled())
111         {
112             if (splitExpression.length() == 0)
113             {
114                 logger.warn("splitExpression is not specified, no processing will take place");
115             }
116             else
117             {
118                 logger.debug("splitExpression is " + splitExpression);
119             }
120         }
121 
122         Object src = message.getPayload();
123 
124         Document dom4jDoc;
125         try
126         {
127             if (validateSchema)
128             {
129                 dom4jDoc = XMLUtils.toDocument(src, getExternalSchemaLocation());
130             }
131             else
132             {
133                 dom4jDoc = XMLUtils.toDocument(src);
134             }
135             if (dom4jDoc == null)
136             {
137                 logger.error("Non-XML message payload: " + src.getClass().toString());
138                 return;
139             }
140         }
141         catch (Exception e)
142         {
143             throw new IllegalArgumentException("Failed to initialise the payload: "
144                 + ExceptionUtils.getStackTrace(e));
145         }
146 
147         if (splitExpression.length() > 0)
148         {
149             XPath xpath = dom4jDoc.createXPath(splitExpression);
150             if (namespaces != null)
151             {
152                 xpath.setNamespaceURIs(namespaces);
153             }
154 
155             List foundNodes = xpath.selectNodes(dom4jDoc);
156             if (enableCorrelation != ENABLE_CORRELATION_NEVER)
157             {
158                 message.setCorrelationGroupSize(foundNodes.size());
159             }
160             if (logger.isDebugEnabled())
161             {
162                 logger.debug("Split into " + foundNodes.size());
163             }
164 
165             List parts = new LinkedList();
166             // Rather than reparsing these when individual messages are
167             // created, lets do it now
168             // We can also avoid parsing the Xml again altogether
169             for (Iterator iterator = foundNodes.iterator(); iterator.hasNext();)
170             {
171                 Node node = (Node)iterator.next();
172                 if (node instanceof Element)
173                 {
174                     // Can't do detach here just in case the source object
175                     // was a document.
176                     node = (Node)node.clone();
177                     parts.add(DocumentHelper.createDocument((Element)node));
178                 }
179                 else
180                 {
181                     logger.warn("Dcoument node: " + node.asXML()
182                                 + " is not an element and thus is not a valid part");
183                 }
184             }
185             nodesContext.set(parts);
186         }
187 
188         Map theProperties = new HashMap();
189         for (Iterator iterator = message.getPropertyNames().iterator(); iterator.hasNext();)
190         {
191             String propertyKey = (String)iterator.next();
192             theProperties.put(propertyKey, message.getProperty(propertyKey));
193         }
194         propertiesContext.set(theProperties);
195     }
196 
197     // @Override
198     protected void cleanup()
199     {
200         nodesContext.set(null);
201         propertiesContext.set(null);
202     }
203     
204     /**
205      * Retrieves a specific message part for the given endpoint. the message will
206      * then be routed via the provider.
207      * 
208      * @param message the current message being processed
209      * @param endpoint the endpoint that will be used to route the resulting message
210      *            part
211      * @return the message part to dispatch
212      */
213     protected MuleMessage getMessagePart(MuleMessage message, OutboundEndpoint endpoint)
214     {
215         List nodes = (List)nodesContext.get();
216 
217         if (nodes == null)
218         {
219             logger.error("Error: nodes are null");
220             return null;
221         }
222 
223         for (Iterator i = nodes.iterator(); i.hasNext();)
224         {
225             Document doc = (Document)i.next();
226 
227             try
228             {
229                 Map theProperties = (Map)propertiesContext.get();
230                 MuleMessage result = new DefaultMuleMessage(doc, new HashMap(theProperties));
231 
232                 if (endpoint.getFilter() == null || endpoint.getFilter().accept(result))
233                 {
234                     if (logger.isDebugEnabled())
235                     {
236                         logger.debug("Endpoint filter matched for node " + i + " of " + nodes.size()
237                                      + ". Routing message over: " + endpoint.getEndpointURI().toString());
238                     }
239                     i.remove();
240                     return result;
241                 }
242                 else
243                 {
244                     if (logger.isDebugEnabled())
245                     {
246                         logger.debug("Endpoint filter did not match, returning null");
247                     }
248                 }
249             }
250             catch (Exception e)
251             {
252                 logger.error("Unable to create message for node at position " + i, e);
253                 return null;
254             }
255         }
256 
257         return null;
258     }
259 }