001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common Development
008: * and Distribution License("CDDL") (collectively, the "License"). You
009: * may not use this file except in compliance with the License. You can obtain
010: * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
011: * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
012: * language governing permissions and limitations under the License.
013: *
014: * When distributing the software, include this License Header Notice in each
015: * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
016: * Sun designates this particular file as subject to the "Classpath" exception
017: * as provided by Sun in the GPL Version 2 section of the License file that
018: * accompanied this code. If applicable, add the following below the License
019: * Header, with the fields enclosed by brackets [] replaced by your own
020: * identifying information: "Portions Copyrighted [year]
021: * [name of copyright owner]"
022: *
023: * Contributor(s):
024: *
025: * If you wish your version of this file to be governed by only the CDDL or
026: * only the GPL Version 2, indicate your decision by adding "[Contributor]
027: * elects to include this software in this distribution under the [CDDL or GPL
028: * Version 2] license." If you don't indicate a single choice of license, a
029: * recipient has the option to distribute your version of this file under
030: * either the CDDL, the GPL Version 2 or to extend the choice of license to
031: * its licensees as provided above. However, if you add GPL Version 2 code
032: * and therefore, elected the GPL Version 2 license, then the option applies
033: * only if the new code is made subject to such option by the copyright
034: * holder.
035: */
036: package com.sun.tools.ws.wsdl.parser;
037:
038: import com.sun.istack.NotNull;
039: import com.sun.istack.Nullable;
040: import com.sun.istack.SAXParseException2;
041: import com.sun.tools.ws.resources.WsdlMessages;
042: import com.sun.tools.ws.wscompile.ErrorReceiver;
043: import com.sun.tools.ws.wscompile.WsimportOptions;
044: import com.sun.tools.ws.wsdl.document.jaxws.JAXWSBindingsConstants;
045: import com.sun.tools.xjc.util.DOMUtils;
046: import com.sun.xml.bind.v2.util.EditDistance;
047: import com.sun.xml.ws.util.DOMUtil;
048: import com.sun.xml.ws.util.JAXWSUtils;
049: import org.w3c.dom.Attr;
050: import org.w3c.dom.Document;
051: import org.w3c.dom.Element;
052: import org.w3c.dom.NamedNodeMap;
053: import org.w3c.dom.Node;
054: import org.w3c.dom.NodeList;
055: import org.xml.sax.SAXParseException;
056:
057: import javax.xml.namespace.NamespaceContext;
058: import javax.xml.xpath.XPath;
059: import javax.xml.xpath.XPathConstants;
060: import javax.xml.xpath.XPathExpressionException;
061: import javax.xml.xpath.XPathFactory;
062: import java.net.MalformedURLException;
063: import java.net.URL;
064: import java.util.ArrayList;
065: import java.util.HashMap;
066: import java.util.HashSet;
067: import java.util.Iterator;
068: import java.util.Map;
069: import java.util.Set;
070:
071: /**
072: * Internalizes external binding declarations.
073: *
074: * @author Vivek Pandey
075: */
076: public class Internalizer {
077: private static final XPathFactory xpf = XPathFactory.newInstance();
078: private final XPath xpath = xpf.newXPath();
079: private final WsimportOptions options;
080: private final DOMForest forest;
081: private final ErrorReceiver errorReceiver;
082:
083: public Internalizer(DOMForest forest, WsimportOptions options,
084: ErrorReceiver errorReceiver) {
085: this .forest = forest;
086: this .options = options;
087: this .errorReceiver = errorReceiver;
088: }
089:
090: public void transform() {
091: Map<Element, Node> targetNodes = new HashMap<Element, Node>();
092: for (Element jaxwsBinding : forest.outerMostBindings) {
093: buildTargetNodeMap(jaxwsBinding, jaxwsBinding, targetNodes);
094: }
095: for (Element jaxwsBinding : forest.outerMostBindings) {
096: move(jaxwsBinding, targetNodes);
097: }
098: }
099:
100: /**
101: * Validates attributes of a <JAXWS:bindings> element.
102: */
103: private void validate(Element bindings) {
104: NamedNodeMap atts = bindings.getAttributes();
105: for (int i = 0; i < atts.getLength(); i++) {
106: Attr a = (Attr) atts.item(i);
107: if (a.getNamespaceURI() != null)
108: continue; // all foreign namespace OK.
109: if (a.getLocalName().equals("node"))
110: continue;
111: if (a.getLocalName().equals("wsdlLocation"))
112: continue;
113:
114: // TODO: flag error for this undefined attribute
115: }
116: }
117:
118: /**
119: * Determines the target node of the "bindings" element
120: * by using the inherited target node, then put
121: * the result into the "result" map.
122: */
123: private void buildTargetNodeMap(Element bindings,
124: Node inheritedTarget, Map<Element, Node> result) {
125: // start by the inherited target
126: Node target = inheritedTarget;
127:
128: validate(bindings); // validate this node
129:
130: // look for @wsdlLocation
131: if (isTopLevelBinding(bindings)) {
132: String wsdlLocation;
133: if (bindings.getAttributeNode("wsdlLocation") != null) {
134: wsdlLocation = bindings.getAttribute("wsdlLocation");
135:
136: try {
137: // absolutize this URI.
138: // TODO: use the URI class
139: // TODO: honor xml:base
140: wsdlLocation = new URL(new URL(forest
141: .getSystemId(bindings.getOwnerDocument())),
142: wsdlLocation).toExternalForm();
143: } catch (MalformedURLException e) {
144: wsdlLocation = JAXWSUtils.absolutize(JAXWSUtils
145: .getFileOrURLName(wsdlLocation));
146: }
147: } else {
148: //the node does not have
149: wsdlLocation = forest.getFirstRootDocument();
150: }
151: target = forest.get(wsdlLocation);
152:
153: if (target == null) {
154: reportError(bindings, WsdlMessages
155: .INTERNALIZER_INCORRECT_SCHEMA_REFERENCE(
156: wsdlLocation, EditDistance.findNearest(
157: wsdlLocation, forest
158: .listSystemIDs())));
159: return; // abort processing this <JAXWS:bindings>
160: }
161: }
162:
163: //if the target node is xs:schema, declare the jaxb version on it as latter on it will be
164: //required by the inlined schema bindings
165:
166: Element element = DOMUtil.getFirstElementChild(target);
167: if (element != null
168: && element.getNamespaceURI().equals(Constants.NS_WSDL)
169: && element.getLocalName().equals("definitions")) {
170: //get all schema elements
171: Element type = DOMUtils.getFirstChildElement(element,
172: Constants.NS_WSDL, "types");
173: if (type != null) {
174: for (Element schemaElement : DOMUtils.getChildElements(
175: type, Constants.NS_XSD, "schema")) {
176: if (!schemaElement.hasAttributeNS(
177: Constants.NS_XMLNS, "jaxb")) {
178: schemaElement
179: .setAttributeNS(
180: Constants.NS_XMLNS,
181: "xmlns:jaxb",
182: JAXWSBindingsConstants.NS_JAXB_BINDINGS);
183: }
184:
185: //add jaxb:bindings version info. Lets put it to 1.0, may need to change latter
186: if (!schemaElement.hasAttributeNS(
187: JAXWSBindingsConstants.NS_JAXB_BINDINGS,
188: "version")) {
189: schemaElement
190: .setAttributeNS(
191: JAXWSBindingsConstants.NS_JAXB_BINDINGS,
192: "jaxb:version",
193: JAXWSBindingsConstants.JAXB_BINDING_VERSION);
194: }
195: }
196: }
197: }
198:
199: boolean hasNode = true;
200: if ((isJAXWSBindings(bindings) || isJAXBBindings(bindings))
201: && bindings.getAttributeNode("node") != null) {
202: target = evaluateXPathNode(bindings, target, bindings
203: .getAttribute("node"), new NamespaceContextImpl(
204: bindings));
205: } else if (isJAXWSBindings(bindings)
206: && (bindings.getAttributeNode("node") == null)
207: && !isTopLevelBinding(bindings)) {
208: hasNode = false;
209: } else if (isGlobalBinding(bindings)
210: && !isWSDLDefinition(target)
211: && isTopLevelBinding(bindings.getParentNode())) {
212: target = getWSDLDefintionNode(bindings, target);
213: }
214:
215: //if target is null it means the xpath evaluation has some problem,
216: // just return
217: if (target == null)
218: return;
219:
220: // update the result map
221: if (hasNode)
222: result.put(bindings, target);
223:
224: // look for child <JAXWS:bindings> and process them recursively
225: Element[] children = getChildElements(bindings);
226: for (Element child : children)
227: buildTargetNodeMap(child, target, result);
228: }
229:
230: private Node getWSDLDefintionNode(Node bindings, Node target) {
231: return evaluateXPathNode(bindings, target, "wsdl:definitions",
232: new NamespaceContext() {
233: public String getNamespaceURI(String prefix) {
234: return "http://schemas.xmlsoap.org/wsdl/";
235: }
236:
237: public String getPrefix(String nsURI) {
238: throw new UnsupportedOperationException();
239: }
240:
241: public Iterator getPrefixes(String namespaceURI) {
242: throw new UnsupportedOperationException();
243: }
244: });
245: }
246:
247: private boolean isWSDLDefinition(Node target) {
248: if (target == null)
249: return false;
250: String localName = target.getLocalName();
251: String nsURI = target.getNamespaceURI();
252: return fixNull(localName).equals("definitions")
253: && fixNull(nsURI).equals(
254: "http://schemas.xmlsoap.org/wsdl/");
255: }
256:
257: private boolean isTopLevelBinding(Node node) {
258: return node.getOwnerDocument().getDocumentElement() == node;
259: }
260:
261: private boolean isJAXWSBindings(Node bindings) {
262: return (bindings.getNamespaceURI().equals(
263: JAXWSBindingsConstants.NS_JAXWS_BINDINGS) && bindings
264: .getLocalName().equals("bindings"));
265: }
266:
267: private boolean isJAXBBindings(Node bindings) {
268: return (bindings.getNamespaceURI().equals(
269: JAXWSBindingsConstants.NS_JAXB_BINDINGS) && bindings
270: .getLocalName().equals("bindings"));
271: }
272:
273: private boolean isGlobalBinding(Node bindings) {
274: if (bindings.getNamespaceURI() == null) {
275: errorReceiver.warning(forest.locatorTable
276: .getStartLocation((Element) bindings), WsdlMessages
277: .INVALID_CUSTOMIZATION_NAMESPACE(bindings
278: .getLocalName()));
279: return false;
280: }
281: return (bindings.getNamespaceURI().equals(
282: JAXWSBindingsConstants.NS_JAXWS_BINDINGS) && (bindings
283: .getLocalName().equals("package")
284: || bindings.getLocalName().equals("enableAsyncMapping")
285: || bindings.getLocalName().equals(
286: "enableAdditionalSOAPHeaderMapping")
287: || bindings.getLocalName().equals("enableWrapperStyle") || bindings
288: .getLocalName().equals("enableMIMEContent")));
289: }
290:
291: private static Element[] getChildElements(Element parent) {
292: ArrayList<Element> a = new ArrayList<Element>();
293: NodeList children = parent.getChildNodes();
294: for (int i = 0; i < children.getLength(); i++) {
295: Node item = children.item(i);
296: if (!(item instanceof Element))
297: continue;
298:
299: if (JAXWSBindingsConstants.NS_JAXWS_BINDINGS.equals(item
300: .getNamespaceURI())
301: || JAXWSBindingsConstants.NS_JAXB_BINDINGS
302: .equals(item.getNamespaceURI()))
303: a.add((Element) item);
304: }
305: return a.toArray(new Element[a.size()]);
306: }
307:
308: private Node evaluateXPathNode(Node bindings, Node target,
309: String expression, NamespaceContext namespaceContext) {
310: NodeList nlst;
311: try {
312: xpath.setNamespaceContext(namespaceContext);
313: nlst = (NodeList) xpath.evaluate(expression, target,
314: XPathConstants.NODESET);
315: } catch (XPathExpressionException e) {
316: reportError((Element) bindings, WsdlMessages
317: .INTERNALIZER_X_PATH_EVALUATION_ERROR(e
318: .getMessage()), e);
319: return null; // abort processing this <jaxb:bindings>
320: }
321:
322: if (nlst.getLength() == 0) {
323: reportError(
324: (Element) bindings,
325: WsdlMessages
326: .INTERNALIZER_X_PATH_EVALUATES_TO_NO_TARGET(expression));
327: return null; // abort
328: }
329:
330: if (nlst.getLength() != 1) {
331: reportError((Element) bindings, WsdlMessages
332: .INTERNALIZER_X_PATH_EVAULATES_TO_TOO_MANY_TARGETS(
333: expression, nlst.getLength()));
334: return null; // abort
335: }
336:
337: Node rnode = nlst.item(0);
338: if (!(rnode instanceof Element)) {
339: reportError(
340: (Element) bindings,
341: WsdlMessages
342: .INTERNALIZER_X_PATH_EVALUATES_TO_NON_ELEMENT(expression));
343: return null; // abort
344: }
345: return rnode;
346: }
347:
348: /**
349: * Moves JAXWS customizations under their respective target nodes.
350: */
351: private void move(Element bindings, Map<Element, Node> targetNodes) {
352: Node target = targetNodes.get(bindings);
353: if (target == null)
354: // this must be the result of an error on the external binding.
355: // recover from the error by ignoring this node
356: return;
357:
358: Element[] children = DOMUtils.getChildElements(bindings);
359:
360: for (Element item : children) {
361: if ("bindings".equals(item.getLocalName())) {
362: // process child <jaxws:bindings> recursively
363: move(item, targetNodes);
364: } else if (isGlobalBinding(item)) {
365: target = targetNodes.get(item);
366: moveUnder(item, (Element) target);
367: } else {
368: if (!(target instanceof Element)) {
369: return; // abort
370: }
371: // move this node under the target
372: moveUnder(item, (Element) target);
373: }
374: }
375: }
376:
377: private boolean isJAXBBindingElement(Element e) {
378: return fixNull(e.getNamespaceURI()).equals(
379: JAXWSBindingsConstants.NS_JAXB_BINDINGS);
380: }
381:
382: private boolean isJAXWSBindingElement(Element e) {
383: return fixNull(e.getNamespaceURI()).equals(
384: JAXWSBindingsConstants.NS_JAXWS_BINDINGS);
385: }
386:
387: /**
388: * Moves the "decl" node under the "target" node.
389: *
390: * @param decl A JAXWS customization element (e.g., <JAXWS:class>)
391: * @param target XML wsdl element under which the declaration should move.
392: * For example, <xs:element>
393: */
394: private void moveUnder(Element decl, Element target) {
395:
396: //if there is @node on decl and has a child element jaxb:bindings, move it under the target
397: //Element jaxb = getJAXBBindingElement(decl);
398: if (isJAXBBindingElement(decl)) {
399: //add jaxb namespace declaration
400: if (!target.hasAttributeNS(Constants.NS_XMLNS, "jaxb")) {
401: target.setAttributeNS(Constants.NS_XMLNS, "xmlns:jaxb",
402: JAXWSBindingsConstants.NS_JAXB_BINDINGS);
403: }
404:
405: //add jaxb:bindings version info. Lets put it to 1.0, may need to change latter
406: if (!target.hasAttributeNS(
407: JAXWSBindingsConstants.NS_JAXB_BINDINGS, "version")) {
408: target.setAttributeNS(
409: JAXWSBindingsConstants.NS_JAXB_BINDINGS,
410: "jaxb:version",
411: JAXWSBindingsConstants.JAXB_BINDING_VERSION);
412: }
413:
414: // HACK: allow XJC extension all the time. This allows people to specify
415: // the <xjc:someExtension> in the external bindings. Otherwise users lack the ability
416: // to specify jaxb:extensionBindingPrefixes, so it won't work.
417: //
418: // the current workaround is still problematic in the sense that
419: // it can't support user-defined extensions. This needs more careful thought.
420:
421: //JAXB doesn't allow writing jaxb:extensionbindingPrefix anywhere other than root element so lets write only on <xs:schema>
422: if (target.getLocalName().equals("schema")
423: && target.getNamespaceURI()
424: .equals(Constants.NS_XSD)
425: && !target.hasAttributeNS(
426: JAXWSBindingsConstants.NS_JAXB_BINDINGS,
427: "extensionBindingPrefixes")) {
428: target.setAttributeNS(
429: JAXWSBindingsConstants.NS_JAXB_BINDINGS,
430: "jaxb:extensionBindingPrefixes", "xjc");
431: target.setAttributeNS(Constants.NS_XMLNS, "xmlns:xjc",
432: JAXWSBindingsConstants.NS_XJC_BINDINGS);
433: }
434:
435: //insert xs:annotation/xs:appinfo where in jaxb:binding will be put
436: target = refineSchemaTarget(target);
437: copyInscopeNSAttributes(decl);
438: } else if (isJAXWSBindingElement(decl)) {
439: //add jaxb namespace declaration
440: if (!target.hasAttributeNS(Constants.NS_XMLNS, "JAXWS")) {
441: target.setAttributeNS(Constants.NS_XMLNS,
442: "xmlns:JAXWS",
443: JAXWSBindingsConstants.NS_JAXWS_BINDINGS);
444: }
445:
446: //insert xs:annotation/xs:appinfo where in jaxb:binding will be put
447: target = refineWSDLTarget(target);
448: copyInscopeNSAttributes(decl);
449: } else {
450: return;
451: }
452:
453: // finally move the declaration to the target node.
454: if (target.getOwnerDocument() != decl.getOwnerDocument()) {
455: // if they belong to different DOM documents, we need to clone them
456: decl = (Element) target.getOwnerDocument().importNode(decl,
457: true);
458:
459: }
460:
461: target.appendChild(decl);
462: }
463:
464: /**
465: * Copy in-scope namespace declarations of the decl node
466: * to the decl node itself so that this move won't change
467: * the in-scope namespace bindings.
468: */
469: private void copyInscopeNSAttributes(Element e) {
470: Element p = e;
471: Set<String> inscopes = new HashSet<String>();
472: while (true) {
473: NamedNodeMap atts = p.getAttributes();
474: for (int i = 0; i < atts.getLength(); i++) {
475: Attr a = (Attr) atts.item(i);
476: if (Constants.NS_XMLNS.equals(a.getNamespaceURI())) {
477: String prefix;
478: if (a.getName().indexOf(':') == -1)
479: prefix = "";
480: else
481: prefix = a.getLocalName();
482:
483: if (inscopes.add(prefix) && p != e) {
484: // if this is the first time we see this namespace bindings,
485: // copy the declaration.
486: // if p==decl, there's no need to. Note that
487: // we want to add prefix to inscopes even if p==Decl
488:
489: e.setAttributeNodeNS((Attr) a.cloneNode(true));
490: }
491: }
492: }
493:
494: if (p.getParentNode() instanceof Document)
495: break;
496:
497: p = (Element) p.getParentNode();
498: }
499:
500: if (!inscopes.contains("")) {
501: // if the default namespace was undeclared in the context of decl,
502: // it must be explicitly set to "" since the new environment might
503: // have a different default namespace URI.
504: e.setAttributeNS(Constants.NS_XMLNS, "xmlns", "");
505: }
506: }
507:
508: public Element refineSchemaTarget(Element target) {
509: // look for existing xs:annotation
510: Element annotation = DOMUtils.getFirstChildElement(target,
511: Constants.NS_XSD, "annotation");
512: if (annotation == null)
513: // none exists. need to make one
514: annotation = insertXMLSchemaElement(target, "annotation");
515:
516: // then look for appinfo
517: Element appinfo = DOMUtils.getFirstChildElement(annotation,
518: Constants.NS_XSD, "appinfo");
519: if (appinfo == null)
520: // none exists. need to make one
521: appinfo = insertXMLSchemaElement(annotation, "appinfo");
522:
523: return appinfo;
524: }
525:
526: public Element refineWSDLTarget(Element target) {
527: // look for existing xs:annotation
528: Element JAXWSBindings = DOMUtils.getFirstChildElement(target,
529: JAXWSBindingsConstants.NS_JAXWS_BINDINGS, "bindings");
530: if (JAXWSBindings == null)
531: // none exists. need to make one
532: JAXWSBindings = insertJAXWSBindingsElement(target,
533: "bindings");
534: return JAXWSBindings;
535: }
536:
537: /**
538: * Creates a new XML Schema element of the given local name
539: * and insert it as the first child of the given parent node.
540: *
541: * @return Newly create element.
542: */
543: private Element insertXMLSchemaElement(Element parent,
544: String localName) {
545: // use the same prefix as the parent node to avoid modifying
546: // the namespace binding.
547: String qname = parent.getTagName();
548: int idx = qname.indexOf(':');
549: if (idx == -1)
550: qname = localName;
551: else
552: qname = qname.substring(0, idx + 1) + localName;
553:
554: Element child = parent.getOwnerDocument().createElementNS(
555: Constants.NS_XSD, qname);
556:
557: NodeList children = parent.getChildNodes();
558:
559: if (children.getLength() == 0)
560: parent.appendChild(child);
561: else
562: parent.insertBefore(child, children.item(0));
563:
564: return child;
565: }
566:
567: private Element insertJAXWSBindingsElement(Element parent,
568: String localName) {
569: String qname = "JAXWS:" + localName;
570:
571: Element child = parent.getOwnerDocument().createElementNS(
572: JAXWSBindingsConstants.NS_JAXWS_BINDINGS, qname);
573:
574: NodeList children = parent.getChildNodes();
575:
576: if (children.getLength() == 0)
577: parent.appendChild(child);
578: else
579: parent.insertBefore(child, children.item(0));
580:
581: return child;
582: }
583:
584: private static @NotNull
585: String fixNull(@Nullable
586: String s) {
587: if (s == null)
588: return "";
589: else
590: return s;
591: }
592:
593: private void reportError(Element errorSource, String formattedMsg) {
594: reportError(errorSource, formattedMsg, null);
595: }
596:
597: private void reportError(Element errorSource, String formattedMsg,
598: Exception nestedException) {
599:
600: SAXParseException e = new SAXParseException2(formattedMsg,
601: forest.locatorTable.getStartLocation(errorSource),
602: nestedException);
603: errorReceiver.error(e);
604: }
605:
606: }
|