View Javadoc

1   /*
2    * Copyright 2004 The Apache Software Foundation.
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */ 
16  package org.apache.commons.betwixt.io.read;
17  
18  import java.util.Map;
19  
20  import org.apache.commons.betwixt.AttributeDescriptor;
21  import org.apache.commons.betwixt.ElementDescriptor;
22  import org.apache.commons.betwixt.TextDescriptor;
23  import org.apache.commons.betwixt.XMLBeanInfo;
24  import org.apache.commons.betwixt.expression.Updater;
25  import org.apache.commons.logging.Log;
26  import org.xml.sax.Attributes;
27  
28  /***
29   * Action that creates and binds a new bean instance.
30   * 
31   * @author <a href='http://jakarta.apache.org/'>Jakarta Commons Team</a>
32   * @version $Revision: 1.2 $
33   */
34  public class BeanBindAction extends MappingAction.Base {
35  
36      /*** Singleton instance */
37      public static final BeanBindAction INSTANCE = new BeanBindAction();
38  
39      /***
40       * Begins a new element which is to be bound to a bean.
41       */
42      public MappingAction begin(
43          String namespace,
44          String name,
45          Attributes attributes,
46          ReadContext context)
47                      throws Exception {
48                          
49          Log log = context.getLog();
50  
51          ElementDescriptor computedDescriptor = context.getCurrentDescriptor();
52  
53          if (log.isTraceEnabled()) {
54              log.trace("Element Pushed: " + name);
55          }
56  
57          // default to ignoring the current element
58          MappingAction action = MappingAction.EMPTY;
59  
60          Object instance = null;
61          Class beanClass = null;
62          if (computedDescriptor == null) {
63              log.trace("No Descriptor");
64          } else {
65              beanClass = computedDescriptor.getSingularPropertyType();
66          }
67          // TODO: this is a bit of a workaround 
68          // need to come up with a better way of doing maps
69          if (beanClass != null && !Map.class.isAssignableFrom(beanClass)) {
70  
71              instance =
72                  createBean(
73                      namespace,
74                      name,
75                      attributes,
76                      computedDescriptor,
77                      context);
78                      
79              if (instance != null) {
80                  action = this;
81                  context.markClassMap(beanClass);
82  
83                  if (log.isTraceEnabled()) {
84                      log.trace("Marked: " + beanClass);
85                  }
86  
87                  context.pushBean(instance);
88  
89                  // if we are a reference to a type we should lookup the original
90                  // as this ElementDescriptor will be 'hollow' 
91                  // and have no child attributes/elements.
92                  // XXX: this should probably be done by the NodeDescriptors...
93                  ElementDescriptor typeDescriptor =
94                      getElementDescriptor(computedDescriptor, context);
95                  //ElementDescriptor typeDescriptor = descriptor;
96  
97                  // iterate through all attributes        
98                  AttributeDescriptor[] attributeDescriptors =
99                      typeDescriptor.getAttributeDescriptors();
100                 context.populateAttributes(attributeDescriptors, attributes);
101 
102                 if (log.isTraceEnabled()) {
103                     log.trace("Created bean " + instance);
104                 }
105 
106                 // add bean for ID matching
107                 if (context.getMapIDs()) {
108                     // XXX need to support custom ID attribute names
109                     // XXX i have a feeling that the current mechanism might need to change
110                     // XXX so i'm leaving this till later
111                     String id = attributes.getValue("id");
112                     if (id != null) {
113                         context.putBean(id, instance);
114                     }
115                 }
116             }
117         }
118         return action;
119     }
120 
121 
122     public void body(String text, ReadContext context) throws Exception {
123         Log log = context.getLog();
124         // Take the first content descriptor
125         ElementDescriptor currentDescriptor = context.getCurrentDescriptor();
126         if (currentDescriptor == null) {
127             if (log.isTraceEnabled()) {
128                 log.trace("path descriptor is null:");
129             }
130         } else {
131             TextDescriptor bodyTextdescriptor =
132                 currentDescriptor.getPrimaryBodyTextDescriptor();
133             if (bodyTextdescriptor != null) {
134                 if (log.isTraceEnabled()) {
135                     log.trace("Setting mixed content for:");
136                     log.trace(bodyTextdescriptor);
137                 }
138                 Updater updater = bodyTextdescriptor.getUpdater();
139                 if (log.isTraceEnabled())
140                 {    
141                     log.trace("Updating mixed content with:");
142                     log.trace(updater);
143                 }
144                 if (updater != null && text != null) {
145                     updater.update(context, text);
146                 }
147             }
148         }
149     }
150 
151     public void end(ReadContext context) throws Exception {
152         // force any setters of the parent bean to be called for this new bean instance
153         Object instance = context.popBean();
154         update(context, instance);
155     }
156 
157     private void update(ReadContext context, Object value) throws Exception {
158         Log log = context.getLog();
159 
160         Updater updater = context.getCurrentUpdater();
161         
162         if ( updater == null ) {
163             if ( context.getLog().isTraceEnabled() ) {
164                 context.getLog().trace("No updater for " + context.getCurrentElement());
165             }
166         } else {
167             updater.update(context, value);
168         }
169 
170         String poppedElement = context.popElement();
171     }
172 
173 
174 
175 
176     /*** 
177     * Factory method to create new bean instances 
178     *
179     * @param namespace the namespace for the element
180     * @param name the local name
181     * @param attributes the <code>Attributes</code> used to match <code>ID/IDREF</code>
182     * @return the created bean
183     */
184     protected Object createBean(
185         String namespace,
186         String name,
187         Attributes attributes,
188         ElementDescriptor descriptor,
189         ReadContext context) {
190         // TODO: recycle element mappings 
191         // Maybe should move the current mapping into the context
192         ElementMapping mapping = new ElementMapping();
193         Class beanClass = descriptor.getSingularPropertyType();
194         if (beanClass != null && beanClass.isArray()) {
195             beanClass = beanClass.getComponentType();
196         }
197 
198         // TODO: beanClass can be deduced from descriptor
199         // so probably 
200         mapping.setType(beanClass);
201         mapping.setNamespace(namespace);
202         mapping.setName(name);
203         mapping.setAttributes(attributes);
204         mapping.setDescriptor(descriptor);
205 
206         Object newInstance =
207             context.getBeanCreationChain().create(mapping, context);
208 
209         return newInstance;
210     }
211 
212     /*** Allows the navigation from a reference to a property object to the 
213     * descriptor defining what the property is. i.e. doing the join from a reference 
214     * to a type to lookup its descriptor.
215     * This could be done automatically by the NodeDescriptors. 
216     * Refer to TODO.txt for more info.
217     *
218     * @param propertyDescriptor find descriptor for property object 
219     * referenced by this descriptor
220     * @return descriptor for the singular property class type referenced.
221     */
222     ElementDescriptor getElementDescriptor(
223         ElementDescriptor propertyDescriptor,
224         ReadContext context) {
225         Log log = context.getLog();
226         Class beanClass = propertyDescriptor.getSingularPropertyType();
227         if (beanClass != null && !Map.class.isAssignableFrom(beanClass)) {
228             if (beanClass.isArray()) {
229                 beanClass = beanClass.getComponentType();
230             }
231             if (log.isTraceEnabled()) {
232                 log.trace("Filling descriptor for: " + beanClass);
233             }
234             try {
235                 XMLBeanInfo xmlInfo =
236                     context.getXMLIntrospector().introspect(beanClass);
237                 return xmlInfo.getElementDescriptor();
238 
239             } catch (Exception e) {
240                 log.warn("Could not introspect class: " + beanClass, e);
241             }
242         }
243         // could not find a better descriptor so use the one we've got
244         return propertyDescriptor;
245     }
246 
247 }