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.util.Map;
020    
021    import org.apache.commons.betwixt.AttributeDescriptor;
022    import org.apache.commons.betwixt.ElementDescriptor;
023    import org.apache.commons.betwixt.TextDescriptor;
024    import org.apache.commons.betwixt.XMLBeanInfo;
025    import org.apache.commons.betwixt.expression.Updater;
026    import org.apache.commons.logging.Log;
027    import org.xml.sax.Attributes;
028    
029    /**
030     * Action that creates and binds a new bean instance.
031     * 
032     * @author <a href='http://commons.apache.org/'>Apache Commons Team</a>
033     * @version $Revision: 561314 $
034     */
035    public class BeanBindAction extends MappingAction.Base {
036    
037        /** Singleton instance */
038        public static final BeanBindAction INSTANCE = new BeanBindAction();
039    
040        /**
041         * Begins a new element which is to be bound to a bean.
042         */
043        public MappingAction begin(
044            String namespace,
045            String name,
046            Attributes attributes,
047            ReadContext context)
048                        throws Exception {
049                            
050            Log log = context.getLog();
051    
052            ElementDescriptor computedDescriptor = context.getCurrentDescriptor();
053    
054            if (log.isTraceEnabled()) {
055                log.trace("Element Pushed: " + name);
056            }
057    
058            // default to ignoring the current element
059            MappingAction action = MappingAction.EMPTY;
060    
061            Object instance = null;
062            Class beanClass = null;
063            if (computedDescriptor == null) {
064                log.trace("No Descriptor");
065            } else {
066                beanClass = computedDescriptor.getSingularPropertyType();
067            }
068            // TODO: this is a bit of a workaround 
069            // need to come up with a better way of doing maps
070            if (beanClass != null && !Map.class.isAssignableFrom(beanClass)) {
071    
072                instance =
073                    createBean(
074                        namespace,
075                        name,
076                        attributes,
077                        computedDescriptor,
078                        context);
079                        
080                if (instance != null) {
081                    action = this;
082                    if (computedDescriptor.isUseBindTimeTypeForMapping())
083                    {
084                        beanClass = instance.getClass();
085                    }
086                    context.markClassMap(beanClass);
087    
088                    if (log.isTraceEnabled()) {
089                        log.trace("Marked: " + beanClass);
090                    }
091    
092                    context.pushBean(instance);
093    
094                    // if we are a reference to a type we should lookup the original
095                    // as this ElementDescriptor will be 'hollow' 
096                    // and have no child attributes/elements.
097                    // XXX: this should probably be done by the NodeDescriptors...
098                    ElementDescriptor typeDescriptor =
099                        getElementDescriptor(computedDescriptor, context);
100    
101                    // iterate through all attributes        
102                    AttributeDescriptor[] attributeDescriptors =
103                        typeDescriptor.getAttributeDescriptors();
104                    context.populateAttributes(attributeDescriptors, attributes);
105    
106                    if (log.isTraceEnabled()) {
107                        log.trace("Created bean " + instance);
108                    }
109    
110                    // add bean for ID matching
111                    if (context.getMapIDs()) {
112                        // XXX need to support custom ID attribute names
113                        // XXX i have a feeling that the current mechanism might need to change
114                        // XXX so i'm leaving this till later
115                        String id = attributes.getValue("id");
116                        if (id != null) {
117                            context.putBean(id, instance);
118                        }
119                    }
120                }
121            }
122            return action;
123        }
124    
125    
126        public void body(String text, ReadContext context) throws Exception {
127            Log log = context.getLog();
128            // Take the first content descriptor
129            ElementDescriptor currentDescriptor = context.getCurrentDescriptor();
130            if (currentDescriptor == null) {
131                if (log.isTraceEnabled()) {
132                    log.trace("path descriptor is null:");
133                }
134            } else {
135                TextDescriptor bodyTextdescriptor =
136                    currentDescriptor.getPrimaryBodyTextDescriptor();
137                if (bodyTextdescriptor != null) {
138                    if (log.isTraceEnabled()) {
139                        log.trace("Setting mixed content for:");
140                        log.trace(bodyTextdescriptor);
141                    }
142                    Updater updater = bodyTextdescriptor.getUpdater();
143                    if (log.isTraceEnabled())
144                    {    
145                        log.trace("Updating mixed content with:");
146                        log.trace(updater);
147                    }
148                    if (updater != null && text != null) {
149                        updater.update(context, text);
150                    }
151                }
152            }
153        }
154    
155        public void end(ReadContext context) throws Exception {
156            // force any setters of the parent bean to be called for this new bean instance
157            Object instance = context.popBean();
158            update(context, instance);
159        }
160    
161        private void update(ReadContext context, Object value) throws Exception {
162            Log log = context.getLog();
163    
164            Updater updater = context.getCurrentUpdater();
165            
166            if ( updater == null ) {
167                if ( context.getLog().isTraceEnabled() ) {
168                    context.getLog().trace("No updater for " + context.getCurrentElement());
169                }
170            } else {
171                updater.update(context, value);
172            }
173    
174            String poppedElement = context.popElement();
175        }
176    
177    
178    
179    
180        /** 
181        * Factory method to create new bean instances 
182        *
183        * @param namespace the namespace for the element
184        * @param name the local name
185        * @param attributes the <code>Attributes</code> used to match <code>ID/IDREF</code>
186        * @return the created bean
187        */
188        protected Object createBean(
189            String namespace,
190            String name,
191            Attributes attributes,
192            ElementDescriptor descriptor,
193            ReadContext context) {
194            // TODO: recycle element mappings 
195            // Maybe should move the current mapping into the context
196            ElementMapping mapping = new ElementMapping();
197            Class beanClass = descriptor.getSingularPropertyType();
198            if (beanClass != null && beanClass.isArray()) {
199                beanClass = beanClass.getComponentType();
200            }
201    
202            // TODO: beanClass can be deduced from descriptor
203            // so this feels a little over-engineered
204            mapping.setType(beanClass);
205            mapping.setNamespace(namespace);
206            mapping.setName(name);
207            mapping.setAttributes(attributes);
208            mapping.setDescriptor(descriptor);
209    
210            Object newInstance =
211                context.getBeanCreationChain().create(mapping, context);
212    
213            return newInstance;
214        }
215    
216        /** Allows the navigation from a reference to a property object to the 
217        * descriptor defining what the property is. i.e. doing the join from a reference 
218        * to a type to lookup its descriptor.
219        * This could be done automatically by the NodeDescriptors. 
220        * Refer to TODO.txt for more info.
221        *
222        * @param propertyDescriptor find descriptor for property object 
223        * referenced by this descriptor
224        * @return descriptor for the singular property class type referenced.
225        */
226        private ElementDescriptor getElementDescriptor(
227            ElementDescriptor propertyDescriptor,
228            ReadContext context) {
229            Log log = context.getLog();
230            Class beanClass = propertyDescriptor.getSingularPropertyType();
231            if (propertyDescriptor.isUseBindTimeTypeForMapping()) {
232                // use the actual bind time type
233                Object current = context.getBean();
234                if (current != null) {
235                    beanClass = current.getClass();
236                }
237            }
238            if (beanClass != null && !Map.class.isAssignableFrom(beanClass)) {
239                if (beanClass.isArray()) {
240                    beanClass = beanClass.getComponentType();
241                }
242                // support for derived beans
243                
244                
245                if (log.isTraceEnabled()) {
246                    log.trace("Filling descriptor for: " + beanClass);
247                }
248                try {
249                    XMLBeanInfo xmlInfo =
250                        context.getXMLIntrospector().introspect(beanClass);
251                    return xmlInfo.getElementDescriptor();
252    
253                } catch (Exception e) {
254                    log.warn("Could not introspect class: " + beanClass, e);
255                }
256            }
257            // could not find a better descriptor so use the one we've got
258            return propertyDescriptor;
259        }
260    
261    }