Fri, 14 Feb 2014 11:13:45 +0100
8026188: Enhance envelope factory
Summary: Avoiding caching data initialized via TCCL in static context; fix also reviewed by Alexander Fomin
Reviewed-by: ahgross, mgrebac, skoivu
ohair@286 | 1 | /* |
mkos@515 | 2 | * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved. |
ohair@286 | 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
ohair@286 | 4 | * |
ohair@286 | 5 | * This code is free software; you can redistribute it and/or modify it |
ohair@286 | 6 | * under the terms of the GNU General Public License version 2 only, as |
ohair@286 | 7 | * published by the Free Software Foundation. Oracle designates this |
ohair@286 | 8 | * particular file as subject to the "Classpath" exception as provided |
ohair@286 | 9 | * by Oracle in the LICENSE file that accompanied this code. |
ohair@286 | 10 | * |
ohair@286 | 11 | * This code is distributed in the hope that it will be useful, but WITHOUT |
ohair@286 | 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
ohair@286 | 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
ohair@286 | 14 | * version 2 for more details (a copy is included in the LICENSE file that |
ohair@286 | 15 | * accompanied this code). |
ohair@286 | 16 | * |
ohair@286 | 17 | * You should have received a copy of the GNU General Public License version |
ohair@286 | 18 | * 2 along with this work; if not, write to the Free Software Foundation, |
ohair@286 | 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
ohair@286 | 20 | * |
ohair@286 | 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
ohair@286 | 22 | * or visit www.oracle.com if you need additional information or have any |
ohair@286 | 23 | * questions. |
ohair@286 | 24 | */ |
ohair@286 | 25 | |
ohair@286 | 26 | package com.sun.tools.internal.xjc.reader.internalizer; |
ohair@286 | 27 | |
ohair@286 | 28 | import java.net.MalformedURLException; |
ohair@286 | 29 | import java.net.URL; |
ohair@286 | 30 | import java.util.ArrayList; |
ohair@286 | 31 | import java.util.HashMap; |
ohair@286 | 32 | import java.util.HashSet; |
ohair@286 | 33 | import java.util.List; |
ohair@286 | 34 | import java.util.Map; |
ohair@286 | 35 | import java.util.Set; |
ohair@286 | 36 | import java.text.ParseException; |
ohair@286 | 37 | |
ohair@286 | 38 | import javax.xml.xpath.XPath; |
ohair@286 | 39 | import javax.xml.xpath.XPathConstants; |
ohair@286 | 40 | import javax.xml.xpath.XPathExpressionException; |
ohair@286 | 41 | import javax.xml.xpath.XPathFactory; |
ohair@286 | 42 | |
ohair@286 | 43 | import com.sun.istack.internal.SAXParseException2; |
ohair@286 | 44 | import com.sun.istack.internal.NotNull; |
ohair@286 | 45 | import com.sun.istack.internal.Nullable; |
ohair@286 | 46 | import com.sun.tools.internal.xjc.ErrorReceiver; |
ohair@286 | 47 | import com.sun.tools.internal.xjc.reader.Const; |
ohair@286 | 48 | import com.sun.tools.internal.xjc.util.DOMUtils; |
ohair@286 | 49 | import com.sun.xml.internal.bind.v2.util.EditDistance; |
alanb@368 | 50 | import com.sun.xml.internal.bind.v2.util.XmlFactory; |
ohair@286 | 51 | import com.sun.xml.internal.xsom.SCD; |
ohair@286 | 52 | import java.io.File; |
ohair@286 | 53 | import java.io.IOException; |
ohair@286 | 54 | import java.util.logging.Level; |
ohair@286 | 55 | import java.util.logging.Logger; |
ohair@286 | 56 | import javax.xml.XMLConstants; |
ohair@286 | 57 | |
ohair@286 | 58 | import org.w3c.dom.Attr; |
ohair@286 | 59 | import org.w3c.dom.Document; |
ohair@286 | 60 | import org.w3c.dom.Element; |
ohair@286 | 61 | import org.w3c.dom.NamedNodeMap; |
ohair@286 | 62 | import org.w3c.dom.Node; |
ohair@286 | 63 | import org.w3c.dom.NodeList; |
ohair@286 | 64 | import org.xml.sax.SAXParseException; |
ohair@286 | 65 | |
ohair@286 | 66 | /** |
ohair@286 | 67 | * Internalizes external binding declarations. |
ohair@286 | 68 | * |
ohair@286 | 69 | * <p> |
ohair@286 | 70 | * The {@link #transform(DOMForest,boolean)} method is the entry point. |
ohair@286 | 71 | * |
ohair@286 | 72 | * @author |
ohair@286 | 73 | * Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com) |
ohair@286 | 74 | */ |
ohair@286 | 75 | class Internalizer { |
ohair@286 | 76 | |
ohair@286 | 77 | private static final String WSDL_NS = "http://schemas.xmlsoap.org/wsdl/"; |
ohair@286 | 78 | |
alanb@368 | 79 | private final XPath xpath; |
ohair@286 | 80 | |
ohair@286 | 81 | /** |
ohair@286 | 82 | * Internalize all <jaxb:bindings> customizations in the given forest. |
ohair@286 | 83 | * |
ohair@286 | 84 | * @return |
ohair@286 | 85 | * if the SCD support is enabled, the return bindings need to be applied |
ohair@286 | 86 | * after schema components are parsed. |
ohair@286 | 87 | * If disabled, the returned binding set will be empty. |
ohair@286 | 88 | * SCDs are only for XML Schema, and doesn't make any sense for other |
ohair@286 | 89 | * schema languages. |
ohair@286 | 90 | */ |
alanb@368 | 91 | static SCDBasedBindingSet transform( DOMForest forest, boolean enableSCD, boolean disableSecureProcessing ) { |
alanb@368 | 92 | return new Internalizer(forest, enableSCD, disableSecureProcessing).transform(); |
ohair@286 | 93 | } |
ohair@286 | 94 | |
ohair@286 | 95 | |
alanb@368 | 96 | private Internalizer(DOMForest forest, boolean enableSCD, boolean disableSecureProcessing) { |
ohair@286 | 97 | this.errorHandler = forest.getErrorHandler(); |
ohair@286 | 98 | this.forest = forest; |
ohair@286 | 99 | this.enableSCD = enableSCD; |
mkos@515 | 100 | xpath = XmlFactory.createXPathFactory(disableSecureProcessing).newXPath(); |
ohair@286 | 101 | } |
ohair@286 | 102 | |
ohair@286 | 103 | /** |
ohair@286 | 104 | * DOMForest object currently being processed. |
ohair@286 | 105 | */ |
ohair@286 | 106 | private final DOMForest forest; |
ohair@286 | 107 | |
ohair@286 | 108 | /** |
ohair@286 | 109 | * All errors found during the transformation is sent to this object. |
ohair@286 | 110 | */ |
ohair@286 | 111 | private ErrorReceiver errorHandler; |
ohair@286 | 112 | |
ohair@286 | 113 | /** |
ohair@286 | 114 | * If true, the SCD-based target selection is supported. |
ohair@286 | 115 | */ |
ohair@286 | 116 | private boolean enableSCD; |
ohair@286 | 117 | |
ohair@286 | 118 | |
ohair@286 | 119 | private SCDBasedBindingSet transform() { |
ohair@286 | 120 | |
ohair@286 | 121 | // either target nodes are conventional DOM nodes (as per spec), |
ohair@286 | 122 | Map<Element,List<Node>> targetNodes = new HashMap<Element,List<Node>>(); |
ohair@286 | 123 | // ... or it will be schema components by means of SCD (RI extension) |
ohair@286 | 124 | SCDBasedBindingSet scd = new SCDBasedBindingSet(forest); |
ohair@286 | 125 | |
ohair@286 | 126 | // |
ohair@286 | 127 | // identify target nodes for all <jaxb:bindings> |
ohair@286 | 128 | // |
ohair@286 | 129 | for (Element jaxbBindings : forest.outerMostBindings) { |
ohair@286 | 130 | // initially, the inherited context is itself |
ohair@286 | 131 | buildTargetNodeMap(jaxbBindings, jaxbBindings, null, targetNodes, scd); |
ohair@286 | 132 | } |
ohair@286 | 133 | |
ohair@286 | 134 | // |
ohair@286 | 135 | // then move them to their respective positions. |
ohair@286 | 136 | // |
ohair@286 | 137 | for (Element jaxbBindings : forest.outerMostBindings) { |
ohair@286 | 138 | move(jaxbBindings, targetNodes); |
ohair@286 | 139 | } |
ohair@286 | 140 | |
ohair@286 | 141 | return scd; |
ohair@286 | 142 | } |
ohair@286 | 143 | |
ohair@286 | 144 | /** |
ohair@286 | 145 | * Validates attributes of a <jaxb:bindings> element. |
ohair@286 | 146 | */ |
ohair@286 | 147 | private void validate( Element bindings ) { |
ohair@286 | 148 | NamedNodeMap atts = bindings.getAttributes(); |
ohair@286 | 149 | for( int i=0; i<atts.getLength(); i++ ) { |
ohair@286 | 150 | Attr a = (Attr)atts.item(i); |
ohair@286 | 151 | if( a.getNamespaceURI()!=null ) |
ohair@286 | 152 | continue; // all foreign namespace OK. |
ohair@286 | 153 | if( a.getLocalName().equals("node") ) |
ohair@286 | 154 | continue; |
ohair@286 | 155 | if( a.getLocalName().equals("schemaLocation")) |
ohair@286 | 156 | continue; |
ohair@286 | 157 | if( a.getLocalName().equals("scd") ) |
ohair@286 | 158 | continue; |
ohair@286 | 159 | |
ohair@286 | 160 | // enhancements |
ohair@286 | 161 | if( a.getLocalName().equals("required") ) // |
ohair@286 | 162 | continue; |
ohair@286 | 163 | if( a.getLocalName().equals("multiple") ) // |
ohair@286 | 164 | continue; |
ohair@286 | 165 | |
ohair@286 | 166 | |
ohair@286 | 167 | // TODO: flag error for this undefined attribute |
ohair@286 | 168 | } |
ohair@286 | 169 | } |
ohair@286 | 170 | |
ohair@286 | 171 | /** |
ohair@286 | 172 | * Determines the target node of the "bindings" element |
ohair@286 | 173 | * by using the inherited target node, then put |
ohair@286 | 174 | * the result into the "result" map and the "scd" map. |
ohair@286 | 175 | * |
ohair@286 | 176 | * @param inheritedTarget |
ohair@286 | 177 | * The current target node. This always exists, even if |
ohair@286 | 178 | * the user starts specifying targets via SCD (in that case |
ohair@286 | 179 | * this inherited target is just not going to be used.) |
ohair@286 | 180 | * @param inheritedSCD |
ohair@286 | 181 | * If the ancestor <bindings> node specifies @scd to |
ohair@286 | 182 | * specify the target via SCD, then this parameter represents that context. |
ohair@286 | 183 | */ |
ohair@286 | 184 | private void buildTargetNodeMap( Element bindings, @NotNull Node inheritedTarget, |
ohair@286 | 185 | @Nullable SCDBasedBindingSet.Target inheritedSCD, |
ohair@286 | 186 | Map<Element,List<Node>> result, SCDBasedBindingSet scdResult ) { |
ohair@286 | 187 | // start by the inherited target |
ohair@286 | 188 | Node target = inheritedTarget; |
ohair@286 | 189 | ArrayList<Node> targetMultiple = null; |
ohair@286 | 190 | |
ohair@286 | 191 | validate(bindings); // validate this node |
ohair@286 | 192 | |
ohair@286 | 193 | boolean required = true; |
ohair@286 | 194 | boolean multiple = false; |
ohair@286 | 195 | |
ohair@286 | 196 | if(bindings.getAttribute("required") != null) { |
ohair@286 | 197 | String requiredAttr = bindings.getAttribute("required"); |
ohair@286 | 198 | |
ohair@286 | 199 | if(requiredAttr.equals("no") || requiredAttr.equals("false") || requiredAttr.equals("0")) |
ohair@286 | 200 | required = false; |
ohair@286 | 201 | } |
ohair@286 | 202 | |
ohair@286 | 203 | if(bindings.getAttribute("multiple") != null) { |
ohair@286 | 204 | String requiredAttr = bindings.getAttribute("multiple"); |
ohair@286 | 205 | |
ohair@286 | 206 | if(requiredAttr.equals("yes") || requiredAttr.equals("true") || requiredAttr.equals("1")) |
ohair@286 | 207 | multiple = true; |
ohair@286 | 208 | } |
ohair@286 | 209 | |
ohair@286 | 210 | |
ohair@286 | 211 | // look for @schemaLocation |
ohair@286 | 212 | if( bindings.getAttributeNode("schemaLocation")!=null ) { |
ohair@286 | 213 | String schemaLocation = bindings.getAttribute("schemaLocation"); |
ohair@286 | 214 | |
ohair@286 | 215 | // enhancement - schemaLocation="*" = bind to all schemas.. |
ohair@286 | 216 | if(schemaLocation.equals("*")) { |
ohair@286 | 217 | for(String systemId : forest.listSystemIDs()) { |
ohair@286 | 218 | if (result.get(bindings) == null) |
ohair@286 | 219 | result.put(bindings, new ArrayList<Node>()); |
ohair@286 | 220 | result.get(bindings).add(forest.get(systemId).getDocumentElement()); |
ohair@286 | 221 | |
ohair@286 | 222 | Element[] children = DOMUtils.getChildElements(bindings, Const.JAXB_NSURI, "bindings"); |
ohair@286 | 223 | for (Element value : children) |
ohair@286 | 224 | buildTargetNodeMap(value, forest.get(systemId).getDocumentElement(), inheritedSCD, result, scdResult); |
ohair@286 | 225 | } |
ohair@286 | 226 | return; |
ohair@286 | 227 | } else { |
ohair@286 | 228 | try { |
ohair@286 | 229 | // TODO: use the URI class |
ohair@286 | 230 | // TODO: honor xml:base |
ohair@286 | 231 | URL loc = new URL( |
ohair@286 | 232 | new URL(forest.getSystemId(bindings.getOwnerDocument())), schemaLocation |
ohair@286 | 233 | ); |
ohair@286 | 234 | schemaLocation = loc.toExternalForm(); |
ohair@286 | 235 | target = forest.get(schemaLocation); |
ohair@286 | 236 | if ((target == null) && (loc.getProtocol().startsWith("file"))) { |
ohair@286 | 237 | File f = new File(loc.getFile()); |
ohair@286 | 238 | schemaLocation = new File(f.getCanonicalPath()).toURI().toString(); |
ohair@286 | 239 | } |
ohair@286 | 240 | } catch( MalformedURLException e ) { |
ohair@286 | 241 | } catch( IOException e ) { |
ohair@286 | 242 | Logger.getLogger(Internalizer.class.getName()).log(Level.FINEST, e.getLocalizedMessage()); |
ohair@286 | 243 | } |
ohair@286 | 244 | |
ohair@286 | 245 | target = forest.get(schemaLocation); |
ohair@286 | 246 | if(target==null) { |
ohair@286 | 247 | reportError( bindings, |
ohair@286 | 248 | Messages.format(Messages.ERR_INCORRECT_SCHEMA_REFERENCE, |
ohair@286 | 249 | schemaLocation, |
ohair@286 | 250 | EditDistance.findNearest(schemaLocation,forest.listSystemIDs()))); |
ohair@286 | 251 | |
ohair@286 | 252 | return; // abort processing this <jaxb:bindings> |
ohair@286 | 253 | } |
ohair@286 | 254 | |
ohair@286 | 255 | target = ((Document)target).getDocumentElement(); |
ohair@286 | 256 | } |
ohair@286 | 257 | } |
ohair@286 | 258 | |
ohair@286 | 259 | // look for @node |
ohair@286 | 260 | if( bindings.getAttributeNode("node")!=null ) { |
ohair@286 | 261 | String nodeXPath = bindings.getAttribute("node"); |
ohair@286 | 262 | |
ohair@286 | 263 | // evaluate this XPath |
ohair@286 | 264 | NodeList nlst; |
ohair@286 | 265 | try { |
ohair@286 | 266 | xpath.setNamespaceContext(new NamespaceContextImpl(bindings)); |
ohair@286 | 267 | nlst = (NodeList)xpath.evaluate(nodeXPath,target,XPathConstants.NODESET); |
ohair@286 | 268 | } catch (XPathExpressionException e) { |
ohair@286 | 269 | if(required) { |
ohair@286 | 270 | reportError( bindings, |
ohair@286 | 271 | Messages.format(Messages.ERR_XPATH_EVAL,e.getMessage()), e ); |
ohair@286 | 272 | return; // abort processing this <jaxb:bindings> |
ohair@286 | 273 | } else { |
ohair@286 | 274 | return; |
ohair@286 | 275 | } |
ohair@286 | 276 | } |
ohair@286 | 277 | |
ohair@286 | 278 | if( nlst.getLength()==0 ) { |
ohair@286 | 279 | if(required) |
ohair@286 | 280 | reportError( bindings, |
ohair@286 | 281 | Messages.format(Messages.NO_XPATH_EVAL_TO_NO_TARGET, nodeXPath) ); |
ohair@286 | 282 | return; // abort |
ohair@286 | 283 | } |
ohair@286 | 284 | |
ohair@286 | 285 | if( nlst.getLength()!=1 ) { |
ohair@286 | 286 | if(!multiple) { |
ohair@286 | 287 | reportError( bindings, |
ohair@286 | 288 | Messages.format(Messages.NO_XPATH_EVAL_TOO_MANY_TARGETS, nodeXPath,nlst.getLength()) ); |
ohair@286 | 289 | |
ohair@286 | 290 | return; // abort |
ohair@286 | 291 | } else { |
ohair@286 | 292 | if(targetMultiple == null) targetMultiple = new ArrayList<Node>(); |
ohair@286 | 293 | for(int i = 0; i < nlst.getLength(); i++) { |
ohair@286 | 294 | targetMultiple.add(nlst.item(i)); |
ohair@286 | 295 | } |
ohair@286 | 296 | } |
ohair@286 | 297 | } |
ohair@286 | 298 | |
ohair@286 | 299 | // check |
ohair@286 | 300 | if(!multiple || nlst.getLength() == 1) { |
ohair@286 | 301 | Node rnode = nlst.item(0); |
ohair@286 | 302 | if (!(rnode instanceof Element)) { |
ohair@286 | 303 | reportError(bindings, |
ohair@286 | 304 | Messages.format(Messages.NO_XPATH_EVAL_TO_NON_ELEMENT, nodeXPath)); |
ohair@286 | 305 | return; // abort |
ohair@286 | 306 | } |
ohair@286 | 307 | |
ohair@286 | 308 | if (!forest.logic.checkIfValidTargetNode(forest, bindings, (Element) rnode)) { |
ohair@286 | 309 | reportError(bindings, |
ohair@286 | 310 | Messages.format(Messages.XPATH_EVAL_TO_NON_SCHEMA_ELEMENT, |
ohair@286 | 311 | nodeXPath, rnode.getNodeName())); |
ohair@286 | 312 | return; // abort |
ohair@286 | 313 | } |
ohair@286 | 314 | |
ohair@286 | 315 | target = rnode; |
ohair@286 | 316 | } else { |
ohair@286 | 317 | for(Node rnode : targetMultiple) { |
ohair@286 | 318 | if (!(rnode instanceof Element)) { |
ohair@286 | 319 | reportError(bindings, |
ohair@286 | 320 | Messages.format(Messages.NO_XPATH_EVAL_TO_NON_ELEMENT, nodeXPath)); |
ohair@286 | 321 | return; // abort |
ohair@286 | 322 | } |
ohair@286 | 323 | |
ohair@286 | 324 | if (!forest.logic.checkIfValidTargetNode(forest, bindings, (Element) rnode)) { |
ohair@286 | 325 | reportError(bindings, |
ohair@286 | 326 | Messages.format(Messages.XPATH_EVAL_TO_NON_SCHEMA_ELEMENT, |
ohair@286 | 327 | nodeXPath, rnode.getNodeName())); |
ohair@286 | 328 | return; // abort |
ohair@286 | 329 | } |
ohair@286 | 330 | } |
ohair@286 | 331 | } |
ohair@286 | 332 | } |
ohair@286 | 333 | |
ohair@286 | 334 | // look for @scd |
ohair@286 | 335 | if( bindings.getAttributeNode("scd")!=null ) { |
ohair@286 | 336 | String scdPath = bindings.getAttribute("scd"); |
ohair@286 | 337 | if(!enableSCD) { |
ohair@286 | 338 | // SCD selector was found, but it's not activated. report an error |
ohair@286 | 339 | // but recover by handling it anyway. this also avoids repeated error messages. |
ohair@286 | 340 | reportError(bindings, |
ohair@286 | 341 | Messages.format(Messages.SCD_NOT_ENABLED)); |
ohair@286 | 342 | enableSCD = true; |
ohair@286 | 343 | } |
ohair@286 | 344 | |
ohair@286 | 345 | try { |
ohair@286 | 346 | inheritedSCD = scdResult.createNewTarget( inheritedSCD, bindings, |
ohair@286 | 347 | SCD.create(scdPath, new NamespaceContextImpl(bindings)) ); |
ohair@286 | 348 | } catch (ParseException e) { |
ohair@286 | 349 | reportError( bindings, Messages.format(Messages.ERR_SCD_EVAL,e.getMessage()),e ); |
ohair@286 | 350 | return; // abort processing this bindings |
ohair@286 | 351 | } |
ohair@286 | 352 | } |
ohair@286 | 353 | |
ohair@286 | 354 | // update the result map |
ohair@286 | 355 | if (inheritedSCD != null) { |
ohair@286 | 356 | inheritedSCD.addBinidng(bindings); |
ohair@286 | 357 | } else if (!multiple || targetMultiple == null) { |
ohair@286 | 358 | if (result.get(bindings) == null) |
ohair@286 | 359 | result.put(bindings, new ArrayList<Node>()); |
ohair@286 | 360 | result.get(bindings).add(target); |
ohair@286 | 361 | } else { |
ohair@286 | 362 | for (Node rnode : targetMultiple) { |
ohair@286 | 363 | if (result.get(bindings) == null) |
ohair@286 | 364 | result.put(bindings, new ArrayList<Node>()); |
ohair@286 | 365 | |
ohair@286 | 366 | result.get(bindings).add(rnode); |
ohair@286 | 367 | } |
ohair@286 | 368 | |
ohair@286 | 369 | } |
ohair@286 | 370 | |
ohair@286 | 371 | |
ohair@286 | 372 | // look for child <jaxb:bindings> and process them recursively |
ohair@286 | 373 | Element[] children = DOMUtils.getChildElements( bindings, Const.JAXB_NSURI, "bindings" ); |
ohair@286 | 374 | for (Element value : children) |
ohair@286 | 375 | if(!multiple || targetMultiple == null) |
ohair@286 | 376 | buildTargetNodeMap(value, target, inheritedSCD, result, scdResult); |
ohair@286 | 377 | else { |
ohair@286 | 378 | for(Node rnode : targetMultiple) { |
ohair@286 | 379 | buildTargetNodeMap(value, rnode, inheritedSCD, result, scdResult); |
ohair@286 | 380 | } |
ohair@286 | 381 | } |
ohair@286 | 382 | } |
ohair@286 | 383 | |
ohair@286 | 384 | /** |
ohair@286 | 385 | * Moves JAXB customizations under their respective target nodes. |
ohair@286 | 386 | */ |
ohair@286 | 387 | private void move(Element bindings, Map<Element, List<Node>> targetNodes) { |
ohair@286 | 388 | List<Node> nodelist = targetNodes.get(bindings); |
ohair@286 | 389 | |
ohair@286 | 390 | if(nodelist == null) { |
ohair@286 | 391 | return; // abort |
ohair@286 | 392 | } |
ohair@286 | 393 | |
ohair@286 | 394 | for (Node target : nodelist) { |
ohair@286 | 395 | if (target == null) // this must be the result of an error on the external binding. |
ohair@286 | 396 | // recover from the error by ignoring this node |
ohair@286 | 397 | { |
ohair@286 | 398 | return; |
ohair@286 | 399 | } |
ohair@286 | 400 | |
ohair@286 | 401 | for (Element item : DOMUtils.getChildElements(bindings)) { |
ohair@286 | 402 | String localName = item.getLocalName(); |
ohair@286 | 403 | |
ohair@286 | 404 | if ("bindings".equals(localName)) { |
ohair@286 | 405 | // process child <jaxb:bindings> recursively |
ohair@286 | 406 | move(item, targetNodes); |
ohair@286 | 407 | } else if ("globalBindings".equals(localName)) { |
ohair@286 | 408 | // <jaxb:globalBindings> always go to the root of document. |
ohair@286 | 409 | Element root = forest.getOneDocument().getDocumentElement(); |
ohair@286 | 410 | if (root.getNamespaceURI().equals(WSDL_NS)) { |
ohair@286 | 411 | NodeList elements = root.getElementsByTagNameNS(XMLConstants.W3C_XML_SCHEMA_NS_URI, "schema"); |
ohair@286 | 412 | if ((elements == null) || (elements.getLength() < 1)) { |
ohair@286 | 413 | reportError(item, Messages.format(Messages.ORPHANED_CUSTOMIZATION, item.getNodeName())); |
ohair@286 | 414 | return; |
ohair@286 | 415 | } else { |
ohair@286 | 416 | moveUnder(item, (Element)elements.item(0)); |
ohair@286 | 417 | } |
ohair@286 | 418 | } else { |
ohair@286 | 419 | moveUnder(item, root); |
ohair@286 | 420 | } |
ohair@286 | 421 | } else { |
ohair@286 | 422 | if (!(target instanceof Element)) { |
ohair@286 | 423 | reportError(item, |
ohair@286 | 424 | Messages.format(Messages.CONTEXT_NODE_IS_NOT_ELEMENT)); |
ohair@286 | 425 | return; // abort |
ohair@286 | 426 | } |
ohair@286 | 427 | |
ohair@286 | 428 | if (!forest.logic.checkIfValidTargetNode(forest, item, (Element) target)) { |
ohair@286 | 429 | reportError(item, |
ohair@286 | 430 | Messages.format(Messages.ORPHANED_CUSTOMIZATION, item.getNodeName())); |
ohair@286 | 431 | return; // abort |
ohair@286 | 432 | } |
ohair@286 | 433 | |
ohair@286 | 434 | // move this node under the target |
ohair@286 | 435 | moveUnder(item, (Element) target); |
ohair@286 | 436 | } |
ohair@286 | 437 | } |
ohair@286 | 438 | } |
ohair@286 | 439 | } |
ohair@286 | 440 | |
ohair@286 | 441 | /** |
ohair@286 | 442 | * Moves the "decl" node under the "target" node. |
ohair@286 | 443 | * |
ohair@286 | 444 | * @param decl |
ohair@286 | 445 | * A JAXB customization element (e.g., <jaxb:class>) |
ohair@286 | 446 | * |
ohair@286 | 447 | * @param target |
ohair@286 | 448 | * XML Schema element under which the declaration should move. |
ohair@286 | 449 | * For example, <xs:element> |
ohair@286 | 450 | */ |
ohair@286 | 451 | private void moveUnder( Element decl, Element target ) { |
ohair@286 | 452 | Element realTarget = forest.logic.refineTarget(target); |
ohair@286 | 453 | |
ohair@286 | 454 | declExtensionNamespace( decl, target ); |
ohair@286 | 455 | |
ohair@286 | 456 | // copy in-scope namespace declarations of the decl node |
ohair@286 | 457 | // to the decl node itself so that this move won't change |
ohair@286 | 458 | // the in-scope namespace bindings. |
ohair@286 | 459 | Element p = decl; |
ohair@286 | 460 | Set<String> inscopes = new HashSet<String>(); |
ohair@286 | 461 | while(true) { |
ohair@286 | 462 | NamedNodeMap atts = p.getAttributes(); |
ohair@286 | 463 | for( int i=0; i<atts.getLength(); i++ ) { |
ohair@286 | 464 | Attr a = (Attr)atts.item(i); |
ohair@286 | 465 | if( Const.XMLNS_URI.equals(a.getNamespaceURI()) ) { |
ohair@286 | 466 | String prefix; |
ohair@286 | 467 | if( a.getName().indexOf(':')==-1 ) prefix = ""; |
ohair@286 | 468 | else prefix = a.getLocalName(); |
ohair@286 | 469 | |
ohair@286 | 470 | if( inscopes.add(prefix) && p!=decl ) { |
ohair@286 | 471 | // if this is the first time we see this namespace bindings, |
ohair@286 | 472 | // copy the declaration. |
ohair@286 | 473 | // if p==decl, there's no need to. Note that |
ohair@286 | 474 | // we want to add prefix to inscopes even if p==Decl |
ohair@286 | 475 | |
ohair@286 | 476 | decl.setAttributeNodeNS( (Attr)a.cloneNode(true) ); |
ohair@286 | 477 | } |
ohair@286 | 478 | } |
ohair@286 | 479 | } |
ohair@286 | 480 | |
ohair@286 | 481 | if( p.getParentNode() instanceof Document ) |
ohair@286 | 482 | break; |
ohair@286 | 483 | |
ohair@286 | 484 | p = (Element)p.getParentNode(); |
ohair@286 | 485 | } |
ohair@286 | 486 | |
ohair@286 | 487 | if( !inscopes.contains("") ) { |
ohair@286 | 488 | // if the default namespace was undeclared in the context of decl, |
ohair@286 | 489 | // it must be explicitly set to "" since the new environment might |
ohair@286 | 490 | // have a different default namespace URI. |
ohair@286 | 491 | decl.setAttributeNS(Const.XMLNS_URI,"xmlns",""); |
ohair@286 | 492 | } |
ohair@286 | 493 | |
ohair@286 | 494 | |
ohair@286 | 495 | // finally move the declaration to the target node. |
ohair@286 | 496 | if( realTarget.getOwnerDocument()!=decl.getOwnerDocument() ) { |
ohair@286 | 497 | // if they belong to different DOM documents, we need to clone them |
ohair@286 | 498 | Element original = decl; |
ohair@286 | 499 | decl = (Element)realTarget.getOwnerDocument().importNode(decl,true); |
ohair@286 | 500 | |
ohair@286 | 501 | // this effectively clones a ndoe,, so we need to copy locators. |
ohair@286 | 502 | copyLocators( original, decl ); |
ohair@286 | 503 | } |
ohair@286 | 504 | |
ohair@286 | 505 | realTarget.appendChild( decl ); |
ohair@286 | 506 | } |
ohair@286 | 507 | |
ohair@286 | 508 | /** |
ohair@286 | 509 | * Recursively visits sub-elements and declare all used namespaces. |
ohair@286 | 510 | * TODO: the fact that we recognize all namespaces in the extension |
ohair@286 | 511 | * is a bad design. |
ohair@286 | 512 | */ |
ohair@286 | 513 | private void declExtensionNamespace(Element decl, Element target) { |
ohair@286 | 514 | // if this comes from external namespaces, add the namespace to |
ohair@286 | 515 | // @extensionBindingPrefixes. |
ohair@286 | 516 | if( !Const.JAXB_NSURI.equals(decl.getNamespaceURI()) ) |
ohair@286 | 517 | declareExtensionNamespace( target, decl.getNamespaceURI() ); |
ohair@286 | 518 | |
ohair@286 | 519 | NodeList lst = decl.getChildNodes(); |
ohair@286 | 520 | for( int i=0; i<lst.getLength(); i++ ) { |
ohair@286 | 521 | Node n = lst.item(i); |
ohair@286 | 522 | if( n instanceof Element ) |
ohair@286 | 523 | declExtensionNamespace( (Element)n, target ); |
ohair@286 | 524 | } |
ohair@286 | 525 | } |
ohair@286 | 526 | |
ohair@286 | 527 | |
ohair@286 | 528 | /** Attribute name. */ |
ohair@286 | 529 | private static final String EXTENSION_PREFIXES = "extensionBindingPrefixes"; |
ohair@286 | 530 | |
ohair@286 | 531 | /** |
ohair@286 | 532 | * Adds the specified namespace URI to the jaxb:extensionBindingPrefixes |
ohair@286 | 533 | * attribute of the target document. |
ohair@286 | 534 | */ |
ohair@286 | 535 | private void declareExtensionNamespace( Element target, String nsUri ) { |
ohair@286 | 536 | // look for the attribute |
ohair@286 | 537 | Element root = target.getOwnerDocument().getDocumentElement(); |
ohair@286 | 538 | Attr att = root.getAttributeNodeNS(Const.JAXB_NSURI,EXTENSION_PREFIXES); |
ohair@286 | 539 | if( att==null ) { |
ohair@286 | 540 | String jaxbPrefix = allocatePrefix(root,Const.JAXB_NSURI); |
ohair@286 | 541 | // no such attribute. Create one. |
ohair@286 | 542 | att = target.getOwnerDocument().createAttributeNS( |
ohair@286 | 543 | Const.JAXB_NSURI,jaxbPrefix+':'+EXTENSION_PREFIXES); |
ohair@286 | 544 | root.setAttributeNodeNS(att); |
ohair@286 | 545 | } |
ohair@286 | 546 | |
ohair@286 | 547 | String prefix = allocatePrefix(root,nsUri); |
ohair@286 | 548 | if( att.getValue().indexOf(prefix)==-1 ) |
ohair@286 | 549 | // avoid redeclaring the same namespace twice. |
ohair@286 | 550 | att.setValue( att.getValue()+' '+prefix); |
ohair@286 | 551 | } |
ohair@286 | 552 | |
ohair@286 | 553 | /** |
ohair@286 | 554 | * Declares a new prefix on the given element and associates it |
ohair@286 | 555 | * with the specified namespace URI. |
ohair@286 | 556 | * <p> |
ohair@286 | 557 | * Note that this method doesn't use the default namespace |
ohair@286 | 558 | * even if it can. |
ohair@286 | 559 | */ |
ohair@286 | 560 | private String allocatePrefix( Element e, String nsUri ) { |
ohair@286 | 561 | // look for existing namespaces. |
ohair@286 | 562 | NamedNodeMap atts = e.getAttributes(); |
ohair@286 | 563 | for( int i=0; i<atts.getLength(); i++ ) { |
ohair@286 | 564 | Attr a = (Attr)atts.item(i); |
ohair@286 | 565 | if( Const.XMLNS_URI.equals(a.getNamespaceURI()) ) { |
ohair@286 | 566 | if( a.getName().indexOf(':')==-1 ) continue; |
ohair@286 | 567 | |
ohair@286 | 568 | if( a.getValue().equals(nsUri) ) |
ohair@286 | 569 | return a.getLocalName(); // found one |
ohair@286 | 570 | } |
ohair@286 | 571 | } |
ohair@286 | 572 | |
ohair@286 | 573 | // none found. allocate new. |
ohair@286 | 574 | while(true) { |
ohair@286 | 575 | String prefix = "p"+(int)(Math.random()*1000000)+'_'; |
ohair@286 | 576 | if(e.getAttributeNodeNS(Const.XMLNS_URI,prefix)!=null) |
ohair@286 | 577 | continue; // this prefix is already allocated. |
ohair@286 | 578 | |
ohair@286 | 579 | e.setAttributeNS(Const.XMLNS_URI,"xmlns:"+prefix,nsUri); |
ohair@286 | 580 | return prefix; |
ohair@286 | 581 | } |
ohair@286 | 582 | } |
ohair@286 | 583 | |
ohair@286 | 584 | |
ohair@286 | 585 | /** |
ohair@286 | 586 | * Copies location information attached to the "src" node to the "dst" node. |
ohair@286 | 587 | */ |
ohair@286 | 588 | private void copyLocators( Element src, Element dst ) { |
ohair@286 | 589 | forest.locatorTable.storeStartLocation( |
ohair@286 | 590 | dst, forest.locatorTable.getStartLocation(src) ); |
ohair@286 | 591 | forest.locatorTable.storeEndLocation( |
ohair@286 | 592 | dst, forest.locatorTable.getEndLocation(src) ); |
ohair@286 | 593 | |
ohair@286 | 594 | // recursively process child elements |
ohair@286 | 595 | Element[] srcChilds = DOMUtils.getChildElements(src); |
ohair@286 | 596 | Element[] dstChilds = DOMUtils.getChildElements(dst); |
ohair@286 | 597 | |
ohair@286 | 598 | for( int i=0; i<srcChilds.length; i++ ) |
ohair@286 | 599 | copyLocators( srcChilds[i], dstChilds[i] ); |
ohair@286 | 600 | } |
ohair@286 | 601 | |
ohair@286 | 602 | |
ohair@286 | 603 | private void reportError( Element errorSource, String formattedMsg ) { |
ohair@286 | 604 | reportError( errorSource, formattedMsg, null ); |
ohair@286 | 605 | } |
ohair@286 | 606 | |
ohair@286 | 607 | private void reportError( Element errorSource, |
ohair@286 | 608 | String formattedMsg, Exception nestedException ) { |
ohair@286 | 609 | |
ohair@286 | 610 | SAXParseException e = new SAXParseException2( formattedMsg, |
ohair@286 | 611 | forest.locatorTable.getStartLocation(errorSource), |
ohair@286 | 612 | nestedException ); |
ohair@286 | 613 | errorHandler.error(e); |
ohair@286 | 614 | } |
ohair@286 | 615 | } |