1 /* 2 * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.xml.internal.bind.v2.runtime; 27 28 import java.io.IOException; 29 import java.lang.reflect.Constructor; 30 import java.lang.reflect.InvocationTargetException; 31 32 import javax.xml.bind.JAXBElement; 33 import javax.xml.bind.JAXBException; 34 import javax.xml.namespace.QName; 35 import javax.xml.stream.XMLStreamException; 36 37 import com.sun.xml.internal.bind.api.AccessorException; 38 import com.sun.xml.internal.bind.v2.model.core.PropertyKind; 39 import com.sun.xml.internal.bind.v2.model.nav.Navigator; 40 import com.sun.xml.internal.bind.v2.model.runtime.RuntimeElementInfo; 41 import com.sun.xml.internal.bind.v2.model.runtime.RuntimePropertyInfo; 42 import com.sun.xml.internal.bind.v2.runtime.property.Property; 43 import com.sun.xml.internal.bind.v2.runtime.property.PropertyFactory; 44 import com.sun.xml.internal.bind.v2.runtime.property.UnmarshallerChain; 45 import com.sun.xml.internal.bind.v2.runtime.reflect.Accessor; 46 import com.sun.xml.internal.bind.v2.runtime.unmarshaller.ChildLoader; 47 import com.sun.xml.internal.bind.v2.runtime.unmarshaller.Discarder; 48 import com.sun.xml.internal.bind.v2.runtime.unmarshaller.Intercepter; 49 import com.sun.xml.internal.bind.v2.runtime.unmarshaller.Loader; 50 import com.sun.xml.internal.bind.v2.runtime.unmarshaller.TagName; 51 import com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallingContext; 52 import com.sun.xml.internal.bind.v2.util.QNameMap; 53 54 import org.xml.sax.SAXException; 55 56 /** 57 * {@link JaxBeanInfo} implementation for {@link RuntimeElementInfo}. 58 * 59 * @author Kohsuke Kawaguchi 60 */ 61 public final class ElementBeanInfoImpl extends JaxBeanInfo<JAXBElement> { 62 63 private Loader loader; 64 65 private final Property property; 66 67 // used to create new instances of JAXBElement. 68 private final QName tagName; 69 public final Class expectedType; 70 private final Class scope; 71 72 /** 73 * If non-null, use this to create an instance. 74 * It takes one value. 75 */ 76 private final Constructor<? extends JAXBElement> constructor; 77 78 ElementBeanInfoImpl(JAXBContextImpl grammar, RuntimeElementInfo rei) { 79 super(grammar,rei,(Class<JAXBElement>)rei.getType(),true,false,true); 80 81 this.property = PropertyFactory.create(grammar,rei.getProperty()); 82 83 tagName = rei.getElementName(); 84 expectedType = Navigator.REFLECTION.erasure(rei.getContentInMemoryType()); 85 scope = rei.getScope()==null ? JAXBElement.GlobalScope.class : rei.getScope().getClazz(); 86 87 Class type = Navigator.REFLECTION.erasure(rei.getType()); 88 if(type==JAXBElement.class) 89 constructor = null; 90 else { 91 try { 92 constructor = type.getConstructor(expectedType); 93 } catch (NoSuchMethodException e) { 94 NoSuchMethodError x = new NoSuchMethodError("Failed to find the constructor for " + type + " with " + expectedType); 95 x.initCause(e); 96 throw x; 97 } 98 } 99 } 100 101 /** 102 * The constructor for the sole instanceof {@link JaxBeanInfo} for 103 * handling user-created {@link JAXBElement}. 104 * 105 * Such {@link JaxBeanInfo} is used only for marshalling. 106 * 107 * This is a hack. 108 */ 109 protected ElementBeanInfoImpl(final JAXBContextImpl grammar) { 110 super(grammar,null,JAXBElement.class,true,false,true); 111 tagName = null; 112 expectedType = null; 113 scope = null; 114 constructor = null; 115 116 this.property = new Property<JAXBElement>() { 117 public void reset(JAXBElement o) { 118 throw new UnsupportedOperationException(); 119 } 120 121 public void serializeBody(JAXBElement e, XMLSerializer target, Object outerPeer) throws SAXException, IOException, XMLStreamException { 122 Class scope = e.getScope(); 123 if(e.isGlobalScope()) scope = null; 124 QName n = e.getName(); 125 ElementBeanInfoImpl bi = grammar.getElement(scope,n); 126 if(bi==null) { 127 // infer what to do from the type 128 JaxBeanInfo tbi; 129 try { 130 tbi = grammar.getBeanInfo(e.getDeclaredType(),true); 131 } catch (JAXBException x) { 132 // if e.getDeclaredType() isn't known to this JAXBContext 133 target.reportError(null,x); 134 return; 135 } 136 Object value = e.getValue(); 137 target.startElement(n.getNamespaceURI(),n.getLocalPart(),n.getPrefix(),null); 138 if(value==null) { 139 target.writeXsiNilTrue(); 140 } else { 141 target.childAsXsiType(value,"value",tbi, false); 142 } 143 target.endElement(); 144 } else { 145 try { 146 bi.property.serializeBody(e,target,e); 147 } catch (AccessorException x) { 148 target.reportError(null,x); 149 } 150 } 151 } 152 153 public void serializeURIs(JAXBElement o, XMLSerializer target) { 154 } 155 156 public boolean hasSerializeURIAction() { 157 return false; 158 } 159 160 public String getIdValue(JAXBElement o) { 161 return null; 162 } 163 164 public PropertyKind getKind() { 165 return PropertyKind.ELEMENT; 166 } 167 168 public void buildChildElementUnmarshallers(UnmarshallerChain chain, QNameMap<ChildLoader> handlers) { 169 } 170 171 public Accessor getElementPropertyAccessor(String nsUri, String localName) { 172 throw new UnsupportedOperationException(); 173 } 174 175 public void wrapUp() { 176 } 177 178 public RuntimePropertyInfo getInfo() { 179 return property.getInfo(); 180 } 181 182 public boolean isHiddenByOverride() { 183 return false; 184 } 185 186 public void setHiddenByOverride(boolean hidden) { 187 throw new UnsupportedOperationException("Not supported on jaxbelements."); 188 } 189 190 public String getFieldName() { 191 return null; 192 } 193 194 }; 195 } 196 197 /** 198 * Use the previous {@link UnmarshallingContext.State}'s target to store 199 * {@link JAXBElement} object to be unmarshalled. This allows the property {@link Loader} 200 * to correctly find the parent object. 201 * This is a hack. 202 */ 203 private final class IntercepterLoader extends Loader implements Intercepter { 204 private final Loader core; 205 206 public IntercepterLoader(Loader core) { 207 this.core = core; 208 } 209 210 @Override 211 public final void startElement(UnmarshallingContext.State state, TagName ea) throws SAXException { 212 state.loader = core; 213 state.intercepter = this; 214 215 // TODO: make sure there aren't too many duplicate of this code 216 // create the object to unmarshal 217 Object child; 218 UnmarshallingContext context = state.getContext(); 219 220 // let's see if we can reuse the existing peer object 221 child = context.getOuterPeer(); 222 223 if(child!=null && jaxbType!=child.getClass()) 224 child = null; // unexpected type. 225 226 if(child!=null) 227 reset((JAXBElement)child,context); 228 229 if(child==null) 230 child = context.createInstance(ElementBeanInfoImpl.this); 231 232 fireBeforeUnmarshal(ElementBeanInfoImpl.this, child, state); 233 234 context.recordOuterPeer(child); 235 UnmarshallingContext.State p = state.prev; 236 p.backup = p.target; 237 p.target = child; 238 239 core.startElement(state,ea); 240 } 241 242 public Object intercept(UnmarshallingContext.State state, Object o) throws SAXException { 243 JAXBElement e = (JAXBElement)state.target; 244 state.target = state.backup; 245 state.backup = null; 246 247 if (state.nil) { 248 e.setNil(true); 249 state.nil = false; 250 } 251 252 if(o!=null) 253 // if the value is a leaf type, it's often already set to the element 254 // through Accessor. 255 e.setValue(o); 256 257 fireAfterUnmarshal(ElementBeanInfoImpl.this, e, state); 258 259 return e; 260 } 261 } 262 263 public String getElementNamespaceURI(JAXBElement e) { 264 return e.getName().getNamespaceURI(); 265 } 266 267 public String getElementLocalName(JAXBElement e) { 268 return e.getName().getLocalPart(); 269 } 270 271 public Loader getLoader(JAXBContextImpl context, boolean typeSubstitutionCapable) { 272 if(loader==null) { 273 // this has to be done lazily to avoid cyclic reference issue 274 UnmarshallerChain c = new UnmarshallerChain(context); 275 QNameMap<ChildLoader> result = new QNameMap<ChildLoader>(); 276 property.buildChildElementUnmarshallers(c,result); 277 if(result.size()==1) 278 // for ElementBeanInfoImpl created from RuntimeElementInfo 279 this.loader = new IntercepterLoader(result.getOne().getValue().loader); 280 else 281 // for special ElementBeanInfoImpl only used for marshalling 282 this.loader = Discarder.INSTANCE; 283 } 284 return loader; 285 } 286 287 public final JAXBElement createInstance(UnmarshallingContext context) throws IllegalAccessException, InvocationTargetException, InstantiationException { 288 return createInstanceFromValue(null); 289 } 290 291 public final JAXBElement createInstanceFromValue(Object o) throws IllegalAccessException, InvocationTargetException, InstantiationException { 292 if(constructor==null) 293 return new JAXBElement(tagName,expectedType,scope,o); 294 else 295 return constructor.newInstance(o); 296 } 297 298 public boolean reset(JAXBElement e, UnmarshallingContext context) { 299 e.setValue(null); 300 return true; 301 } 302 303 public String getId(JAXBElement e, XMLSerializer target) { 304 // TODO: is this OK? Should we be returning the ID value of the type property? 305 /* 306 There's one case where we JAXBElement needs to be designated as ID, 307 and that is when there's a global element whose type is ID. 308 */ 309 Object o = e.getValue(); 310 if(o instanceof String) 311 return (String)o; 312 else 313 return null; 314 } 315 316 public void serializeBody(JAXBElement element, XMLSerializer target) throws SAXException, IOException, XMLStreamException { 317 try { 318 property.serializeBody(element,target,null); 319 } catch (AccessorException x) { 320 target.reportError(null,x); 321 } 322 } 323 324 public void serializeRoot(JAXBElement e, XMLSerializer target) throws SAXException, IOException, XMLStreamException { 325 serializeBody(e,target); 326 } 327 328 public void serializeAttributes(JAXBElement e, XMLSerializer target) { 329 // noop 330 } 331 332 public void serializeURIs(JAXBElement e, XMLSerializer target) { 333 // noop 334 } 335 336 public final Transducer<JAXBElement> getTransducer() { 337 return null; 338 } 339 340 @Override 341 public void wrapUp() { 342 super.wrapUp(); 343 property.wrapUp(); 344 } 345 346 @Override 347 public void link(JAXBContextImpl grammar) { 348 super.link(grammar); 349 getLoader(grammar,true); // make sure to build them, if we hadn't done so 350 } 351 }