1 /* 2 * Copyright (c) 1997, 2014, 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.runtime.RuntimeElementInfo; 40 import com.sun.xml.internal.bind.v2.model.runtime.RuntimePropertyInfo; 41 import com.sun.xml.internal.bind.v2.runtime.property.Property; 42 import com.sun.xml.internal.bind.v2.runtime.property.PropertyFactory; 43 import com.sun.xml.internal.bind.v2.runtime.property.UnmarshallerChain; 44 import com.sun.xml.internal.bind.v2.runtime.reflect.Accessor; 45 import com.sun.xml.internal.bind.v2.runtime.unmarshaller.ChildLoader; 46 import com.sun.xml.internal.bind.v2.runtime.unmarshaller.Discarder; 47 import com.sun.xml.internal.bind.v2.runtime.unmarshaller.Intercepter; 48 import com.sun.xml.internal.bind.v2.runtime.unmarshaller.Loader; 49 import com.sun.xml.internal.bind.v2.runtime.unmarshaller.TagName; 50 import com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallingContext; 51 import com.sun.xml.internal.bind.v2.util.QNameMap; 52 53 import org.xml.sax.SAXException; 54 55 /** 56 * {@link JaxBeanInfo} implementation for {@link RuntimeElementInfo}. 57 * 58 * @author Kohsuke Kawaguchi 59 */ 60 public final class ElementBeanInfoImpl extends JaxBeanInfo<JAXBElement> { 61 62 private Loader loader; 63 64 private final Property property; 65 66 // used to create new instances of JAXBElement. 67 private final QName tagName; 68 public final Class expectedType; 69 private final Class scope; 70 71 /** 72 * If non-null, use this to create an instance. 73 * It takes one value. 74 */ 75 private final Constructor<? extends JAXBElement> constructor; 76 77 ElementBeanInfoImpl(JAXBContextImpl grammar, RuntimeElementInfo rei) { 78 super(grammar,rei,(Class<JAXBElement>)rei.getType(),true,false,true); 79 80 this.property = PropertyFactory.create(grammar,rei.getProperty()); 81 82 tagName = rei.getElementName(); 83 expectedType = (Class) Utils.REFLECTION_NAVIGATOR.erasure(rei.getContentInMemoryType()); 84 scope = rei.getScope()==null ? JAXBElement.GlobalScope.class : rei.getScope().getClazz(); 85 86 Class type = (Class) Utils.REFLECTION_NAVIGATOR.erasure(rei.getType()); 87 if(type==JAXBElement.class) 88 constructor = null; 89 else { 90 try { 91 constructor = type.getConstructor(expectedType); 92 } catch (NoSuchMethodException e) { 93 NoSuchMethodError x = new NoSuchMethodError("Failed to find the constructor for " + type + " with " + expectedType); 94 x.initCause(e); 95 throw x; 96 } 97 } 98 } 99 100 /** 101 * The constructor for the sole instanceof {@link JaxBeanInfo} for 102 * handling user-created {@link JAXBElement}. 103 * 104 * Such {@link JaxBeanInfo} is used only for marshalling. 105 * 106 * This is a hack. 107 */ 108 protected ElementBeanInfoImpl(final JAXBContextImpl grammar) { 109 super(grammar,null,JAXBElement.class,true,false,true); 110 tagName = null; 111 expectedType = null; 112 scope = null; 113 constructor = null; 114 115 this.property = new Property<JAXBElement>() { 116 public void reset(JAXBElement o) { 117 throw new UnsupportedOperationException(); 118 } 119 120 public void serializeBody(JAXBElement e, XMLSerializer target, Object outerPeer) throws SAXException, IOException, XMLStreamException { 121 Class scope = e.getScope(); 122 if(e.isGlobalScope()) scope = null; 123 QName n = e.getName(); 124 ElementBeanInfoImpl bi = grammar.getElement(scope,n); 125 if(bi==null) { 126 // infer what to do from the type 127 JaxBeanInfo tbi; 128 try { 129 tbi = grammar.getBeanInfo(e.getDeclaredType(),true); 130 } catch (JAXBException x) { 131 // if e.getDeclaredType() isn't known to this JAXBContext 132 target.reportError(null,x); 133 return; 134 } 135 Object value = e.getValue(); 136 target.startElement(n.getNamespaceURI(),n.getLocalPart(),n.getPrefix(),null); 137 if(value==null) { 138 target.writeXsiNilTrue(); 139 } else { 140 target.childAsXsiType(value,"value",tbi, false); 141 } 142 target.endElement(); 143 } else { 144 try { 145 bi.property.serializeBody(e,target,e); 146 } catch (AccessorException x) { 147 target.reportError(null,x); 148 } 149 } 150 } 151 152 public void serializeURIs(JAXBElement o, XMLSerializer target) { 153 } 154 155 public boolean hasSerializeURIAction() { 156 return false; 157 } 158 159 public String getIdValue(JAXBElement o) { 160 return null; 161 } 162 163 public PropertyKind getKind() { 164 return PropertyKind.ELEMENT; 165 } 166 167 public void buildChildElementUnmarshallers(UnmarshallerChain chain, QNameMap<ChildLoader> handlers) { 168 } 169 170 public Accessor getElementPropertyAccessor(String nsUri, String localName) { 171 throw new UnsupportedOperationException(); 172 } 173 174 public void wrapUp() { 175 } 176 177 public RuntimePropertyInfo getInfo() { 178 return property.getInfo(); 179 } 180 181 public boolean isHiddenByOverride() { 182 return false; 183 } 184 185 public void setHiddenByOverride(boolean hidden) { 186 throw new UnsupportedOperationException("Not supported on jaxbelements."); 187 } 188 189 public String getFieldName() { 190 return null; 191 } 192 193 }; 194 } 195 196 /** 197 * Use the previous {@link UnmarshallingContext.State}'s target to store 198 * {@link JAXBElement} object to be unmarshalled. This allows the property {@link Loader} 199 * to correctly find the parent object. 200 * This is a hack. 201 */ 202 private final class IntercepterLoader extends Loader implements Intercepter { 203 private final Loader core; 204 205 public IntercepterLoader(Loader core) { 206 this.core = core; 207 } 208 209 @Override 210 public final void startElement(UnmarshallingContext.State state, TagName ea) throws SAXException { 211 state.setLoader(core); 212 state.setIntercepter(this); 213 214 // TODO: make sure there aren't too many duplicate of this code 215 // create the object to unmarshal 216 Object child; 217 UnmarshallingContext context = state.getContext(); 218 219 // let's see if we can reuse the existing peer object 220 child = context.getOuterPeer(); 221 222 if(child!=null && jaxbType!=child.getClass()) 223 child = null; // unexpected type. 224 225 if(child!=null) 226 reset((JAXBElement)child,context); 227 228 if(child==null) 229 child = context.createInstance(ElementBeanInfoImpl.this); 230 231 fireBeforeUnmarshal(ElementBeanInfoImpl.this, child, state); 232 233 context.recordOuterPeer(child); 234 UnmarshallingContext.State p = state.getPrev(); 235 p.setBackup(p.getTarget()); 236 p.setTarget(child); 237 238 core.startElement(state,ea); 239 } 240 241 public Object intercept(UnmarshallingContext.State state, Object o) throws SAXException { 242 JAXBElement e = (JAXBElement)state.getTarget(); 243 state.setTarget(state.getBackup()); 244 state.setBackup(null); 245 246 if (state.isNil()) { 247 e.setNil(true); 248 state.setNil(false); 249 } 250 251 if(o!=null) 252 // if the value is a leaf type, it's often already set to the element 253 // through Accessor. 254 e.setValue(o); 255 256 fireAfterUnmarshal(ElementBeanInfoImpl.this, e, state); 257 258 return e; 259 } 260 } 261 262 public String getElementNamespaceURI(JAXBElement e) { 263 return e.getName().getNamespaceURI(); 264 } 265 266 public String getElementLocalName(JAXBElement e) { 267 return e.getName().getLocalPart(); 268 } 269 270 public Loader getLoader(JAXBContextImpl context, boolean typeSubstitutionCapable) { 271 if(loader==null) { 272 // this has to be done lazily to avoid cyclic reference issue 273 UnmarshallerChain c = new UnmarshallerChain(context); 274 QNameMap<ChildLoader> result = new QNameMap<ChildLoader>(); 275 property.buildChildElementUnmarshallers(c,result); 276 if(result.size()==1) 277 // for ElementBeanInfoImpl created from RuntimeElementInfo 278 this.loader = new IntercepterLoader(result.getOne().getValue().loader); 279 else 280 // for special ElementBeanInfoImpl only used for marshalling 281 this.loader = Discarder.INSTANCE; 282 } 283 return loader; 284 } 285 286 public final JAXBElement createInstance(UnmarshallingContext context) throws IllegalAccessException, InvocationTargetException, InstantiationException { 287 return createInstanceFromValue(null); 288 } 289 290 public final JAXBElement createInstanceFromValue(Object o) throws IllegalAccessException, InvocationTargetException, InstantiationException { 291 if(constructor==null) 292 return new JAXBElement(tagName,expectedType,scope,o); 293 else 294 return constructor.newInstance(o); 295 } 296 297 public boolean reset(JAXBElement e, UnmarshallingContext context) { 298 e.setValue(null); 299 return true; 300 } 301 302 public String getId(JAXBElement e, XMLSerializer target) { 303 // TODO: is this OK? Should we be returning the ID value of the type property? 304 /* 305 There's one case where we JAXBElement needs to be designated as ID, 306 and that is when there's a global element whose type is ID. 307 */ 308 Object o = e.getValue(); 309 if(o instanceof String) 310 return (String)o; 311 else 312 return null; 313 } 314 315 public void serializeBody(JAXBElement element, XMLSerializer target) throws SAXException, IOException, XMLStreamException { 316 try { 317 property.serializeBody(element,target,null); 318 } catch (AccessorException x) { 319 target.reportError(null,x); 320 } 321 } 322 323 public void serializeRoot(JAXBElement e, XMLSerializer target) throws SAXException, IOException, XMLStreamException { 324 serializeBody(e,target); 325 } 326 327 public void serializeAttributes(JAXBElement e, XMLSerializer target) { 328 // noop 329 } 330 331 public void serializeURIs(JAXBElement e, XMLSerializer target) { 332 // noop 333 } 334 335 public final Transducer<JAXBElement> getTransducer() { 336 return null; 337 } 338 339 @Override 340 public void wrapUp() { 341 super.wrapUp(); 342 property.wrapUp(); 343 } 344 345 @Override 346 public void link(JAXBContextImpl grammar) { 347 super.link(grammar); 348 getLoader(grammar,true); // make sure to build them, if we hadn't done so 349 } 350 }