1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
195
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
278
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
356
357
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
384
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
442
443
444
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
517
518
519
520
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
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
596
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 }