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 }