View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */ 
17  package org.apache.commons.betwixt.io.read;
18  
19  import java.beans.IntrospectionException;
20  
21  import org.apache.commons.betwixt.AttributeDescriptor;
22  import org.apache.commons.betwixt.BindingConfiguration;
23  import org.apache.commons.betwixt.ElementDescriptor;
24  import org.apache.commons.betwixt.Options;
25  import org.apache.commons.betwixt.XMLBeanInfo;
26  import org.apache.commons.betwixt.XMLIntrospector;
27  import org.apache.commons.betwixt.expression.Context;
28  import org.apache.commons.betwixt.expression.Updater;
29  import org.apache.commons.betwixt.registry.PolymorphicReferenceResolver;
30  import org.apache.commons.betwixt.strategy.ActionMappingStrategy;
31  import org.apache.commons.collections.ArrayStack;
32  import org.apache.commons.logging.Log;
33  import org.apache.commons.logging.LogFactory;
34  import org.xml.sax.Attributes;
35  
36  /**  
37    * <p>Extends <code>Context</code> to provide read specific functionality.</p> 
38    * <p>
39    * Three stacks are used to manage the reading:
40    * </p>
41    * <ul>
42    *     <li><strong>Action mapping stack</strong> contains the {@link MappingAction}'s
43    * used to execute the mapping of the current element and it's ancesters back to the 
44    * document root.</li>
45    *     <li><strong>Result stack</strong> contains the objects which are bound
46    * to the current element and to each of it's ancester's back to the root</li>
47    *     <li><strong>Element mapping stack</strong> records the names of the element
48    * and the classes to which they are bound</li>
49    * </ul>
50    * @author Robert Burrell Donkina
51    * @since 0.5
52    */
53  public class ReadContext extends Context {
54  ;
55  	/** Classloader to be used to load beans during reading */
56  	private ClassLoader classLoader;
57  	/** The read specific configuration */
58  	private ReadConfiguration readConfiguration;
59  	/** Records the element path together with the locations where classes were mapped*/
60  	private ArrayStack elementMappingStack = new ArrayStack();
61  	/** Contains actions for each element */
62  	private ArrayStack actionMappingStack = new ArrayStack();
63  	/** Stack contains all beans created */
64  	private ArrayStack objectStack = new ArrayStack();
65      /** Stack contains element descriptors */
66      private ArrayStack descriptorStack = new ArrayStack();
67      /** Stack contains updaters */
68      private ArrayStack updaterStack = new ArrayStack();
69  
70  	private Class rootClass;
71      /** The <code>XMLIntrospector</code> to be used to map the xml*/
72  	private XMLIntrospector xmlIntrospector;
73  
74  	/** 
75  	  * Constructs a <code>ReadContext</code> with the same settings 
76  	  * as an existing <code>Context</code>.
77  	  * @param context not null
78  	  * @param readConfiguration not null
79  	  */
80  	public ReadContext(Context context, ReadConfiguration readConfiguration) {
81  		super(context);
82  		this.readConfiguration = readConfiguration;
83  	}
84  
85  	/**
86  	  * Constructs a <code>ReadContext</code> with standard log.
87  	  * @param bindingConfiguration the dynamic configuration, not null
88  	  * @param readConfiguration the extra read configuration not null
89  	  */
90  	public ReadContext(
91  		BindingConfiguration bindingConfiguration,
92  		ReadConfiguration readConfiguration) {
93  		this(
94  			LogFactory.getLog(ReadContext.class),
95  			bindingConfiguration,
96  			readConfiguration);
97  	}
98  
99  	/** 
100 	  * Base constructor
101 	  * @param log log to this Log
102 	  * @param bindingConfiguration the dynamic configuration, not null
103 	  * @param readConfiguration the extra read configuration not null
104 	  */
105 	public ReadContext(
106 		Log log,
107 		BindingConfiguration bindingConfiguration,
108 		ReadConfiguration readConfiguration) {
109 		super(null, log, bindingConfiguration);
110 		this.readConfiguration = readConfiguration;
111 	}
112 
113 	/** 
114 	  * Constructs a <code>ReadContext</code> 
115 	  * with the same settings as an existing <code>Context</code>.
116 	  * @param readContext not null
117 	  */
118 	public ReadContext(ReadContext readContext) {
119 		super(readContext);
120 		classLoader = readContext.classLoader;
121 		readConfiguration = readContext.readConfiguration;
122 	}
123 
124 	/**
125 	 * Puts a bean into storage indexed by an (xml) ID.
126 	 *
127 	 * @param id the ID string of the xml element associated with the bean
128 	 * @param bean the Object to store, not null
129 	 */
130 	public void putBean(String id, Object bean) {
131 		getIdMappingStrategy().setReference(this, bean, id);
132 	}
133 
134 	/**
135 	 * Gets a bean from storage by an (xml) ID.
136 	 *
137 	 * @param id the ID string of the xml element associated with the bean
138 	 * @return the Object that the ID references, otherwise null
139 	 */
140 	public Object getBean(String id) {
141 		return getIdMappingStrategy().getReferenced(this, id);
142 	}
143 
144 	/** 
145 	 * Clears the beans indexed by id.
146 	 */
147 	public void clearBeans() {
148         getIdMappingStrategy().reset();
149 	}
150 
151 	/**
152 	  * Gets the classloader to be used.
153 	  * @return the classloader that should be used to load all classes, possibly null
154 	  */
155 	public ClassLoader getClassLoader() {
156 		return classLoader;
157 	}
158 
159 	/**
160 	  * Sets the classloader to be used.
161 	  * @param classLoader the ClassLoader to be used, possibly null
162 	  */
163 	public void setClassLoader(ClassLoader classLoader) {
164 		this.classLoader = classLoader;
165 	}
166 
167 	/** 
168 	  * Gets the <code>BeanCreationChange</code> to be used to create beans 
169 	  * when an element is mapped.
170 	  * @return the BeanCreationChain not null
171 	  */
172 	public BeanCreationChain getBeanCreationChain() {
173 		return readConfiguration.getBeanCreationChain();
174 	}
175 
176     /**
177      * Gets the strategy used to define default mappings actions
178      * for elements.
179      * @return <code>ActionMappingStrategy</code>. not null
180      */
181     public ActionMappingStrategy getActionMappingStrategy() {
182         return readConfiguration.getActionMappingStrategy();
183     }
184 
185 	/**
186 	  * Pops the top element from the element mapping stack.
187 	  * Also removes any mapped class marks below the top element.
188 	  *
189 	  * @return the name of the element popped 
190 	  * if there are any more elements on the stack, otherwise null.
191 	  * This is the local name if the parser is namespace aware, otherwise the name
192 	  */
193 	public String popElement() {
194         // since the descriptor stack is populated by pushElement,
195         // need to ensure that it's correct popped by popElement
196         if (!descriptorStack.isEmpty()) {
197             descriptorStack.pop();
198         }
199         
200         if (!updaterStack.isEmpty()) {
201             updaterStack.pop();
202         }
203         
204         popOptions();
205         
206 		Object top = null;
207 		if (!elementMappingStack.isEmpty()) {
208 			top = elementMappingStack.pop();
209 			if (top != null) {
210 				if (!(top instanceof String)) {
211 					return popElement();
212 				}
213 			}
214 		}
215 
216 		return (String) top;
217 	}
218 
219     /**
220      * Gets the element name for the currently mapped element.
221      * @return the name of the currently mapped element, 
222      * or null if there has been no element mapped 
223      */
224 	public String getCurrentElement() {
225 	    String result = null;
226 	    int stackSize = elementMappingStack.size();
227 	    int i = 0;
228 	    while ( i < stackSize ) {
229 	        Object mappedElement = elementMappingStack.peek(i);
230 	        if (mappedElement instanceof String) {
231 	            result  = (String) mappedElement;
232 	            break;
233 	        }
234 	        ++i;
235 	    }
236 	    return result;
237 	}
238 
239 	/**
240 	  * Gets the Class that was last mapped, if there is one.
241 	  * 
242 	  * @return the Class last marked as mapped 
243       * or null if no class has been mapped
244 	  */
245 	public Class getLastMappedClass() {
246         Class lastMapped = null;
247         for (int i = 0, size = elementMappingStack.size();
248             i < size;
249             i++) {
250             Object entry = elementMappingStack.peek(i);
251             if (entry instanceof Class) {
252                 lastMapped = (Class) entry;
253                 break;
254             }
255         }
256         return lastMapped;
257 	}
258 
259     private ElementDescriptor getParentDescriptor() throws IntrospectionException {
260         ElementDescriptor result = null;
261         if (descriptorStack.size() > 1) {
262             result = (ElementDescriptor) descriptorStack.peek(1);
263         }
264         return result;
265     }
266     
267 
268 	/** 
269 	  * Pushes the given element onto the element mapping stack.
270 	  *
271 	  * @param elementName the local name if the parser is namespace aware,
272 	  * otherwise the full element name. Not null
273 	  */
274 	public void pushElement(String elementName) throws Exception {
275 
276 		elementMappingStack.push(elementName);
277 		// special case to ensure that root class is appropriately marked
278 		//TODO: is this really necessary?
279         ElementDescriptor nextDescriptor = null;
280 		if (elementMappingStack.size() == 1 && rootClass != null) {
281 			markClassMap(rootClass);
282             XMLBeanInfo rootClassInfo 
283                 = getXMLIntrospector().introspect(rootClass);
284             nextDescriptor = rootClassInfo.getElementDescriptor();
285 		} else {
286             ElementDescriptor currentDescriptor = getCurrentDescriptor();
287             if (currentDescriptor != null) {
288                 nextDescriptor = currentDescriptor.getElementDescriptor(elementName);
289             }
290         }
291         Updater updater = null;
292         Options options = null;
293         if (nextDescriptor != null) {
294             updater = nextDescriptor.getUpdater();
295             options = nextDescriptor.getOptions();
296         }
297         updaterStack.push(updater);
298         descriptorStack.push(nextDescriptor);
299         pushOptions(options);
300 	}
301 
302 	/**
303 	  * Marks the element name stack with a class mapping.
304 	  * Relative paths and last mapped class are calculated using these marks.
305 	  * 
306 	  * @param mappedClazz the Class which has been mapped at the current path, not null
307 	  */
308 	public void markClassMap(Class mappedClazz) throws IntrospectionException {
309         if (mappedClazz.isArray()) {
310             mappedClazz = mappedClazz.getComponentType();
311         }
312 		elementMappingStack.push(mappedClazz);
313         
314         XMLBeanInfo mappedClassInfo = getXMLIntrospector().introspect(mappedClazz);
315         ElementDescriptor mappedElementDescriptor = mappedClassInfo.getElementDescriptor();
316         descriptorStack.push(mappedElementDescriptor);
317         
318         Updater updater = mappedElementDescriptor.getUpdater();
319         updaterStack.push(updater);
320 	}
321 
322 	/**
323 	 * Pops an action mapping from the stack
324 	 * @return <code>MappingAction</code>, not null
325 	 */
326 	public MappingAction popMappingAction() {
327 		return (MappingAction) actionMappingStack.pop();
328 	}
329 
330 	/**
331 	 * Pushs an action mapping onto the stack
332 	 * @param mappingAction
333 	 */
334 	public void pushMappingAction(MappingAction mappingAction) {
335 		actionMappingStack.push(mappingAction);
336 	}
337 
338 	/**
339 	 * Gets the current mapping action
340 	 * @return MappingAction 
341 	 */
342 	public MappingAction currentMappingAction() {
343 		if (actionMappingStack.size() == 0)
344 		{
345 			return null;	
346 		}
347 		return (MappingAction) actionMappingStack.peek();
348 	}
349 
350 	public Object getBean() {
351 		return objectStack.peek();
352 	}
353 
354 	public void setBean(Object bean) {
355 		// TODO: maybe need to deprecate the set bean method
356 		// and push into subclass
357 		// for now, do nothing		
358 	}
359 
360     /**
361      * Pops the last mapping <code>Object</code> from the 
362      * stack containing beans that have been mapped.
363      * @return the last bean pushed onto the stack
364      */
365 	public Object popBean() {
366 		return objectStack.pop();
367 	}
368 
369     /**
370      * Pushs a newly mapped <code>Object</code> onto the mapped bean stack.
371      * @param bean
372      */
373 	public void pushBean(Object bean) {
374 		objectStack.push(bean);
375 	}
376 
377     /**
378      * Gets the <code>XMLIntrospector</code> to be used to create
379      * the mappings for the xml.
380      * @return <code>XMLIntrospector</code>, not null
381      */
382 	public XMLIntrospector getXMLIntrospector() {
383         // read context is not intended to be used by multiple threads
384         // so no need to worry about lazy creation
385         if (xmlIntrospector == null) {
386             xmlIntrospector = new XMLIntrospector();
387         }
388 		return xmlIntrospector;
389 	}
390 
391     /**
392      * Sets the <code>XMLIntrospector</code> to be used to create
393      * the mappings for the xml.
394      * @param xmlIntrospector <code>XMLIntrospector</code>, not null
395      */
396 	public void setXMLIntrospector(XMLIntrospector xmlIntrospector) {
397 		this.xmlIntrospector = xmlIntrospector;
398 	}
399 
400 	public Class getRootClass() {
401 		return rootClass;
402 	}
403 
404 	public void setRootClass(Class rootClass) {
405 		this.rootClass = rootClass;
406 	}
407 
408     /**
409      * Gets the <code>ElementDescriptor</code> that describes the
410      * mapping for the current element.
411      * @return <code>ElementDescriptor</code> or null if there is no
412      * current mapping
413      * @throws Exception
414      */
415 	public ElementDescriptor getCurrentDescriptor() throws Exception {
416 		ElementDescriptor result = null;
417         if (!descriptorStack.empty()) {
418             result = (ElementDescriptor) descriptorStack.peek();
419         }
420 		return result;
421 	}
422     
423     /**
424      * Populates the object mapped by the <code>AttributeDescriptor</code>s
425      * with the values in the given <code>Attributes</code>.
426      * @param attributeDescriptors <code>AttributeDescriptor</code>s, not null
427      * @param attributes <code>Attributes</code>, not null
428      */
429 	public void populateAttributes(
430 		AttributeDescriptor[] attributeDescriptors,
431 		Attributes attributes) {
432 
433 		Log log = getLog();
434 		if (attributeDescriptors != null) {
435 			for (int i = 0, size = attributeDescriptors.length;
436 				i < size;
437 				i++) {
438 				AttributeDescriptor attributeDescriptor =
439 					attributeDescriptors[i];
440 
441 				// The following isn't really the right way to find the attribute
442 				// but it's quite robust.
443 				// The idea is that you try both namespace and local name first
444 				// and if this returns null try the qName.
445 				String value =
446 					attributes.getValue(
447 						attributeDescriptor.getURI(),
448 						attributeDescriptor.getLocalName());
449 
450 				if (value == null) {
451 					value =
452 						attributes.getValue(
453 							attributeDescriptor.getQualifiedName());
454 				}
455 
456 				if (log.isTraceEnabled()) {
457 					log.trace("Attr URL:" + attributeDescriptor.getURI());
458 					log.trace(
459 						"Attr LocalName:" + attributeDescriptor.getLocalName());
460 					log.trace(value);
461 				}
462 
463 				Updater updater = attributeDescriptor.getUpdater();
464 				log.trace(updater);
465 				if (updater != null && value != null) {
466 					updater.update(this, value);
467 				}
468 			}
469 		}
470 	}
471 
472     /**
473      * <p>Pushes an <code>Updater</code> onto the stack.</p>
474      * <p>
475      * <strong>Note</strong>Any action pushing an <code>Updater</code> onto
476      * the stack should take responsibility for popping
477      * the updater from the stack at an appropriate time.
478      * </p>
479      * <p>
480      * <strong>Usage:</strong> this may be used by actions
481      * which require a temporary object to be updated.
482      * Pushing an updater onto the stack allow actions
483      * downstream to transparently update the temporary proxy.
484      * </p>
485      * @param updater Updater, possibly null
486      */
487     public void pushUpdater(Updater updater) {
488         updaterStack.push(updater);
489     }
490     
491     /**
492      * Pops the top <code>Updater</code> from the stack.
493      * <p>
494      * <strong>Note</strong>Any action pushing an <code>Updater</code> onto
495      * the stack should take responsibility for popping
496      * the updater from the stack at an appropriate time.
497      * </p>
498      * @return <code>Updater</code>, possibly null
499      */
500     public Updater popUpdater() {
501         return (Updater) updaterStack.pop();
502     }
503 
504     /**
505      * Gets the current <code>Updater</code>.
506      * This may (or may not) be the updater for the current
507      * descriptor.
508      * If the current descriptor is a bean child,
509      * the the current updater will (most likely) 
510      * be the updater for the property.
511      * Actions (that, for example, use proxy objects)
512      * may push updaters onto the stack.
513      * @return Updater, possibly null
514      */
515     public Updater getCurrentUpdater() {
516         // TODO: think about whether this is right
517         //       it makes some sense to look back up the 
518         //       stack until a non-empty updater is found.
519         //       actions who need to put a stock to this 
520         //       behaviour can always use an ignoring implementation. 
521         Updater result = null;
522         if (!updaterStack.empty()) {
523             result = (Updater) updaterStack.peek();
524             if ( result == null && updaterStack.size() >1 ) {
525                 result = (Updater) updaterStack.peek(1);
526             }
527         }
528         return result;  
529     }
530 
531     /**
532      * Resolves any polymorphism in the element mapping.
533      * @param mapping <code>ElementMapping</code> describing the mapped element
534      * @return <code>null</code> if the type cannot be resolved 
535      * or if the current descriptor is not polymorphic
536      * @since 0.8
537      */
538     public Class resolvePolymorphicType(ElementMapping mapping) {
539         Class result = null;
540         Log log = getLog();
541         try {
542             ElementDescriptor currentDescriptor = getCurrentDescriptor();
543             if (currentDescriptor != null) {
544                 if (currentDescriptor.isPolymorphic()) {
545                     PolymorphicReferenceResolver resolver = getXMLIntrospector().getPolymorphicReferenceResolver();
546                     result = resolver.resolveType(mapping, this);
547                     if (result == null) {
548                         // try the other polymorphic descriptors
549                         ElementDescriptor parent = getParentDescriptor();
550                         if (parent != null) {
551                             ElementDescriptor[] descriptors = parent.getElementDescriptors();
552                             ElementDescriptor originalDescriptor = mapping.getDescriptor();
553                             boolean resolved = false;
554                             for (int i=0; i<descriptors.length;i++) {
555                                 ElementDescriptor descriptor = descriptors[i];
556                                 if (descriptor.isPolymorphic()) {
557                                     mapping.setDescriptor(descriptor);
558                                     result = resolver.resolveType(mapping, this);
559                                     if (result != null) {
560                                         resolved = true;
561                                         descriptorStack.pop();
562                                         popOptions();
563                                         descriptorStack.push(descriptor);
564                                         pushOptions(descriptor.getOptions());
565                                         Updater originalUpdater = originalDescriptor.getUpdater();
566                                         Updater newUpdater = descriptor.getUpdater();
567                                         substituteUpdater(originalUpdater, newUpdater);
568                                         break;
569                                     }
570                                 }
571                             }
572                             if (resolved) {
573                                 log.debug("Resolved polymorphic type");
574                             } else {
575                                 log.debug("Failed to resolve polymorphic type");
576                                 mapping.setDescriptor(originalDescriptor);
577                             }
578                         }
579                     }
580                 }
581             }
582         } catch (Exception e) {
583             log.info("Failed to resolved polymorphic type");
584             log.debug(mapping, e);
585         }
586         return result;
587     }
588 
589     /**
590      * Substitutes one updater in the stack for another.
591      * @param originalUpdater <code>Updater</code> possibly null
592      * @param newUpdater <code>Updater</code> possibly null
593      */
594     private void substituteUpdater(Updater originalUpdater, Updater newUpdater) {
595         // recursively pop elements off the stack until the first match is found
596         // TODO: may need to consider using custom NILL object and match descriptors
597         if (!updaterStack.isEmpty()) {
598             Updater updater = (Updater) updaterStack.pop();
599             if (originalUpdater == null && updater == null) {
600                 updaterStack.push(newUpdater);
601             } else if (originalUpdater.equals(updater)) {
602                 updaterStack.push(newUpdater);
603             } else {
604                 substituteUpdater(originalUpdater, newUpdater);
605                 updaterStack.push(updater);
606             }
607         }
608     }
609 
610 }