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    }