View Javadoc

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