View Javadoc

1   /*
2    * Copyright 2010 Capgemini
3    * Licensed under the Apache License, Version 2.0 (the "License"); 
4    * you may not use this file except in compliance with the License. 
5    * You may obtain a copy of the License at 
6    * 
7    * http://www.apache.org/licenses/LICENSE-2.0 
8    * 
9    * Unless required by applicable law or agreed to in writing, software 
10   * distributed under the License is distributed on an "AS IS" BASIS, 
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
12   * See the License for the specific language governing permissions and 
13   * limitations under the License. 
14   * 
15   */
16  package org.xmlfield.core;
17  
18  import static com.google.common.collect.Iterables.toArray;
19  import static org.xmlfield.core.internal.XPathUtils.getElementNameWithSelector;
20  import static org.xmlfield.core.internal.XmlFieldUtils.getResourceNamespaces;
21  import static org.xmlfield.core.internal.XmlFieldUtils.getResourceXPath;
22  
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.io.Writer;
26  import java.lang.reflect.InvocationHandler;
27  import java.lang.reflect.Proxy;
28  import java.util.ArrayList;
29  import java.util.HashMap;
30  import java.util.List;
31  import java.util.Map;
32  
33  import javax.xml.parsers.ParserConfigurationException;
34  import javax.xml.transform.OutputKeys;
35  
36  import org.xml.sax.SAXException;
37  import org.xmlfield.core.api.XmlFieldNode;
38  import org.xmlfield.core.api.XmlFieldNodeList;
39  import org.xmlfield.core.api.XmlFieldNodeModifier;
40  import org.xmlfield.core.api.XmlFieldNodeModifierFactory;
41  import org.xmlfield.core.api.XmlFieldNodeParser;
42  import org.xmlfield.core.api.XmlFieldNodeParserFactory;
43  import org.xmlfield.core.api.XmlFieldObject;
44  import org.xmlfield.core.api.XmlFieldSelector;
45  import org.xmlfield.core.api.XmlFieldSelectorFactory;
46  import org.xmlfield.core.exception.XmlFieldException;
47  import org.xmlfield.core.exception.XmlFieldParsingException;
48  import org.xmlfield.core.exception.XmlFieldXPathException;
49  import org.xmlfield.core.impl.dom.DomNodeParser;
50  import org.xmlfield.core.internal.NamespaceMap;
51  import org.xmlfield.core.internal.XPathUtils;
52  import org.xmlfield.core.internal.XmlFieldInvocationHandler;
53  import org.xmlfield.core.internal.XmlFieldUtils;
54  
55  /**
56   * This class is the entry point of XmlField. It can convert xml data to objects
57   * (both ways).
58   * 
59   * <pre>
60   * // Source Xml
61   * String xml ="&lt;modelRootTag&gt;&lt;/modelRootTag&gt;"; 
62   * 
63   * // Read doc
64   * XmlField xf = new XmlField();
65   * IModel model = xf.xmlToObject(xmlRessource, IModel.class)
66   * 
67   * // Back to XML.
68   * xml = xf.objectToXml( model);
69   * </pre>
70   * 
71   * <p>
72   * This class and return objects are not thread safe. If the same objects are
73   * used concurrently in multiple threads, be sure to synchronize or use
74   * XmlFieldFactory with ThreadLocal enabled.
75   * 
76   * @author Guillaume Mary <guillaume.mary@capgemini.com>
77   * @author Nicolas Richeton
78   */
79  public class XmlField {
80  	/**
81  	 * Classloader used to load the proxies.
82  	 */
83  	private static final ClassLoader classLoader = Thread.currentThread()
84  			.getContextClassLoader();
85  
86  	private static XmlFieldNodeModifierFactory modifierFactory = XmlFieldNodeModifierFactory
87  			.newInstance();
88  	private static XmlFieldNodeParserFactory parserFactory = XmlFieldNodeParserFactory
89  			.newInstance();
90  	private static XmlFieldSelectorFactory selectorFactory = XmlFieldSelectorFactory
91  			.newInstance();
92  
93  	private boolean getterCache = false;
94  	private XmlFieldNodeModifier modifier;
95  	/**
96  	 * Parser used to parse the xml to node
97  	 */
98  	private XmlFieldNodeParser parser;
99  
100 	private Map<String, String> parserConfiguration;
101 	/**
102 	 * Selector used to execute xpath expression
103 	 */
104 	private XmlFieldSelector selector;
105 
106 	/**
107 	 * Create XmlField object for xml/object manipulations.
108 	 * 
109 	 * <p>
110 	 * Use default configuration.
111 	 */
112 	public XmlField() {
113 		this(null);
114 	}
115 
116 	/**
117 	 * Create XmlField object for xml/object manipulations. This constructor can
118 	 * be used to gain fine control over xmlfield output for example
119 	 * (indentation, xml declaration etc. ).
120 	 * 
121 	 * <p>
122 	 * Configuration is forwarded to xml transformer.
123 	 * </p>
124 	 * <p>
125 	 * See {@link OutputKeys} for universal valid keys and values.
126 	 * </p>
127 	 * <p>
128 	 * For specific transformer implementation see available documentation, for
129 	 * exemple, if xalan is used see
130 	 * org.apache.xml.serializer.OutputPropertiesFactory
131 	 * </p>
132 	 * <p>
133 	 * BEWARE : if {@link OutputKeys#MEDIA_TYPE} is set to anything but xml, it
134 	 * can defeat XmlField purpose. this is applicable to other configuration
135 	 * too.
136 	 * </p>
137 	 * 
138 	 * @param parserConfiguration
139 	 *            parser configuration. Allowed keys are specific to the parser
140 	 *            implementations. See {@link DomNodeParser} for default
141 	 *            implementation.
142 	 */
143 	public XmlField(Map<String, String> parserConfiguration) {
144 		this.parserConfiguration = parserConfiguration;
145 	}
146 
147 	/**
148 	 * Returns the modifier associated with this XmlField object.
149 	 * <p>
150 	 * {@link XmlFieldNodeModifier} instance is created on demand at the first
151 	 * call.
152 	 * 
153 	 * @return
154 	 */
155 	public XmlFieldNodeModifier _getModifier() {
156 		if (modifier == null) {
157 			modifier = modifierFactory.newModifier();
158 		}
159 
160 		return modifier;
161 	}
162 
163 	/**
164 	 * Returns the parser associated with this XmlField object.
165 	 * <p>
166 	 * {@link XmlFieldNodeParser} instance is created on demand at the first
167 	 * call.
168 	 * 
169 	 * @return
170 	 */
171 	public XmlFieldNodeParser _getParser() {
172 		if (parser == null) {
173 			parser = parserFactory.newParser(parserConfiguration);
174 		}
175 
176 		return parser;
177 	}
178 
179 	/**
180 	 * Returns the selector associated with this XmlField object.
181 	 * <p>
182 	 * {@link XmlFieldSelector} instance is created on demand at the first call.
183 	 * 
184 	 * @return
185 	 */
186 	public XmlFieldSelector _getSelector() {
187 		if (selector == null) {
188 			selector = selectorFactory.newSelector();
189 		}
190 
191 		return selector;
192 	}
193 
194 	/**
195 	 * Changes interface of an already attached node.
196 	 * 
197 	 * @param o
198 	 *            an object obtained by any {@link #attach} call.
199 	 * @param type
200 	 *            the new interface for dom manipulation.
201 	 * @return an objet implementing the given interface.
202 	 */
203 	public <T> T castObject(Object o, Class<T> type) {
204 		return loadProxy(XmlFieldUtils.getXmlFieldNode(o), type);
205 	}
206 
207 	/**
208 	 * Returns the current parser configuration.
209 	 * <p>
210 	 * The returned object is cannot be updated.
211 	 * 
212 	 * @return
213 	 */
214 	public Map<String, String> getParserConfiguration() {
215 		return new HashMap<String, String>(parserConfiguration);
216 	}
217 
218 	public boolean isGetterCache() {
219 		return getterCache;
220 	}
221 
222 	private <T> T loadProxy(final XmlFieldNode node, final Class<T> type) {
223 
224 		// Handle case when requested type is String.
225 		if (String.class.equals(type)) {
226 			return type.cast(node.getTextContent());
227 		}
228 
229 		final Class<?>[] types = new Class<?>[] { type, XmlFieldObject.class };
230 
231 		final InvocationHandler invocationHandler = new XmlFieldInvocationHandler(
232 				this, node, type);
233 
234 		final T proxy = type.cast(Proxy.newProxyInstance(classLoader, types,
235 				invocationHandler));
236 
237 		return proxy;
238 	}
239 
240 	/**
241 	 * instantiate a new XmlField interface.
242 	 * 
243 	 * the return object can be manipulated by Xmlfield like any object obtained
244 	 * by {@link #bind} methods
245 	 * 
246 	 * @param <T>
247 	 *            Class of interface to instantiate
248 	 * @param type
249 	 *            Class of interface to instantiate
250 	 * @return an object implementing Class
251 	 *         <code>type<code> and {@link XmlFieldObject}
252 	 * 
253 	 * @throws ParserConfigurationException
254 	 * @throws SAXException
255 	 * @throws IOException
256 	 * @throws XmlFieldParsingException
257 	 */
258 	public <T> T newObject(Class<T> type) throws XmlFieldParsingException {
259 
260 		// Create a new xml document with an empty tag (based on the
261 		// annotation).
262 		String resourceXPath = getResourceXPath(type);
263 		String tag = getElementNameWithSelector(resourceXPath);
264 		NamespaceMap namespaces = getResourceNamespaces(type);
265 		String xml = XmlFieldUtils.emptyTag(tag, namespaces);
266 
267 		// Create object from this document
268 		return xmlToObject(xml, type);
269 	}
270 
271 	public <T> T[] nodeToArray(final String resourceXPath,
272 			final XmlFieldNode node, final Class<T> type)
273 			throws XmlFieldXPathException {
274 
275 		final NamespaceMap namespaces = getResourceNamespaces(type);
276 
277 		final XmlFieldNodeList xmlFieldNodes = _getSelector()
278 				.selectXPathToNodeList(namespaces, resourceXPath, node);
279 
280 		final List<T> list = new ArrayList<T>();
281 		for (int i = 0; i < xmlFieldNodes.getLength(); i++) {
282 			final T proxy = loadProxy(xmlFieldNodes.item(i), type);
283 
284 			list.add(proxy);
285 		}
286 		return toArray(list, type);
287 	}
288 
289 	public <T> T[] nodeToArray(final XmlFieldNode node, final Class<T> type)
290 			throws XmlFieldXPathException {
291 
292 		final String resourceXPath = getResourceXPath(type);
293 
294 		return nodeToArray(resourceXPath, node, type);
295 	}
296 
297 	/**
298 	 * Function to attach an array of different objects type .
299 	 * 
300 	 * @param resourceXPath
301 	 *            Xpath to the collection.
302 	 * @param explicitCollection
303 	 *            Hashmap for matching name of xpath and clas.
304 	 * @param node
305 	 *            node of java object.
306 	 * @throws XmlFieldXPathException
307 	 */
308 	public Object[] nodeToExplicitArray(final String resourceXPath,
309 			final XmlFieldNode node,
310 			final Map<String, Class<?>> explicitCollection)
311 			throws XmlFieldXPathException {
312 
313 		// we should replace the last occurrence of the last xpath name with a *
314 		String toReplace = XPathUtils.getElementName(resourceXPath);
315 		StringBuilder b = new StringBuilder(resourceXPath);
316 		b.replace(resourceXPath.lastIndexOf(toReplace),
317 				resourceXPath.lastIndexOf(toReplace) + 1, "*");
318 		final String resourceXPathGlobal = b.toString();
319 
320 		// TODO
321 		// See https://sourceforge.net/apps/mantisbt/xmlfield/view.php?id=46
322 
323 		final NamespaceMap namespaces = getResourceNamespaces(null);
324 
325 		final XmlFieldNodeList xmlFieldNodes = _getSelector()
326 				.selectXPathToNodeList(namespaces, resourceXPathGlobal, node);
327 
328 		final List<Object> list = new ArrayList<Object>();
329 
330 		for (int i = 0; i < xmlFieldNodes.getLength(); i++) {
331 			XmlFieldNode xmlFieldNode = xmlFieldNodes.item(i);
332 			if (explicitCollection.containsKey(xmlFieldNode.getNodeName())) {
333 				final Object proxy = loadProxy(xmlFieldNode,
334 						explicitCollection.get(xmlFieldNode.getNodeName()));
335 				list.add(proxy);
336 			}
337 
338 		}
339 
340 		return toArray(list, Object.class);
341 	}
342 
343 	/**
344 	 * Bind a node located by the xpath expression to the specified type
345 	 * 
346 	 * @param resourceXPath
347 	 *            xpath expression used to locate the node to bind
348 	 * @param node
349 	 *            the root node {@link XmlFieldNode}
350 	 * @param resourceType
351 	 *            the expected interface class
352 	 * @return null for non matching xml/type.
353 	 */
354 	public <T> T nodeToObject(final String resourceXPath,
355 			final XmlFieldNode node, final Class<T> resourceType) {
356 
357 		final NamespaceMap namespaces = getResourceNamespaces(resourceType);
358 
359 		final XmlFieldNode subNode;
360 
361 		if (resourceXPath == null) {
362 
363 			subNode = node;
364 
365 		} else {
366 
367 			try {
368 
369 				subNode = _getSelector().selectXPathToNode(namespaces,
370 						resourceXPath, node);
371 
372 			} catch (final XmlFieldXPathException e) {
373 
374 				throw new RuntimeException(e);
375 			}
376 		}
377 
378 		if (subNode == null || subNode.getNode() == null) {
379 			return null;
380 		} else {
381 			return loadProxy(subNode, resourceType);
382 		}
383 	}
384 
385 	/**
386 	 * Bind an xml field node to the specified type. This type should have some
387 	 * xpath annotations.
388 	 * 
389 	 * @param <T>
390 	 *            interface type
391 	 * @param node
392 	 *            node
393 	 * @param type
394 	 *            interface to bind to
395 	 * @return instance binded to the xml
396 	 */
397 	public <T> T nodeToObject(final XmlFieldNode node, final Class<T> type) {
398 		// Get the root tag and create an object from it.
399 		return nodeToObject(getResourceXPath(type), node, type);
400 	}
401 
402 	public String nodeToXml(final XmlFieldNode node)
403 			throws XmlFieldParsingException {
404 		return _getParser().nodeToXml(node);
405 	}
406 
407 	public void nodeToXml(final XmlFieldNode node, Writer writer)
408 			throws XmlFieldParsingException {
409 		_getParser().nodeToXml(node, writer);
410 	}
411 
412 	public XmlFieldNode objectToNode(Object o) {
413 		return XmlFieldUtils.getXmlFieldNode(o);
414 	}
415 
416 	public String objectToXml(Object o) throws XmlFieldParsingException {
417 		return _getParser().nodeToXml(objectToNode(o));
418 	}
419 
420 	public void objectToXml(Object o, Writer writer)
421 			throws XmlFieldParsingException {
422 		_getParser().nodeToXml(objectToNode(o), writer);
423 	}
424 
425 	/**
426 	 * Enables caching for get methods.
427 	 * 
428 	 * <p>
429 	 * Warning : this cache is experimental and cannot get changes mades when
430 	 * using different objects to access the same XML Node. The use of this
431 	 * cache is NOT recommended at the moment.
432 	 * 
433 	 * @param getterCache
434 	 */
435 	public void setGetterCache(boolean getterCache) {
436 		this.getterCache = getterCache;
437 	}
438 
439 	/**
440 	 * Bind an xml string to an array of entities.
441 	 * 
442 	 * the xml should be a list of entities enclosed by a root element.
443 	 * 
444 	 * @param <T>
445 	 *            interface type
446 	 * @param xml
447 	 *            an xml string with a root element enclosing a list of node to
448 	 *            bind
449 	 * @param type
450 	 *            interface to bind to
451 	 * @return an array
452 	 * @throws XmlFieldParsingException
453 	 * @throws XmlFieldXPathException
454 	 */
455 	public <T> T[] xmlToArray(String xml, Class<T> type)
456 			throws XmlFieldException {
457 
458 		XmlFieldNode node = xmlToNode(xml);
459 		T[] resultArray = nodeToArray(
460 				getElementNameWithSelector(getResourceXPath(type)), node, type);
461 		return resultArray;
462 
463 	}
464 
465 	/**
466 	 * Load the XML document from an input stream, load it internally in a tree
467 	 * and return the root node.
468 	 * <p>
469 	 * As XmlField supports multiple XML parsing engines, the return object is a
470 	 * generic type which wraps the actual implementation of the tree.
471 	 * 
472 	 * <p>
473 	 * The document root is intended to be used with XmlField#nodeToObject(
474 	 * XmlFieldNode node, Class<T> type) to get an object instance to read/write
475 	 * the document.
476 	 * 
477 	 * @param xmlInputStream
478 	 *            Input stream on an XML document.
479 	 * @return Root node of the XML document tree.
480 	 * @throws XmlFieldParsingException
481 	 *             When document is invalid, and cannot be parsed or when an
482 	 *             exception occurs.
483 	 */
484 	public XmlFieldNode xmlToNode(final InputStream xmlInputStream)
485 			throws XmlFieldParsingException {
486 		return _getParser().xmlToNode(xmlInputStream);
487 	}
488 
489 	/**
490 	 * Load the XML document from a string, load it internally in a tree and
491 	 * return the root node.
492 	 * <p>
493 	 * As XmlField supports multiple XML parsing engines, the return object is a
494 	 * generic type which wraps the actual implementation of the tree.
495 	 * 
496 	 * <p>
497 	 * The document root is intended to be used with XmlField#nodeToObject(
498 	 * XmlFieldNode node, Class<T> type) to get an object instance to read/write
499 	 * the document.
500 	 * 
501 	 * @param xml
502 	 *            An XML document in a single string
503 	 * @return Root node of the XML document tree.
504 	 * @throws XmlFieldParsingException
505 	 *             When document is invalid and cannot be parsed.
506 	 */
507 	public XmlFieldNode xmlToNode(final String xml)
508 			throws XmlFieldParsingException {
509 		return _getParser().xmlToNode(xml);
510 	}
511 
512 	/**
513 	 * @param xmlContent
514 	 * @param type
515 	 * @return
516 	 * @throws XmlFieldParsingException
517 	 */
518 	public <T> T xmlToObject(InputStream xmlContent, Class<T> type)
519 			throws XmlFieldParsingException {
520 		return nodeToObject(xmlToNode(xmlContent), type);
521 	}
522 
523 	/**
524 	 * Create an interface for the given xml and matching the given interface.
525 	 * 
526 	 * @param xml
527 	 *            the xml String to load
528 	 * @param type
529 	 *            the expected interface
530 	 * @return a proxy object responding to given type, return null if type
531 	 *         does'n match the xml string.
532 	 * @throws ParserConfigurationException
533 	 *             when parsing xml failed
534 	 * @throws SAXException
535 	 *             when parsing xml failed
536 	 * @throws IOException
537 	 *             when parsing xml failed
538 	 * @throws XmlFieldParsingException
539 	 */
540 	public <T> T xmlToObject(String xml, Class<T> type)
541 			throws XmlFieldParsingException {
542 		return nodeToObject(xmlToNode(xml), type);
543 	}
544 }