001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.servicemix.components.mps;
018:
019: import java.io.IOException;
020: import java.util.HashMap;
021: import java.util.Map;
022:
023: import javax.jbi.JBIException;
024: import javax.jbi.component.ComponentContext;
025: import javax.jbi.messaging.MessageExchange;
026: import javax.jbi.messaging.MessagingException;
027: import javax.jbi.messaging.NormalizedMessage;
028: import javax.xml.parsers.DocumentBuilder;
029: import javax.xml.parsers.DocumentBuilderFactory;
030: import javax.xml.parsers.ParserConfigurationException;
031: import javax.xml.transform.TransformerException;
032:
033: import org.apache.commons.logging.Log;
034: import org.apache.commons.logging.LogFactory;
035: import org.apache.servicemix.components.util.TransformComponentSupport;
036: import org.apache.servicemix.jbi.jaxp.SourceTransformer;
037: import org.apache.xpath.CachedXPathAPI;
038: import org.apache.xpath.objects.XObject;
039: import org.springframework.core.io.Resource;
040: import org.springframework.util.Assert;
041: import org.w3c.dom.Document;
042: import org.w3c.dom.Element;
043: import org.w3c.dom.Node;
044: import org.xml.sax.SAXException;
045:
046: /**
047: * Sets properties on the message, loaded from an XML MPS file
048: * where the properties to set are located in a <property-set ..>
049: * inside the XML config file.
050: *
051: * There can be more than one propertySet to "load".
052: *
053: * The property values are derived from 3 types of config.
054: * The first config that can return a value as a String to
055: * set onto the message, will be the "value" that is set
056: * as the property.
057: *
058: * <static-value>
059: * As it's name suggests, the "value" of this element
060: * will be the value of the JBI property.
061: *
062: * This is helpful as a default value, or as a static value.
063: *
064: * <exisiting-property> and <existint-property name="..."/>
065: * This will obtain the value of an existing property (itself)
066: * or another property on the same message.
067: *
068: * This can be helpful when you want the to "ONLY" change the
069: * the value of the property if there is some "xpath" expression
070: * that could not be derived.
071: *
072: * name=".." form will copy the string value of the other JBI property
073: * onto this one, (duping it). This may be handy when you have a component
074: * which expects a new property, but you have it as a different name at the moment.
075: *
076: * <xpath-expression>
077: * As it's name suggests, will locate a value in the inMessage source
078: * and set the resulting XPath String as the value of the JBI property.
079: *
080: * So given the three types, they can be arranged in any order. and the first
081: * PropertyValue Type that returns a value, will become the "value" of the JBI property.
082: *
083: *
084: * <!--
085: * <mps>
086: * <property-set name="someSetNameForASetOfProperties">
087: * <property name="some.property.name1">
088: * <static-value><![CDATA[value for the property]]></static-value>
089: * </property>
090: * <property name="some.property.name2">
091: * <xpath-expression>
092: * <![CDATA[/someexpath/statement/to/be/applied/to/message/source]]>
093: * </xpath-expression>
094: * <existing-property name="someproperty"/>
095: * <static-value><![CDATA[a value in the raw]]></static-value>
096: * </property>
097: * <property name="prop.xpath.with.static.default">
098: * <xpath-expression>
099: * <![CDATA[/someexpath/statement]]>
100: * </xpath-expression>
101: * <static-value><![CDATA[some default if xpath does not resolve]]></static-value>
102: * </property>
103: * <property name="prop.xpath.or.keep.existing">
104: * <xpath-expression>
105: * <![CDATA[/someexpath/statement]]>
106: * </xpath-expression>
107: * <existing-property/>
108: * </property>
109: * <property name="new.prop.name">
110: * <existing-property name="other.property"/>
111: * </property>
112: * <property name="...">
113: * ...
114: * </property>
115: * </property-set>
116: * <property-set name="...">
117: * ...
118: * </property-set>
119: * </mps>
120: * -->
121: *
122: *
123: * @author rbuckland
124: *
125: */
126: public class MessagePropertySetterXML extends TransformComponentSupport {
127:
128: /**
129: * Apache commons logger
130: */
131: private final transient Log logger = LogFactory.getLog(getClass());
132:
133: /**
134: * The name of our JBI property we may have to interrogate to
135: * use as the "xpath" for the set of config.
136: */
137: public static final String MPS_PROP_NAME_PROPERTYSET = "org.apache.servicemix.components.mps.propertyset";
138:
139: /**
140: * The XML Element name "property-set"
141: */
142: public static final String XML_ELEMENT_NAME = "property-set";
143:
144: /**
145: * This is our XML file
146: */
147: private Resource xmlConfiguration = null;
148:
149: /**
150: * Our XML config MPS.
151: */
152: private Document xmlMPSdom = null;
153:
154: /**
155: * Holds the propertyValueResolver objects.
156: * if propertySet is not null, this holds one.
157: * if propertySet is null, this will hold
158: * one for each propertyvalue entry used during the time
159: * we are "active".
160: */
161: private Map propertSets = new HashMap();
162:
163: /**
164: * If this is set, it is hardcoded as the fixed value.
165: *
166: * If this is null
167: * we will look for a propery MPS_PROP_NAME_PROPERTYSET
168: * as the "name" of the property set we will work with.
169: * So the message incoming, seeds the set of properties to load, just by
170: * the JBI property itself.
171: */
172: private String propertySet = null;
173:
174: /**
175: * If the XPath for a propertySet is not null
176: * we will try and locate the propertySet to use by
177: * pulling a String from the inMessage content using this
178: * XPath.
179: */
180: private String xpathForPropertySet = null;
181:
182: /**
183: * Here is the transform of the message
184: * We will locate the propertySetName to use
185: * and the load up our propertySet magic wand and apply all the properties
186: * we can to the outgoing NormalizedMessage
187: */
188: protected boolean transform(MessageExchange arg0,
189: NormalizedMessage in, NormalizedMessage out)
190: throws MessagingException {
191: try {
192: copyPropertiesAndAttachments(arg0, in, out);
193: out.setContent(in.getContent());
194:
195: String propertySetName = "";
196: if (xpathForPropertySet != null) {
197: try {
198: CachedXPathAPI xpathApi = new org.apache.xpath.CachedXPathAPI();
199: Document doc = new SourceTransformer()
200: .toDOMDocument(in);
201: XObject propSetXO = xpathApi.eval(doc
202: .getDocumentElement(), xpathForPropertySet);
203: propertySetName = propSetXO.str();
204: } catch (Exception e) {
205: throw new MessagingException(
206: "Problem getting the propertySet using XPath",
207: e);
208: }
209: } else if (this .propertySet != null) {
210: propertySetName = this .propertySet;
211: } else if (in.getProperty(MPS_PROP_NAME_PROPERTYSET) != null) {
212: propertySetName = in.getProperty(
213: MPS_PROP_NAME_PROPERTYSET).toString();
214: } else {
215: return false;
216: }
217: logger.info("Applying properties from property-set ["
218: + propertySetName + "]");
219: getPropertySetByName(propertySetName).applyProperties(in,
220: out);
221: return true;
222: } catch (JBIException e) {
223: throw new MessagingException("Problem setting properties",
224: e);
225: } catch (PropertySetNotFoundException e) {
226: logger.warn(e.getLocalizedMessage());
227: return false;
228: }
229: }
230:
231: /**
232: * Initialise by loading the mps.xml file up into an internal DOM.
233: *
234: * @throws JBIException
235: */
236: private void initConfig() throws JBIException {
237: Assert.notNull(this .xmlConfiguration);
238: DocumentBuilderFactory domFactory = DocumentBuilderFactory
239: .newInstance();
240: domFactory.setIgnoringElementContentWhitespace(true);
241: domFactory.setIgnoringComments(true);
242: domFactory.setCoalescing(true); // convert CDATA to test nodes
243: DocumentBuilder domBuilder;
244: try {
245: logger
246: .info("Intialising MessagePropertySetterXML, loading settings from "
247: + this .xmlConfiguration.getFile()
248: .getAbsolutePath());
249: domBuilder = domFactory.newDocumentBuilder();
250: xmlMPSdom = domBuilder.parse(this .xmlConfiguration
251: .getInputStream());
252: } catch (ParserConfigurationException e) {
253: throw new JBIException(
254: "Problem parsing the XML config file for MPS", e);
255: } catch (SAXException e) {
256: throw new JBIException(
257: "Problem loading the XML config file for MPS", e);
258: } catch (IOException e) {
259: throw new JBIException(
260: "Problem loading the XML config file for MPS", e);
261: }
262:
263: }
264:
265: /**
266: * We are only interested in loading the XML onfig, nothing else
267: */
268: public void init(ComponentContext context) throws JBIException {
269: super .init(context);
270: initConfig();
271: }
272:
273: /**
274: * Create a propertyset (loading up the Object config based on the XML config)
275: *
276: * @param propertySetName
277: * @return
278: * @throws JBIException
279: * @throws PropertySetNotFoundException
280: */
281: private PropertySet createPropertySet(String propertySetName)
282: throws JBIException, PropertySetNotFoundException {
283: PropertySet ps;
284: CachedXPathAPI xpath = new CachedXPathAPI();
285: StringBuffer xpathSB = new StringBuffer("//").append(
286: XML_ELEMENT_NAME).append("[@name='").append(
287: propertySetName).append("']");
288: try {
289: Node propertySetNode = xpath.selectSingleNode(xmlMPSdom,
290: xpathSB.toString());
291: if (propertySetNode == null) {
292: throw new PropertySetNotFoundException(
293: "Could not find a property-set for ["
294: + propertySetName + "] in "
295: + xmlConfiguration.getFilename());
296: }
297: ps = new PropertySet(propertySetName,
298: (Element) propertySetNode);
299: this .propertSets.put(propertySetName, ps);
300: } catch (TransformerException e) {
301: throw new JBIException(
302: "Could not load the PropertySet for " + propertySet,
303: e);
304: } catch (ConfigNotSupportedException e) {
305: throw new JBIException(
306: "Could not load the PropertySet for. XMLConfig is not good for "
307: + propertySet, e);
308: }
309: return ps;
310:
311: }
312:
313: /**
314: * Get a property set from ou
315: * @param name
316: * @return
317: * @throws JBIException
318: */
319: private PropertySet getPropertySetByName(String name)
320: throws JBIException, PropertySetNotFoundException {
321: // find a pre "created" one
322: if (this .propertSets.containsKey(name)) {
323: return (PropertySet) this .propertSets.get(name);
324: } else {
325: return this .createPropertySet(name);
326: }
327:
328: }
329:
330: /**
331: * @param propertySet The propertySet to set.
332: */
333: public void setPropertySet(String propertySet) {
334: this .propertySet = propertySet;
335: }
336:
337: /**
338: * @param xmlConfiguration The xmlConfiguration to set.
339: */
340: public void setXmlConfiguration(Resource xmlConfiguration) {
341: this .xmlConfiguration = xmlConfiguration;
342: }
343:
344: /**
345: * @param xpathForPropertySet xpath to apply to a message to derive the name of the property-set we want to load
346: */
347: public void setXpathForPropertySet(String xpathForPropertySet) {
348: this.xpathForPropertySet = xpathForPropertySet;
349: }
350:
351: }
|