001: /*
002: * Portions Copyright 2006 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025: package com.sun.tools.internal.ws.wsdl.parser;
026:
027: import java.net.MalformedURLException;
028: import java.net.URL;
029: import java.net.URLDecoder;
030: import java.util.ArrayList;
031: import java.util.HashMap;
032: import java.util.HashSet;
033: import java.util.Iterator;
034: import java.util.Map;
035: import java.util.Set;
036: import java.io.UnsupportedEncodingException;
037:
038: import javax.xml.namespace.QName;
039: import javax.xml.namespace.NamespaceContext;
040: import javax.xml.xpath.XPath;
041: import javax.xml.xpath.XPathConstants;
042: import javax.xml.xpath.XPathExpressionException;
043: import javax.xml.xpath.XPathFactory;
044:
045: import org.w3c.dom.Attr;
046: import org.w3c.dom.Document;
047: import org.w3c.dom.Element;
048: import org.w3c.dom.NamedNodeMap;
049: import org.w3c.dom.Node;
050: import org.w3c.dom.NodeList;
051:
052: import com.sun.tools.internal.xjc.util.DOMUtils;
053: import com.sun.tools.internal.ws.processor.util.ProcessorEnvironment;
054: import com.sun.xml.internal.ws.util.JAXWSUtils;
055: import com.sun.xml.internal.ws.util.JAXWSUtils;
056: import com.sun.xml.internal.ws.util.localization.Localizable;
057: import com.sun.xml.internal.ws.util.localization.LocalizableMessageFactory;
058: import com.sun.tools.internal.ws.wsdl.document.jaxws.JAXWSBindingsConstants;
059: import com.sun.tools.internal.ws.util.xml.XmlUtil;
060:
061: /**
062: * Internalizes external binding declarations.
063: * @author Vivek Pandey
064: */
065: public class Internalizer {
066: private Map<String, Document> wsdlDocuments;
067: private Map<String, Document> jaxwsBindings;
068: private static final XPathFactory xpf = XPathFactory.newInstance();
069: private final XPath xpath = xpf.newXPath();
070: private final LocalizableMessageFactory messageFactory = new LocalizableMessageFactory(
071: "com.sun.tools.internal.ws.resources.wsdl");;
072: private ProcessorEnvironment env;
073:
074: public void transform(Map<String, Document> jaxwsBindings,
075: Map<String, Document> wsdlDocuments,
076: ProcessorEnvironment env) {
077: if (jaxwsBindings == null)
078: return;
079: this .env = env;
080: this .wsdlDocuments = wsdlDocuments;
081: this .jaxwsBindings = jaxwsBindings;
082: Map targetNodes = new HashMap<Element, Node>();
083:
084: // identify target nodes for all <JAXWS:bindings>
085: for (Map.Entry<String, Document> jaxwsBinding : jaxwsBindings
086: .entrySet()) {
087: Element e = jaxwsBinding.getValue().getDocumentElement();
088: // initially, the inherited context is itself
089: buildTargetNodeMap(e, e, targetNodes);
090: }
091:
092: // then move them to their respective positions.
093: for (Map.Entry<String, Document> jaxwsBinding : jaxwsBindings
094: .entrySet()) {
095: Element e = jaxwsBinding.getValue().getDocumentElement();
096: move(e, targetNodes);
097: }
098:
099: }
100:
101: /**
102: * Validates attributes of a <JAXWS:bindings> element.
103: */
104: private void validate(Element bindings) {
105: NamedNodeMap atts = bindings.getAttributes();
106: for (int i = 0; i < atts.getLength(); i++) {
107: Attr a = (Attr) atts.item(i);
108: if (a.getNamespaceURI() != null)
109: continue; // all foreign namespace OK.
110: if (a.getLocalName().equals("node"))
111: continue;
112: if (a.getLocalName().equals("wsdlLocation"))
113: continue;
114:
115: // TODO: flag error for this undefined attribute
116: }
117: }
118:
119: /**
120: * Gets the DOM tree associated with the specified system ID,
121: * or null if none is found.
122: */
123: public Document get(String systemId) {
124: Document doc = wsdlDocuments.get(systemId);
125:
126: if (doc == null && systemId.startsWith("file:/")
127: && !systemId.startsWith("file://")) {
128: // As of JDK1.4, java.net.URL.toExternal method returns URLs like
129: // "file:/abc/def/ghi" which is an incorrect file protocol URL according to RFC1738.
130: // Some other correctly functioning parts return the correct URLs ("file:///abc/def/ghi"),
131: // and this descripancy breaks DOM look up by system ID.
132:
133: // this extra check solves this problem.
134: doc = wsdlDocuments.get("file://" + systemId.substring(5));
135: }
136:
137: if (doc == null && systemId.startsWith("file:")) {
138: // on Windows, filenames are case insensitive.
139: // perform case-insensitive search for improved user experience
140: String systemPath = getPath(systemId);
141: for (String key : wsdlDocuments.keySet()) {
142: if (key.startsWith("file:")
143: && getPath(key).equalsIgnoreCase(systemPath)) {
144: doc = wsdlDocuments.get(key);
145: break;
146: }
147: }
148: }
149: return doc;
150: }
151:
152: /**
153: * Strips off the leading 'file:///' portion from an URL.
154: */
155: private String getPath(String key) {
156: key = key.substring(5); // skip 'file:'
157: while (key.length() > 0 && key.charAt(0) == '/')
158: key = key.substring(1);
159: return key;
160: }
161:
162: /**
163: * Determines the target node of the "bindings" element
164: * by using the inherited target node, then put
165: * the result into the "result" map.
166: */
167: private void buildTargetNodeMap(Element bindings,
168: Node inheritedTarget, Map<Element, Node> result) {
169: // start by the inherited target
170: Node target = inheritedTarget;
171:
172: validate(bindings); // validate this node
173:
174: // look for @wsdlLocation
175: if (bindings.getAttributeNode("wsdlLocation") != null) {
176: String wsdlLocation = bindings.getAttribute("wsdlLocation");
177:
178: try {
179: // absolutize this URI.
180: // TODO: use the URI class
181: // TODO: honor xml:base
182: wsdlLocation = new URL(new URL(getSystemId(bindings
183: .getOwnerDocument())), wsdlLocation)
184: .toExternalForm();
185: } catch (MalformedURLException e) {
186: wsdlLocation = JAXWSUtils.absolutize(JAXWSUtils
187: .getFileOrURLName(wsdlLocation));
188: }
189:
190: target = get(wsdlLocation);
191: if (target == null) {
192: error("internalizer.targetNotFound",
193: new Object[] { wsdlLocation });
194: return; // abort processing this <JAXWS:bindings>
195: }
196: }
197:
198: boolean hasNode = true;
199: if (isJAXWSBindings(bindings)
200: && bindings.getAttributeNode("node") != null) {
201: target = evaluateXPathNode(target, bindings
202: .getAttribute("node"), new NamespaceContextImpl(
203: bindings));
204: } else if (isJAXWSBindings(bindings)
205: && (bindings.getAttributeNode("node") == null)
206: && !isTopLevelBinding(bindings)) {
207: hasNode = false;
208: } else if (isGlobalBinding(bindings)
209: && !isWSDLDefinition(target)
210: && isTopLevelBinding(bindings.getParentNode())) {
211: target = getWSDLDefintionNode(target);
212: }
213:
214: //if target is null it means the xpath evaluation has some problem,
215: // just return
216: if (target == null)
217: return;
218:
219: // update the result map
220: if (hasNode)
221: result.put(bindings, target);
222:
223: // look for child <JAXWS:bindings> and process them recursively
224: Element[] children = getChildElements(bindings,
225: JAXWSBindingsConstants.NS_JAXWS_BINDINGS);
226: for (int i = 0; i < children.length; i++)
227: buildTargetNodeMap(children[i], target, result);
228: }
229:
230: private Node getWSDLDefintionNode(Node target) {
231: return evaluateXPathNode(target, "wsdl:definitions",
232: new javax.xml.namespace.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: if (((localName != null) && localName.equals("definitions"))
253: && (nsURI != null && nsURI
254: .equals("http://schemas.xmlsoap.org/wsdl/")))
255: return true;
256: return false;
257:
258: }
259:
260: private boolean isTopLevelBinding(Node node) {
261: if (node instanceof Document)
262: node = ((Document) node).getDocumentElement();
263: return ((node != null) && (((Element) node)
264: .getAttributeNode("wsdlLocation") != null));
265: }
266:
267: private boolean isJAXWSBindings(Node bindings) {
268: return (bindings.getNamespaceURI().equals(
269: JAXWSBindingsConstants.NS_JAXWS_BINDINGS) && bindings
270: .getLocalName().equals("bindings"));
271: }
272:
273: private boolean isGlobalBinding(Node bindings) {
274: if ((bindings.getNamespaceURI() == null)) {
275: warn("invalid.customization.namespace",
276: new Object[] { bindings.getLocalName() });
277: return false;
278: }
279: return (bindings.getNamespaceURI().equals(
280: JAXWSBindingsConstants.NS_JAXWS_BINDINGS) && (bindings
281: .getLocalName().equals("package")
282: || bindings.getLocalName().equals("enableAsyncMapping")
283: || bindings.getLocalName().equals(
284: "enableAdditionalSOAPHeaderMapping")
285: || bindings.getLocalName().equals("enableWrapperStyle") || bindings
286: .getLocalName().equals("enableMIMEContent")));
287: }
288:
289: private static Element[] getChildElements(Element parent,
290: String nsUri) {
291: ArrayList a = new ArrayList();
292: NodeList children = parent.getChildNodes();
293: for (int i = 0; i < children.getLength(); i++) {
294: Node item = children.item(i);
295: if (!(item instanceof Element))
296: continue;
297:
298: if (nsUri.equals(item.getNamespaceURI()))
299: a.add(item);
300: }
301: return (Element[]) a.toArray(new Element[a.size()]);
302: }
303:
304: private Node evaluateXPathNode(Node target, String expression,
305: NamespaceContext namespaceContext) {
306: NodeList nlst;
307: try {
308: xpath.setNamespaceContext(namespaceContext);
309: nlst = (NodeList) xpath.evaluate(expression, target,
310: XPathConstants.NODESET);
311: } catch (XPathExpressionException e) {
312: error("internalizer.XPathEvaluationError", new Object[] { e
313: .getMessage() });
314: if (env.verbose())
315: e.printStackTrace();
316: return null; // abort processing this <jaxb:bindings>
317: }
318:
319: if (nlst.getLength() == 0) {
320: error("internalizer.XPathEvaluatesToNoTarget",
321: new Object[] { expression });
322: return null; // abort
323: }
324:
325: if (nlst.getLength() != 1) {
326: error("internalizer.XPathEvaulatesToTooManyTargets",
327: new Object[] { expression, nlst.getLength() });
328: return null; // abort
329: }
330:
331: Node rnode = nlst.item(0);
332: if (!(rnode instanceof Element)) {
333: error("internalizer.XPathEvaluatesToNonElement",
334: new Object[] { expression });
335: return null; // abort
336: }
337: return (Element) rnode;
338: }
339:
340: /**
341: * Moves JAXWS customizations under their respective target nodes.
342: */
343: private void move(Element bindings, Map<Element, Node> targetNodes) {
344: Node target = targetNodes.get(bindings);
345: if (target == null)
346: // this must be the result of an error on the external binding.
347: // recover from the error by ignoring this node
348: return;
349:
350: Element[] children = DOMUtils.getChildElements(bindings);
351:
352: for (Element item : children) {
353: if ("bindings".equals(item.getLocalName())) {
354: // process child <jaxws:bindings> recursively
355: move(item, targetNodes);
356: } else if (isGlobalBinding(item)) {
357: target = targetNodes.get(item);
358: moveUnder(item, (Element) target);
359: } else {
360: if (!(target instanceof Element)) {
361: return; // abort
362: }
363: // move this node under the target
364: moveUnder(item, (Element) target);
365: }
366: }
367: }
368:
369: private boolean isJAXBBindingElement(Element e) {
370: if ((e.getNamespaceURI() != null)
371: && e.getNamespaceURI().equals(
372: JAXWSBindingsConstants.NS_JAXB_BINDINGS))
373: return true;
374: return false;
375: }
376:
377: private boolean isJAXWSBindingElement(Element e) {
378: if ((e.getNamespaceURI() != null)
379: && e.getNamespaceURI().equals(
380: JAXWSBindingsConstants.NS_JAXWS_BINDINGS))
381: return true;
382: return false;
383: }
384:
385: /**
386: * Moves the "decl" node under the "target" node.
387: *
388: * @param decl
389: * A JAXWS customization element (e.g., <JAXWS:class>)
390: *
391: * @param target
392: * XML wsdl element under which the declaration should move.
393: * For example, <xs:element>
394: */
395: private void moveUnder(Element decl, Element target) {
396:
397: //if there is @node on decl and has a child element jaxb:bindings, move it under the target
398: //Element jaxb = getJAXBBindingElement(decl);
399: if (isJAXBBindingElement(decl)) {
400: //add jaxb namespace declaration
401: if (!target.hasAttributeNS(Constants.NS_XMLNS, "jaxb")) {
402: target.setAttributeNS(Constants.NS_XMLNS, "xmlns:jaxb",
403: JAXWSBindingsConstants.NS_JAXB_BINDINGS);
404: }
405:
406: //add jaxb:bindings version info. Lets put it to 1.0, may need to change latter
407: if (!target.hasAttributeNS(
408: JAXWSBindingsConstants.NS_JAXB_BINDINGS, "version")) {
409: target.setAttributeNS(
410: JAXWSBindingsConstants.NS_JAXB_BINDINGS,
411: "jaxb:version",
412: JAXWSBindingsConstants.JAXB_BINDING_VERSION);
413: }
414:
415: //insert xs:annotation/xs:appinfo where in jaxb:binding will be put
416: target = refineSchemaTarget(target);
417: copyInscopeNSAttributes(decl);
418: } else if (isJAXWSBindingElement(decl)) {
419: //add jaxb namespace declaration
420: if (!target.hasAttributeNS(Constants.NS_XMLNS, "JAXWS")) {
421: target.setAttributeNS(Constants.NS_XMLNS,
422: "xmlns:JAXWS",
423: JAXWSBindingsConstants.NS_JAXWS_BINDINGS);
424: }
425:
426: //insert xs:annotation/xs:appinfo where in jaxb:binding will be put
427: target = refineWSDLTarget(target);
428: copyInscopeNSAttributes(decl);
429: } else {
430: return;
431: }
432:
433: // finally move the declaration to the target node.
434: if (target.getOwnerDocument() != decl.getOwnerDocument()) {
435: // if they belong to different DOM documents, we need to clone them
436: Element original = decl;
437: decl = (Element) target.getOwnerDocument().importNode(decl,
438: true);
439:
440: }
441:
442: target.appendChild(decl);
443: }
444:
445: /**
446: * Copy in-scope namespace declarations of the decl node
447: * to the decl node itself so that this move won't change
448: * the in-scope namespace bindings.
449: */
450: private void copyInscopeNSAttributes(Element e) {
451: Element p = e;
452: Set inscopes = new HashSet();
453: while (true) {
454: NamedNodeMap atts = p.getAttributes();
455: for (int i = 0; i < atts.getLength(); i++) {
456: Attr a = (Attr) atts.item(i);
457: if (Constants.NS_XMLNS.equals(a.getNamespaceURI())) {
458: String prefix;
459: if (a.getName().indexOf(':') == -1)
460: prefix = "";
461: else
462: prefix = a.getLocalName();
463:
464: if (inscopes.add(prefix) && p != e) {
465: // if this is the first time we see this namespace bindings,
466: // copy the declaration.
467: // if p==decl, there's no need to. Note that
468: // we want to add prefix to inscopes even if p==Decl
469:
470: e.setAttributeNodeNS((Attr) a.cloneNode(true));
471: }
472: }
473: }
474:
475: if (p.getParentNode() instanceof Document)
476: break;
477:
478: p = (Element) p.getParentNode();
479: }
480:
481: if (!inscopes.contains("")) {
482: // if the default namespace was undeclared in the context of decl,
483: // it must be explicitly set to "" since the new environment might
484: // have a different default namespace URI.
485: e.setAttributeNS(Constants.NS_XMLNS, "xmlns", "");
486: }
487: }
488:
489: public Element refineSchemaTarget(Element target) {
490: // look for existing xs:annotation
491: Element annotation = DOMUtils.getFirstChildElement(target,
492: Constants.NS_XSD, "annotation");
493: if (annotation == null)
494: // none exists. need to make one
495: annotation = insertXMLSchemaElement(target, "annotation");
496:
497: // then look for appinfo
498: Element appinfo = DOMUtils.getFirstChildElement(annotation,
499: Constants.NS_XSD, "appinfo");
500: if (appinfo == null)
501: // none exists. need to make one
502: appinfo = insertXMLSchemaElement(annotation, "appinfo");
503:
504: return appinfo;
505: }
506:
507: public Element refineWSDLTarget(Element target) {
508: // look for existing xs:annotation
509: Element JAXWSBindings = DOMUtils.getFirstChildElement(target,
510: JAXWSBindingsConstants.NS_JAXWS_BINDINGS, "bindings");
511: if (JAXWSBindings == null)
512: // none exists. need to make one
513: JAXWSBindings = insertJAXWSBindingsElement(target,
514: "bindings");
515: return JAXWSBindings;
516: }
517:
518: /**
519: * Creates a new XML Schema element of the given local name
520: * and insert it as the first child of the given parent node.
521: *
522: * @return
523: * Newly create element.
524: */
525: private Element insertXMLSchemaElement(Element parent,
526: String localName) {
527: // use the same prefix as the parent node to avoid modifying
528: // the namespace binding.
529: String qname = parent.getTagName();
530: int idx = qname.indexOf(':');
531: if (idx == -1)
532: qname = localName;
533: else
534: qname = qname.substring(0, idx + 1) + localName;
535:
536: Element child = parent.getOwnerDocument().createElementNS(
537: Constants.NS_XSD, qname);
538:
539: NodeList children = parent.getChildNodes();
540:
541: if (children.getLength() == 0)
542: parent.appendChild(child);
543: else
544: parent.insertBefore(child, children.item(0));
545:
546: return child;
547: }
548:
549: private Element insertJAXWSBindingsElement(Element parent,
550: String localName) {
551: String qname = "JAXWS:" + localName;
552:
553: Element child = parent.getOwnerDocument().createElementNS(
554: JAXWSBindingsConstants.NS_JAXWS_BINDINGS, qname);
555:
556: NodeList children = parent.getChildNodes();
557:
558: if (children.getLength() == 0)
559: parent.appendChild(child);
560: else
561: parent.insertBefore(child, children.item(0));
562:
563: return child;
564: }
565:
566: private String getSystemId(Document doc) {
567: for (Map.Entry<String, Document> e : jaxwsBindings.entrySet()) {
568: if (e.getValue() == doc)
569: return e.getKey();
570: }
571: return null;
572: }
573:
574: protected void warn(Localizable msg) {
575: env.warn(msg);
576: }
577:
578: protected void error(String key, Object[] args) {
579: env.error(messageFactory.getMessage(key, args));
580: }
581:
582: protected void warn(String key) {
583: env.warn(messageFactory.getMessage(key));
584: }
585:
586: protected void warn(String key, Object[] args) {
587: env.warn(messageFactory.getMessage(key, args));
588: }
589:
590: protected void info(String key) {
591: env.info(messageFactory.getMessage(key));
592: }
593:
594: protected void info(String key, String arg) {
595: env.info(messageFactory.getMessage(key, arg));
596: }
597:
598: }
|