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