View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */ 
17  package org.apache.commons.betwixt.io.read;
18  
19  import java.util.Map;
20  
21  import org.apache.commons.betwixt.AttributeDescriptor;
22  import org.apache.commons.betwixt.ElementDescriptor;
23  import org.apache.commons.betwixt.TextDescriptor;
24  import org.apache.commons.betwixt.XMLBeanInfo;
25  import org.apache.commons.betwixt.expression.Updater;
26  import org.apache.commons.logging.Log;
27  import org.xml.sax.Attributes;
28  
29  /**
30   * Action that creates and binds a new bean instance.
31   * 
32   * @author <a href='http://commons.apache.org/'>Apache Commons Team</a>
33   * @version $Revision: 561314 $
34   */
35  public class BeanBindAction extends MappingAction.Base {
36  
37      /** Singleton instance */
38      public static final BeanBindAction INSTANCE = new BeanBindAction();
39  
40      /**
41       * Begins a new element which is to be bound to a bean.
42       */
43      public MappingAction begin(
44          String namespace,
45          String name,
46          Attributes attributes,
47          ReadContext context)
48                      throws Exception {
49                          
50          Log log = context.getLog();
51  
52          ElementDescriptor computedDescriptor = context.getCurrentDescriptor();
53  
54          if (log.isTraceEnabled()) {
55              log.trace("Element Pushed: " + name);
56          }
57  
58          // default to ignoring the current element
59          MappingAction action = MappingAction.EMPTY;
60  
61          Object instance = null;
62          Class beanClass = null;
63          if (computedDescriptor == null) {
64              log.trace("No Descriptor");
65          } else {
66              beanClass = computedDescriptor.getSingularPropertyType();
67          }
68          // TODO: this is a bit of a workaround 
69          // need to come up with a better way of doing maps
70          if (beanClass != null && !Map.class.isAssignableFrom(beanClass)) {
71  
72              instance =
73                  createBean(
74                      namespace,
75                      name,
76                      attributes,
77                      computedDescriptor,
78                      context);
79                      
80              if (instance != null) {
81                  action = this;
82                  if (computedDescriptor.isUseBindTimeTypeForMapping())
83                  {
84                      beanClass = instance.getClass();
85                  }
86                  context.markClassMap(beanClass);
87  
88                  if (log.isTraceEnabled()) {
89                      log.trace("Marked: " + beanClass);
90                  }
91  
92                  context.pushBean(instance);
93  
94                  // if we are a reference to a type we should lookup the original
95                  // as this ElementDescriptor will be 'hollow' 
96                  // and have no child attributes/elements.
97                  // XXX: this should probably be done by the NodeDescriptors...
98                  ElementDescriptor typeDescriptor =
99                      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 }