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.commons.betwixt.io.read;
018:
019:import java.beans.IntrospectionException;
020:
021:import org.apache.commons.betwixt.AttributeDescriptor;
022:import org.apache.commons.betwixt.BindingConfiguration;
023:import org.apache.commons.betwixt.ElementDescriptor;
024:import org.apache.commons.betwixt.Options;
025:import org.apache.commons.betwixt.XMLBeanInfo;
026:import org.apache.commons.betwixt.XMLIntrospector;
027:import org.apache.commons.betwixt.expression.Context;
028:import org.apache.commons.betwixt.expression.Updater;
029:import org.apache.commons.betwixt.registry.PolymorphicReferenceResolver;
030:import org.apache.commons.betwixt.strategy.ActionMappingStrategy;
031:import org.apache.commons.collections.ArrayStack;
032:import org.apache.commons.logging.Log;
033:import org.apache.commons.logging.LogFactory;
034:import org.xml.sax.Attributes;
035:
036:/**
037: * <p>Extends <code>Context</code> to provide read specific functionality.</p>
038: * <p>
039: * Three stacks are used to manage the reading:
040: * </p>
041: * <ul>
042: * <li><strong>Action mapping stack</strong> contains the {@link MappingAction}'s
043: * used to execute the mapping of the current element and it's ancesters back to the
044: * document root.</li>
045: * <li><strong>Result stack</strong> contains the objects which are bound
046: * to the current element and to each of it's ancester's back to the root</li>
047: * <li><strong>Element mapping stack</strong> records the names of the element
048: * and the classes to which they are bound</li>
049: * </ul>
050: * @author Robert Burrell Donkina
051: * @since 0.5
052: */
053:public class ReadContext extends Context {
054:;
055: /** Classloader to be used to load beans during reading */
056: private ClassLoader classLoader;
057: /** The read specific configuration */
058: private ReadConfiguration readConfiguration;
059: /** Records the element path together with the locations where classes were mapped*/
060: private ArrayStack elementMappingStack = new ArrayStack();
061: /** Contains actions for each element */
062: private ArrayStack actionMappingStack = new ArrayStack();
063: /** Stack contains all beans created */
064: private ArrayStack objectStack = new ArrayStack();
065: /** Stack contains element descriptors */
066: private ArrayStack descriptorStack = new ArrayStack();
067: /** Stack contains updaters */
068: private ArrayStack updaterStack = new ArrayStack();
069:
070: private Class rootClass;
071: /** The <code>XMLIntrospector</code> to be used to map the xml*/
072: private XMLIntrospector xmlIntrospector;
073:
074: /**
075: * Constructs a <code>ReadContext</code> with the same settings
076: * as an existing <code>Context</code>.
077: * @param context not null
078: * @param readConfiguration not null
079: */
080: public ReadContext(Context context, ReadConfiguration readConfiguration) {
081: super (context);
082: this .readConfiguration = readConfiguration;
083: }
084:
085: /**
086: * Constructs a <code>ReadContext</code> with standard log.
087: * @param bindingConfiguration the dynamic configuration, not null
088: * @param readConfiguration the extra read configuration not null
089: */
090: public ReadContext(
091: BindingConfiguration bindingConfiguration,
092: ReadConfiguration readConfiguration) {
093: this (
094: LogFactory.getLog(ReadContext.class),
095: bindingConfiguration,
096: readConfiguration);
097: }
098:
099: /**
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:}
|