View Javadoc
1   /*
2    * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
3    * The software in this package is published under the terms of the CPAL v1.0
4    * license, a copy of which has been included with this distribution in the
5    * LICENSE.txt file.
6    */
7   package org.mule.module.xml.routing;
8   
9   import org.mule.api.MuleMessage;
10  import org.mule.api.lifecycle.InitialisationException;
11  import org.mule.api.registry.RegistrationException;
12  import org.mule.config.i18n.CoreMessages;
13  import org.mule.module.xml.util.NamespaceManager;
14  import org.mule.routing.CorrelationMode;
15  import org.mule.routing.outbound.AbstractRoundRobinMessageSplitter;
16  import org.mule.util.ExceptionUtils;
17  import org.mule.util.IOUtils;
18  import org.mule.util.StringUtils;
19  
20  import java.io.InputStream;
21  import java.io.StringReader;
22  import java.util.Collections;
23  import java.util.HashMap;
24  import java.util.Iterator;
25  import java.util.LinkedList;
26  import java.util.List;
27  import java.util.Map;
28  
29  import org.dom4j.Document;
30  import org.dom4j.DocumentHelper;
31  import org.dom4j.Element;
32  import org.dom4j.Node;
33  import org.dom4j.XPath;
34  import org.dom4j.io.DOMReader;
35  import org.dom4j.io.SAXReader;
36  
37  /**
38   * <code>XmlMessageSplitter</code> will split a DOM4J document into nodes
39   * based on the "splitExpression" property. <p/> Optionally, you can specify a
40   * <code>namespaces</code> property map that contain prefix/namespace mappings.
41   * Mind if you have a default namespace declared you should map it to some namespace,
42   * and reference it in the <code>splitExpression</code> property. <p/> The splitter
43   * can optionally validate against an XML schema. By default schema validation is
44   * turned off. <p/> You may reference an external schema from the classpath by using
45   * the <code>externalSchemaLocation</code> property. <p/> Note that each part
46   * returned is actually returned as a new Document.
47   */
48  public class XmlMessageSplitter extends AbstractRoundRobinMessageSplitter
49  {
50      // xml parser feature names for optional XSD validation
51      public static final String APACHE_XML_FEATURES_VALIDATION_SCHEMA = "http://apache.org/xml/features/validation/schema";
52      public static final String APACHE_XML_FEATURES_VALIDATION_SCHEMA_FULL_CHECKING = "http://apache.org/xml/features/validation/schema-full-checking";
53  
54      // JAXP property for specifying external XSD location
55      public static final String JAXP_PROPERTIES_SCHEMA_SOURCE = "http://java.sun.com/xml/jaxp/properties/schemaSource";
56  
57      // JAXP properties for specifying external XSD language (as required by newer
58      // JAXP implementation)
59      public static final String JAXP_PROPERTIES_SCHEMA_LANGUAGE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
60      public static final String JAXP_PROPERTIES_SCHEMA_LANGUAGE_VALUE = "http://www.w3.org/2001/XMLSchema";
61  
62      protected volatile String splitExpression = "";
63      protected volatile Map<?, ?> namespaces;
64      protected NamespaceManager namespaceManager;
65  
66      protected volatile boolean validateSchema;
67      protected volatile String externalSchemaLocation = "";
68  
69      public void setSplitExpression(String splitExpression)
70      {
71          this.splitExpression = StringUtils.trimToEmpty(splitExpression);
72      }
73  
74      public void setNamespaces(Map<?, ?> namespaces)
75      {
76          this.namespaces = namespaces;
77      }
78  
79      public Map<?, ?> getNamespaces()
80      {
81          return Collections.unmodifiableMap(namespaces);
82      }
83  
84      public String getSplitExpression()
85      {
86          return splitExpression;
87      }
88  
89      public boolean isValidateSchema()
90      {
91          return validateSchema;
92      }
93  
94      public void setValidateSchema(boolean validateSchema)
95      {
96          this.validateSchema = validateSchema;
97      }
98  
99      public String getExternalSchemaLocation()
100     {
101         return externalSchemaLocation;
102     }
103 
104     /**
105      * Set classpath location of the XSD to check against. If the resource cannot be
106      * found, an exception will be thrown at runtime.
107      *
108      * @param externalSchemaLocation location of XSD
109      */
110     public void setExternalSchemaLocation(String externalSchemaLocation)
111     {
112         this.externalSchemaLocation = externalSchemaLocation;
113     }
114 
115     @Override
116     public void initialise() throws InitialisationException
117     {
118         logger.warn("Deprecation warning: The XmlMessageSplitter router has been deprecating in Mule 2.2 in favour of using the <expression-splitter> router.");
119         if (StringUtils.isBlank(splitExpression))
120         {
121             throw new IllegalArgumentException(CoreMessages.objectIsNull("splitExpression").getMessage());
122         }
123 
124         try
125         {
126             namespaceManager = muleContext.getRegistry().lookupObject(NamespaceManager.class);
127 
128             if (namespaceManager != null)
129             {
130                 if (namespaces == null)
131                 {
132                     namespaces = new HashMap<Object, Object>(namespaceManager.getNamespaces());
133                 }
134                 else
135                 {
136                     namespaces.putAll(namespaceManager.getNamespaces());
137                 }
138             }
139 
140         }
141         catch (RegistrationException e)
142         {
143             throw new InitialisationException(CoreMessages.failedToLoad("NamespaceManager"), e, this);
144         }
145 
146         super.initialise();
147     }
148 
149     /**
150      * Template method can be used to split the message up before the getMessagePart
151      * method is called .
152      *
153      * @param message the message being routed
154      */
155     @Override
156     protected List splitMessage(MuleMessage message)
157     {
158         if (logger.isDebugEnabled())
159         {
160             if (splitExpression.length() == 0)
161             {
162                 logger.warn("splitExpression is not specified, no processing will take place");
163             }
164             else
165             {
166                 logger.debug("splitExpression is " + splitExpression);
167             }
168         }
169 
170         Object src = message.getPayload();
171 
172         try
173         {
174             if (src instanceof byte[])
175             {
176                 src = new String((byte[]) src);
177             }
178 
179             Document dom4jDoc;
180 
181             if (src instanceof String)
182             {
183                 String xml = (String) src;
184                 SAXReader reader = new SAXReader();
185                 setDoSchemaValidation(reader, isValidateSchema());
186 
187                 dom4jDoc = reader.read(new StringReader(xml));
188             }
189             else if (src instanceof org.dom4j.Document)
190             {
191                 dom4jDoc = (org.dom4j.Document) src;
192             }
193             else if (src instanceof org.w3c.dom.Document)
194             {
195                 DOMReader xmlReader = new DOMReader();
196                 dom4jDoc = xmlReader.read((org.w3c.dom.Document)src);
197             }
198             else
199             {
200                 throw new IllegalArgumentException(CoreMessages.objectNotOfCorrectType(
201                         src.getClass(), new Class[]{org.w3c.dom.Document.class, Document.class, String.class, byte[].class}).getMessage());
202             }
203 
204             XPath xpath = dom4jDoc.createXPath(splitExpression);
205             if (namespaces != null)
206             {
207                 xpath.setNamespaceURIs(namespaces);
208             }
209 
210             List foundNodes = xpath.selectNodes(dom4jDoc);
211             if (enableCorrelation != CorrelationMode.NEVER)
212             {
213                 message.setCorrelationGroupSize(foundNodes.size());
214             }
215             if (logger.isDebugEnabled())
216             {
217                 logger.debug("Split into " + foundNodes.size());
218             }
219 
220             List parts = new LinkedList();
221             // Rather than reparsing these when individual messages are
222             // created, lets do it now
223             // We can also avoid parsing the Xml again altogether
224             for (Iterator iterator = foundNodes.iterator(); iterator.hasNext();)
225             {
226                 Node node = (Node) iterator.next();
227                 if (node instanceof Element)
228                 {
229                     // Can't do detach here just in case the source object
230                     // was a document.
231                     node = (Node) node.clone();
232                     parts.add(DocumentHelper.createDocument((Element) node));
233                 }
234                 else
235                 {
236                     logger.warn("Dcoument node: " + node.asXML()
237                             + " is not an element and thus is not a valid part");
238                 }
239             }
240             return parts;
241         }
242         catch (Exception ex)
243         {
244             throw new IllegalArgumentException("Failed to initialise the payload: "
245                     + ExceptionUtils.getStackTrace(ex));
246         }
247     }
248 
249     protected void setDoSchemaValidation(SAXReader reader, boolean validate) throws Exception
250     {
251         reader.setValidation(validate);
252         reader.setFeature(APACHE_XML_FEATURES_VALIDATION_SCHEMA, validate);
253         reader.setFeature(APACHE_XML_FEATURES_VALIDATION_SCHEMA_FULL_CHECKING, true);
254 
255         // By default we're not validating against an XSD. If this is the case,
256         // there's no need to continue here, so we bail.
257         if (!validate)
258         {
259             return;
260         }
261 
262         InputStream xsdAsStream = IOUtils.getResourceAsStream(getExternalSchemaLocation(), getClass());
263         if (xsdAsStream == null)
264         {
265             throw new IllegalArgumentException("Couldn't find schema at "
266                 + getExternalSchemaLocation());
267         }
268 
269         // Set schema language property (must be done before the schemaSource
270         // is set)
271         reader.setProperty(JAXP_PROPERTIES_SCHEMA_LANGUAGE, JAXP_PROPERTIES_SCHEMA_LANGUAGE_VALUE);
272 
273         // Need this one to map schemaLocation to a physical location
274         reader.setProperty(JAXP_PROPERTIES_SCHEMA_SOURCE, xsdAsStream);
275     }
276 }