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 &lt;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., &lt;JAXWS:class>)
322      * @param target XML wsdl element under which the declaration should move.
323      *               For example, &lt;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