1 /* 2 * Copyright (c) 1997, 2017, 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.unmarshaller; 27 28 import java.util.Collection; 29 import java.util.HashMap; 30 import java.util.Map; 31 32 import javax.xml.namespace.QName; 33 34 import com.sun.xml.internal.bind.Util; 35 import com.sun.xml.internal.bind.api.AccessorException; 36 import com.sun.xml.internal.bind.api.JAXBRIContext; 37 import com.sun.xml.internal.bind.v2.WellKnownNamespace; 38 import com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl; 39 import com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl; 40 import com.sun.xml.internal.bind.v2.runtime.JaxBeanInfo; 41 import com.sun.xml.internal.bind.v2.runtime.property.AttributeProperty; 42 import com.sun.xml.internal.bind.v2.runtime.property.Property; 43 import com.sun.xml.internal.bind.v2.runtime.property.StructureLoaderBuilder; 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.reflect.TransducedAccessor; 47 import com.sun.xml.internal.bind.v2.util.QNameMap; 48 49 import java.util.Iterator; 50 import org.xml.sax.Attributes; 51 import org.xml.sax.SAXException; 52 53 /** 54 * Loads children of an element. 55 * 56 * <p> 57 * This loader works with a single {@link JaxBeanInfo} and handles 58 * attributes, child elements, or child text. 59 * 60 * @author Kohsuke Kawaguchi 61 */ 62 public final class StructureLoader extends Loader { 63 /** 64 * This map statically stores information of the 65 * unmarshaller loader and can be used while unmarshalling 66 * Since creating new QNames is expensive use this optimized 67 * version of the map 68 */ 69 private final QNameMap<ChildLoader> childUnmarshallers = new QNameMap<ChildLoader>(); 70 71 /** 72 * Loader that processes elements that didn't match anf of the {@link #childUnmarshallers}. 73 * Can be null. 74 */ 75 private /*final*/ ChildLoader catchAll; 76 77 /** 78 * If we have a loader for processing text. Otherwise null. 79 */ 80 private /*final*/ ChildLoader textHandler; 81 82 /** 83 * Unmarshallers for attribute values. 84 * May be null if no attribute is expected and {@link #attCatchAll}==null. 85 */ 86 private /*final*/ QNameMap<TransducedAccessor> attUnmarshallers; 87 88 /** 89 * This will receive all the attributes 90 * that were not processed. Never be null. 91 */ 92 private /*final*/ Accessor<Object,Map<QName,String>> attCatchAll; 93 94 private final JaxBeanInfo beanInfo; 95 96 /** 97 * The number of scopes this dispatcher needs to keep active. 98 */ 99 private /*final*/ int frameSize; 100 101 // this class is potentially useful for general audience, not just for ClassBeanInfoImpl, 102 // but since right now that is the only user, we make the construction code very specific 103 // to ClassBeanInfoImpl. See rev.1.5 of this file for the original general purpose definition. 104 public StructureLoader(ClassBeanInfoImpl beanInfo) { 105 super(true); 106 this.beanInfo = beanInfo; 107 } 108 109 /** 110 * Completes the initialization. 111 * 112 * <p> 113 * To fix the cyclic reference issue, the main part of the initialization needs to be done 114 * after a {@link StructureLoader} is set to {@link ClassBeanInfoImpl#loader}. 115 */ 116 public void init( JAXBContextImpl context, ClassBeanInfoImpl beanInfo, Accessor<?,Map<QName,String>> attWildcard) { 117 UnmarshallerChain chain = new UnmarshallerChain(context); 118 for (ClassBeanInfoImpl bi = beanInfo; bi != null; bi = bi.superClazz) { 119 for (int i = bi.properties.length - 1; i >= 0; i--) { 120 Property p = bi.properties[i]; 121 122 switch(p.getKind()) { 123 case ATTRIBUTE: 124 if(attUnmarshallers==null) 125 attUnmarshallers = new QNameMap<TransducedAccessor>(); 126 AttributeProperty ap = (AttributeProperty) p; 127 attUnmarshallers.put(ap.attName.toQName(),ap.xacc); 128 break; 129 case ELEMENT: 130 case REFERENCE: 131 case MAP: 132 case VALUE: 133 p.buildChildElementUnmarshallers(chain,childUnmarshallers); 134 break; 135 } 136 } 137 } 138 139 this.frameSize = chain.getScopeSize(); 140 141 textHandler = childUnmarshallers.get(StructureLoaderBuilder.TEXT_HANDLER); 142 catchAll = childUnmarshallers.get(StructureLoaderBuilder.CATCH_ALL); 143 144 if(attWildcard!=null) { 145 attCatchAll = (Accessor<Object,Map<QName,String>>) attWildcard; 146 // we use attUnmarshallers==null as a sign to skip the attribute processing 147 // altogether, so if we have an att wildcard we need to have an empty qname map. 148 if(attUnmarshallers==null) 149 attUnmarshallers = EMPTY; 150 } else { 151 attCatchAll = null; 152 } 153 } 154 155 @Override 156 public void startElement(UnmarshallingContext.State state, TagName ea) throws SAXException { 157 UnmarshallingContext context = state.getContext(); 158 159 // create the object to unmarshal 160 Object child; 161 assert !beanInfo.isImmutable(); 162 163 // let's see if we can reuse the existing peer object 164 child = context.getInnerPeer(); 165 166 if(child != null && beanInfo.jaxbType!=child.getClass()) 167 child = null; // unexpected type. 168 169 if(child != null) 170 beanInfo.reset(child,context); 171 172 if(child == null) 173 child = context.createInstance(beanInfo); 174 175 context.recordInnerPeer(child); 176 177 state.setTarget(child); 178 179 fireBeforeUnmarshal(beanInfo, child, state); 180 181 182 context.startScope(frameSize); 183 184 if(attUnmarshallers!=null) { 185 Attributes atts = ea.atts; 186 for (int i = 0; i < atts.getLength(); i ++){ 187 String auri = atts.getURI(i); 188 // may be empty string based on parser settings 189 String alocal = atts.getLocalName(i); 190 if ("".equals(alocal)) { 191 alocal = atts.getQName(i); 192 } 193 String avalue = atts.getValue(i); 194 TransducedAccessor xacc = attUnmarshallers.get(auri, alocal); 195 try { 196 if(xacc!=null) { 197 xacc.parse(child,avalue); 198 } else if (attCatchAll!=null) { 199 String qname = atts.getQName(i); 200 if(atts.getURI(i).equals(WellKnownNamespace.XML_SCHEMA_INSTANCE)) 201 continue; // xsi:* attributes are meant to be processed by us, not by user apps. 202 Object o = state.getTarget(); 203 Map<QName,String> map = attCatchAll.get(o); 204 if(map==null) { 205 // TODO: use ClassFactory.inferImplClass(sig,knownImplClasses) 206 207 // if null, create a new map. 208 if(attCatchAll.valueType.isAssignableFrom(HashMap.class)) 209 map = new HashMap<QName,String>(); 210 else { 211 // we don't know how to create a map for this. 212 // report an error and back out 213 context.handleError(Messages.UNABLE_TO_CREATE_MAP.format(attCatchAll.valueType)); 214 return; 215 } 216 attCatchAll.set(o,map); 217 } 218 219 String prefix; 220 int idx = qname.indexOf(':'); 221 if(idx<0) prefix=""; 222 else prefix=qname.substring(0,idx); 223 224 map.put(new QName(auri,alocal,prefix),avalue); 225 } 226 } catch (AccessorException e) { 227 handleGenericException(e,true); 228 } 229 } 230 } 231 } 232 233 @Override 234 public void childElement(UnmarshallingContext.State state, TagName arg) throws SAXException { 235 ChildLoader child = childUnmarshallers.get(arg.uri,arg.local); 236 if(child == null) { 237 Boolean backupWithParentNamespace = ((JAXBContextImpl) state.getContext().getJAXBContext()).backupWithParentNamespace; 238 backupWithParentNamespace = backupWithParentNamespace != null 239 ? backupWithParentNamespace 240 : Boolean.parseBoolean(Util.getSystemProperty(JAXBRIContext.BACKUP_WITH_PARENT_NAMESPACE)); 241 if ((beanInfo != null) && (beanInfo.getTypeNames() != null) && backupWithParentNamespace) { 242 Iterator<?> typeNamesIt = beanInfo.getTypeNames().iterator(); 243 QName parentQName = null; 244 if ((typeNamesIt != null) && (typeNamesIt.hasNext()) && (catchAll == null)) { 245 parentQName = (QName) typeNamesIt.next(); 246 String parentUri = parentQName.getNamespaceURI(); 247 child = childUnmarshallers.get(parentUri, arg.local); 248 } 249 } 250 if (child == null) { 251 child = catchAll; 252 if(child==null) { 253 super.childElement(state,arg); 254 return; 255 } 256 } 257 } 258 259 state.setLoader(child.loader); 260 state.setReceiver(child.receiver); 261 } 262 263 @Override 264 public Collection<QName> getExpectedChildElements() { 265 return childUnmarshallers.keySet(); 266 } 267 268 @Override 269 public Collection<QName> getExpectedAttributes() { 270 return attUnmarshallers.keySet(); 271 } 272 273 @Override 274 public void text(UnmarshallingContext.State state, CharSequence text) throws SAXException { 275 if(textHandler!=null) 276 textHandler.loader.text(state,text); 277 } 278 279 @Override 280 public void leaveElement(UnmarshallingContext.State state, TagName ea) throws SAXException { 281 state.getContext().endScope(frameSize); 282 fireAfterUnmarshal(beanInfo, state.getTarget(), state.getPrev()); 283 } 284 285 private static final QNameMap<TransducedAccessor> EMPTY = new QNameMap<TransducedAccessor>(); 286 287 public JaxBeanInfo getBeanInfo() { 288 return beanInfo; 289 } 290 }