View Javadoc

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