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.tools.internal.ws.wsdl.parser; 27 28 import com.sun.istack.internal.NotNull; 29 import com.sun.istack.internal.Nullable; 30 import com.sun.istack.internal.SAXParseException2; 31 import com.sun.tools.internal.ws.resources.WsdlMessages; 32 import com.sun.tools.internal.ws.wscompile.ErrorReceiver; 33 import com.sun.tools.internal.ws.wscompile.WsimportOptions; 34 import com.sun.tools.internal.ws.wsdl.document.jaxws.JAXWSBindingsConstants; 35 import com.sun.tools.internal.xjc.util.DOMUtils; 36 import com.sun.xml.internal.bind.v2.util.EditDistance; 37 import com.sun.xml.internal.ws.util.DOMUtil; 38 import com.sun.xml.internal.ws.util.JAXWSUtils; 39 import com.sun.xml.internal.ws.util.xml.XmlUtil; 40 import org.w3c.dom.*; 41 import org.xml.sax.SAXParseException; 42 43 import javax.xml.namespace.NamespaceContext; 44 import javax.xml.xpath.XPath; 45 import javax.xml.xpath.XPathConstants; 46 import javax.xml.xpath.XPathExpressionException; 47 import javax.xml.xpath.XPathFactory; 48 import java.net.MalformedURLException; 49 import java.net.URL; 50 import java.util.ArrayList; 51 import java.util.HashSet; 52 import java.util.Iterator; 53 import java.util.Set; 54 55 56 /** 57 * Internalizes external binding declarations. 58 * 59 * @author Vivek Pandey 60 */ 61 public class Internalizer { 62 63 private final XPath xpath = xpf.get().newXPath(); 64 private final DOMForest forest; 65 private final ErrorReceiver errorReceiver; 66 Internalizer(DOMForest forest, WsimportOptions options, ErrorReceiver errorReceiver)67 public Internalizer(DOMForest forest, WsimportOptions options, ErrorReceiver errorReceiver) { 68 this.forest = forest; 69 this.errorReceiver = errorReceiver; 70 } 71 transform()72 public void transform() { 73 for (Element jaxwsBinding : forest.outerMostBindings) { 74 internalize(jaxwsBinding, jaxwsBinding); 75 } 76 } 77 78 private static final ContextClassloaderLocal<XPathFactory> xpf = new ContextClassloaderLocal<XPathFactory>() { 79 @Override 80 protected XPathFactory initialValue() throws Exception { 81 return XPathFactory.newInstance(); 82 } 83 }; 84 /** 85 * Validates attributes of a <JAXWS:bindings> element. 86 */ validate(Element bindings)87 private void validate(Element bindings) { 88 NamedNodeMap atts = bindings.getAttributes(); 89 for (int i = 0; i < atts.getLength(); i++) { 90 Attr a = (Attr) atts.item(i); 91 if (a.getNamespaceURI() != null) { 92 continue; // all foreign namespace OK. 93 } 94 if (a.getLocalName().equals("node")) { 95 continue; 96 } 97 if (a.getLocalName().equals("wsdlLocation")) { 98 continue; 99 } 100 101 // TODO: flag error for this undefined attribute 102 } 103 } 104 internalize(Element bindings, Node inheritedTarget)105 private void internalize(Element bindings, Node inheritedTarget) { 106 // start by the inherited target 107 Node target = inheritedTarget; 108 109 validate(bindings); // validate this node 110 111 // look for @wsdlLocation 112 if (isTopLevelBinding(bindings)) { 113 String wsdlLocation; 114 if (bindings.getAttributeNode("wsdlLocation") != null) { 115 wsdlLocation = bindings.getAttribute("wsdlLocation"); 116 117 try { 118 // absolutize this URI. 119 // TODO: use the URI class 120 // TODO: honor xml:base 121 wsdlLocation = new URL(new URL(forest.getSystemId(bindings.getOwnerDocument())), 122 wsdlLocation).toExternalForm(); 123 } catch (MalformedURLException e) { 124 wsdlLocation = JAXWSUtils.absolutize(JAXWSUtils.getFileOrURLName(wsdlLocation)); 125 } 126 } else { 127 //the node does not have 128 wsdlLocation = forest.getFirstRootDocument(); 129 } 130 target = forest.get(wsdlLocation); 131 132 if (target == null) { 133 reportError(bindings, WsdlMessages.INTERNALIZER_INCORRECT_SCHEMA_REFERENCE(wsdlLocation, EditDistance.findNearest(wsdlLocation, forest.listSystemIDs()))); 134 return; // abort processing this <JAXWS:bindings> 135 } 136 } 137 138 //if the target node is xs:schema, declare the jaxb version on it as latter on it will be 139 //required by the inlined schema bindings 140 141 Element element = DOMUtil.getFirstElementChild(target); 142 if (element != null && element.getNamespaceURI().equals(Constants.NS_WSDL) && element.getLocalName().equals("definitions")) { 143 //get all schema elements 144 Element type = DOMUtils.getFirstChildElement(element, Constants.NS_WSDL, "types"); 145 if (type != null) { 146 for (Element schemaElement : DOMUtils.getChildElements(type, Constants.NS_XSD, "schema")) { 147 if (!schemaElement.hasAttributeNS(Constants.NS_XMLNS, "jaxb")) { 148 schemaElement.setAttributeNS(Constants.NS_XMLNS, "xmlns:jaxb", JAXWSBindingsConstants.NS_JAXB_BINDINGS); 149 } 150 151 //add jaxb:bindings version info. Lets put it to 1.0, may need to change latter 152 if (!schemaElement.hasAttributeNS(JAXWSBindingsConstants.NS_JAXB_BINDINGS, "version")) { 153 schemaElement.setAttributeNS(JAXWSBindingsConstants.NS_JAXB_BINDINGS, "jaxb:version", JAXWSBindingsConstants.JAXB_BINDING_VERSION); 154 } 155 } 156 } 157 } 158 159 160 NodeList targetNodes = null; 161 boolean hasNode = true; 162 boolean isToplevelBinding = isTopLevelBinding(bindings); 163 if ((isJAXWSBindings(bindings) || isJAXBBindings(bindings)) && bindings.getAttributeNode("node") != null) { 164 targetNodes = evaluateXPathMultiNode(bindings, target, bindings.getAttribute("node"), new NamespaceContextImpl(bindings)); 165 } else 166 if (isJAXWSBindings(bindings) && (bindings.getAttributeNode("node") == null) && !isToplevelBinding) { 167 hasNode = false; 168 } else 169 if (isGlobalBinding(bindings) && !isWSDLDefinition(target) && isTopLevelBinding(bindings.getParentNode())) { 170 targetNodes = getWSDLDefintionNode(bindings, target); 171 } 172 173 //if target is null or empty it means the xpath evaluation has some problem, 174 // just return 175 if (targetNodes == null && hasNode && !isToplevelBinding) { 176 return; 177 } 178 179 if (hasNode) { 180 if (targetNodes != null) { 181 for (int i = 0; i < targetNodes.getLength(); i++) { 182 insertBinding(bindings, targetNodes.item(i)); 183 // look for child <JAXWS:bindings> and process them recursively 184 Element[] children = getChildElements(bindings); 185 for (Element child : children) { 186 if ("bindings".equals(child.getLocalName())) { 187 internalize(child, targetNodes.item(i)); 188 } 189 } 190 } 191 } 192 } 193 if (targetNodes == null) { 194 // look for child <JAXWS:bindings> and process them recursively 195 Element[] children = getChildElements(bindings); 196 197 for (Element child : children) { 198 internalize(child, target); 199 } 200 } 201 } 202 203 /** 204 * Moves JAXWS customizations under their respective target nodes. 205 */ insertBinding(@otNull Element bindings, @NotNull Node target)206 private void insertBinding(@NotNull Element bindings, @NotNull Node target) { 207 if ("bindings".equals(bindings.getLocalName())) { 208 Element[] children = DOMUtils.getChildElements(bindings); 209 for (Element item : children) { 210 if ("bindings".equals(item.getLocalName())) { 211 //done 212 } else { 213 moveUnder(item, (Element) target); 214 215 } 216 } 217 } else { 218 moveUnder(bindings, (Element) target); 219 } 220 } 221 getWSDLDefintionNode(Node bindings, Node target)222 private NodeList getWSDLDefintionNode(Node bindings, Node target) { 223 return evaluateXPathMultiNode(bindings, target, "wsdl:definitions", 224 new NamespaceContext() { 225 @Override 226 public String getNamespaceURI(String prefix) { 227 return "http://schemas.xmlsoap.org/wsdl/"; 228 } 229 230 @Override 231 public String getPrefix(String nsURI) { 232 throw new UnsupportedOperationException(); 233 } 234 235 @Override 236 public Iterator getPrefixes(String namespaceURI) { 237 throw new UnsupportedOperationException(); 238 } 239 }); 240 } 241 242 private boolean isWSDLDefinition(Node target) { 243 if (target == null) { 244 return false; 245 } 246 String localName = target.getLocalName(); 247 String nsURI = target.getNamespaceURI(); 248 return fixNull(localName).equals("definitions") && fixNull(nsURI).equals("http://schemas.xmlsoap.org/wsdl/"); 249 } 250 251 private boolean isTopLevelBinding(Node node) { 252 return node.getOwnerDocument().getDocumentElement() == node; 253 } 254 255 private boolean isJAXWSBindings(Node bindings) { 256 return (bindings.getNamespaceURI().equals(JAXWSBindingsConstants.NS_JAXWS_BINDINGS) && bindings.getLocalName().equals("bindings")); 257 } 258 259 private boolean isJAXBBindings(Node bindings) { 260 return (bindings.getNamespaceURI().equals(JAXWSBindingsConstants.NS_JAXB_BINDINGS) && bindings.getLocalName().equals("bindings")); 261 } 262 263 private boolean isGlobalBinding(Node bindings) { 264 if (bindings.getNamespaceURI() == null) { 265 errorReceiver.warning(forest.locatorTable.getStartLocation((Element) bindings), WsdlMessages.INVALID_CUSTOMIZATION_NAMESPACE(bindings.getLocalName())); 266 return false; 267 } 268 return (bindings.getNamespaceURI().equals(JAXWSBindingsConstants.NS_JAXWS_BINDINGS) && 269 (bindings.getLocalName().equals("package") || 270 bindings.getLocalName().equals("enableAsyncMapping") || 271 bindings.getLocalName().equals("enableAdditionalSOAPHeaderMapping") || 272 bindings.getLocalName().equals("enableWrapperStyle") || 273 bindings.getLocalName().equals("enableMIMEContent"))); 274 } 275 276 private static Element[] getChildElements(Element parent) { 277 ArrayList<Element> a = new ArrayList<Element>(); 278 NodeList children = parent.getChildNodes(); 279 for (int i = 0; i < children.getLength(); i++) { 280 Node item = children.item(i); 281 if (!(item instanceof Element)) { 282 continue; 283 } 284 if (JAXWSBindingsConstants.NS_JAXWS_BINDINGS.equals(item.getNamespaceURI()) || 285 JAXWSBindingsConstants.NS_JAXB_BINDINGS.equals(item.getNamespaceURI())) { 286 a.add((Element) item); 287 } 288 } 289 return a.toArray(new Element[a.size()]); 290 } 291 292 private NodeList evaluateXPathMultiNode(Node bindings, Node target, String expression, NamespaceContext namespaceContext) { 293 NodeList nlst; 294 try { 295 xpath.setNamespaceContext(namespaceContext); 296 nlst = (NodeList) xpath.evaluate(expression, target, XPathConstants.NODESET); 297 } catch (XPathExpressionException e) { 298 reportError((Element) bindings, WsdlMessages.INTERNALIZER_X_PATH_EVALUATION_ERROR(e.getMessage()), e); 299 return null; // abort processing this <jaxb:bindings> 300 } 301 302 if (nlst.getLength() == 0) { 303 reportError((Element) bindings, WsdlMessages.INTERNALIZER_X_PATH_EVALUATES_TO_NO_TARGET(expression)); 304 return null; // abort 305 } 306 307 return nlst; 308 } 309 310 private boolean isJAXBBindingElement(Element e) { 311 return fixNull(e.getNamespaceURI()).equals(JAXWSBindingsConstants.NS_JAXB_BINDINGS); 312 } 313 314 private boolean isJAXWSBindingElement(Element e) { 315 return fixNull(e.getNamespaceURI()).equals(JAXWSBindingsConstants.NS_JAXWS_BINDINGS); 316 } 317 318 /** 319 * Moves the "decl" node under the "target" node. 320 * 321 * @param decl A JAXWS customization element (e.g., <JAXWS:class>) 322 * @param target XML wsdl element under which the declaration should move. 323 * For example, <xs:element> 324 */ 325 private void moveUnder(Element decl, Element target) { 326 327 //if there is @node on decl and has a child element jaxb:bindings, move it under the target 328 //Element jaxb = getJAXBBindingElement(decl); 329 if (isJAXBBindingElement(decl)) { 330 //add jaxb namespace declaration 331 if (!target.hasAttributeNS(Constants.NS_XMLNS, "jaxb")) { 332 target.setAttributeNS(Constants.NS_XMLNS, "xmlns:jaxb", JAXWSBindingsConstants.NS_JAXB_BINDINGS); 333 } 334 335 //add jaxb:bindings version info. Lets put it to 1.0, may need to change latter 336 if (!target.hasAttributeNS(JAXWSBindingsConstants.NS_JAXB_BINDINGS, "version")) { 337 target.setAttributeNS(JAXWSBindingsConstants.NS_JAXB_BINDINGS, "jaxb:version", JAXWSBindingsConstants.JAXB_BINDING_VERSION); 338 } 339 340 // HACK: allow XJC extension all the time. This allows people to specify 341 // the <xjc:someExtension> in the external bindings. Otherwise users lack the ability 342 // to specify jaxb:extensionBindingPrefixes, so it won't work. 343 // 344 // the current workaround is still problematic in the sense that 345 // it can't support user-defined extensions. This needs more careful thought. 346 347 //JAXB doesn't allow writing jaxb:extensionbindingPrefix anywhere other than root element so lets write only on <xs:schema> 348 if (target.getLocalName().equals("schema") && target.getNamespaceURI().equals(Constants.NS_XSD) && !target.hasAttributeNS(JAXWSBindingsConstants.NS_JAXB_BINDINGS, "extensionBindingPrefixes")) { 349 target.setAttributeNS(JAXWSBindingsConstants.NS_JAXB_BINDINGS, "jaxb:extensionBindingPrefixes", "xjc"); 350 target.setAttributeNS(Constants.NS_XMLNS, "xmlns:xjc", JAXWSBindingsConstants.NS_XJC_BINDINGS); 351 } 352 353 //insert xs:annotation/xs:appinfo where in jaxb:binding will be put 354 target = refineSchemaTarget(target); 355 copyInscopeNSAttributes(decl); 356 } else if (isJAXWSBindingElement(decl)) { 357 //add jaxb namespace declaration 358 if (!target.hasAttributeNS(Constants.NS_XMLNS, "JAXWS")) { 359 target.setAttributeNS(Constants.NS_XMLNS, "xmlns:JAXWS", JAXWSBindingsConstants.NS_JAXWS_BINDINGS); 360 } 361 362 //insert xs:annotation/xs:appinfo where in jaxb:binding will be put 363 target = refineWSDLTarget(target); 364 copyInscopeNSAttributes(decl); 365 } else { 366 return; 367 } 368 369 // finally move the declaration to the target node. 370 if (target.getOwnerDocument() != decl.getOwnerDocument()) { 371 // if they belong to different DOM documents, we need to clone them 372 decl = (Element) target.getOwnerDocument().importNode(decl, true); 373 374 } 375 376 target.appendChild(decl); 377 } 378 379 /** 380 * Copy in-scope namespace declarations of the decl node 381 * to the decl node itself so that this move won't change 382 * the in-scope namespace bindings. 383 */ 384 private void copyInscopeNSAttributes(Element e) { 385 Element p = e; 386 Set<String> inscopes = new HashSet<String>(); 387 while (true) { 388 NamedNodeMap atts = p.getAttributes(); 389 for (int i = 0; i < atts.getLength(); i++) { 390 Attr a = (Attr) atts.item(i); 391 if (Constants.NS_XMLNS.equals(a.getNamespaceURI())) { 392 String prefix; 393 if (a.getName().indexOf(':') == -1) { 394 prefix = ""; 395 } else { 396 prefix = a.getLocalName(); 397 } 398 399 if (inscopes.add(prefix) && p != e) { 400 // if this is the first time we see this namespace bindings, 401 // copy the declaration. 402 // if p==decl, there's no need to. Note that 403 // we want to add prefix to inscopes even if p==Decl 404 405 e.setAttributeNodeNS((Attr) a.cloneNode(true)); 406 } 407 } 408 } 409 410 if (p.getParentNode() instanceof Document) { 411 break; 412 } 413 414 p = (Element) p.getParentNode(); 415 } 416 417 if (!inscopes.contains("")) { 418 // if the default namespace was undeclared in the context of decl, 419 // it must be explicitly set to "" since the new environment might 420 // have a different default namespace URI. 421 e.setAttributeNS(Constants.NS_XMLNS, "xmlns", ""); 422 } 423 } 424 425 public Element refineSchemaTarget(Element target) { 426 // look for existing xs:annotation 427 Element annotation = DOMUtils.getFirstChildElement(target, Constants.NS_XSD, "annotation"); 428 if (annotation == null) { 429 // none exists. need to make one 430 annotation = insertXMLSchemaElement(target, "annotation"); 431 } 432 433 // then look for appinfo 434 Element appinfo = DOMUtils.getFirstChildElement(annotation, Constants.NS_XSD, "appinfo"); 435 if (appinfo == null) { 436 // none exists. need to make one 437 appinfo = insertXMLSchemaElement(annotation, "appinfo"); 438 } 439 440 return appinfo; 441 } 442 443 public Element refineWSDLTarget(Element target) { 444 // look for existing xs:annotation 445 Element JAXWSBindings = DOMUtils.getFirstChildElement(target, JAXWSBindingsConstants.NS_JAXWS_BINDINGS, "bindings"); 446 if (JAXWSBindings == null) { 447 // none exists. need to make one 448 JAXWSBindings = insertJAXWSBindingsElement(target, "bindings"); 449 } 450 return JAXWSBindings; 451 } 452 453 /** 454 * Creates a new XML Schema element of the given local name 455 * and insert it as the first child of the given parent node. 456 * 457 * @return Newly create element. 458 */ 459 private Element insertXMLSchemaElement(Element parent, String localName) { 460 // use the same prefix as the parent node to avoid modifying 461 // the namespace binding. 462 String qname = parent.getTagName(); 463 int idx = qname.indexOf(':'); 464 if (idx == -1) { 465 qname = localName; 466 } else { 467 qname = qname.substring(0, idx + 1) + localName; 468 } 469 470 Element child = parent.getOwnerDocument().createElementNS(Constants.NS_XSD, qname); 471 472 NodeList children = parent.getChildNodes(); 473 474 if (children.getLength() == 0) { 475 parent.appendChild(child); 476 } else { 477 parent.insertBefore(child, children.item(0)); 478 } 479 480 return child; 481 } 482 483 private Element insertJAXWSBindingsElement(Element parent, String localName) { 484 String qname = "JAXWS:" + localName; 485 486 Element child = parent.getOwnerDocument().createElementNS(JAXWSBindingsConstants.NS_JAXWS_BINDINGS, qname); 487 488 NodeList children = parent.getChildNodes(); 489 490 if (children.getLength() == 0) { 491 parent.appendChild(child); 492 } else { 493 parent.insertBefore(child, children.item(0)); 494 } 495 496 return child; 497 } 498 499 @NotNull 500 static String fixNull(@Nullable String s) { 501 if (s == null) { 502 return ""; 503 } else { 504 return s; 505 } 506 } 507 508 private void reportError(Element errorSource, String formattedMsg) { 509 reportError(errorSource, formattedMsg, null); 510 } 511 512 private void reportError(Element errorSource, 513 String formattedMsg, Exception nestedException) { 514 515 SAXParseException e = new SAXParseException2(formattedMsg, 516 forest.locatorTable.getStartLocation(errorSource), 517 nestedException); 518 errorReceiver.error(e); 519 } 520 521 522 } 523