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 }