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