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.xml.ws.mex.client;
037:
038: import java.io.IOException;
039: import java.io.InputStream;
040: import java.util.ArrayList;
041: import java.util.HashMap;
042: import java.util.List;
043: import java.util.Map;
044: import java.util.logging.Level;
045: import java.util.logging.Logger;
046:
047: import javax.xml.transform.Source;
048: import javax.xml.transform.Transformer;
049: import javax.xml.transform.TransformerException;
050: import javax.xml.transform.TransformerFactory;
051: import javax.xml.transform.dom.DOMResult;
052: import javax.xml.transform.dom.DOMSource;
053: import javax.xml.transform.stream.StreamSource;
054: import javax.xml.ws.WebServiceException;
055: import org.w3c.dom.Attr;
056: import org.w3c.dom.DOMException;
057: import org.w3c.dom.NamedNodeMap;
058:
059: import org.w3c.dom.Node;
060: import org.w3c.dom.NodeList;
061:
062: import com.sun.xml.ws.api.wsdl.parser.ServiceDescriptor;
063: import com.sun.xml.ws.mex.MessagesMessages;
064: import com.sun.xml.ws.mex.client.schema.Metadata;
065: import com.sun.xml.ws.mex.client.schema.MetadataReference;
066: import com.sun.xml.ws.mex.client.schema.MetadataSection;
067:
068: import static com.sun.xml.ws.mex.MetadataConstants.SCHEMA_DIALECT;
069: import static com.sun.xml.ws.mex.MetadataConstants.WSDL_DIALECT;
070:
071: /**
072: * This class is used by the JAX-WS code when it needs to retrieve
073: * metadata from an endpoint using mex. An address is passed into
074: * the MetadataResolverImpl class, which creates a service
075: * descriptor and returns it.
076: * <P>
077: * Because wsdl and schema import@location attributes are removed
078: * from the data when empty, this class will add them back in
079: * for wsdl imports. The value that is used for the attribute
080: * matches the systemId of the Source that contains the imported
081: * wsdl (which may be different from the target namespace of the
082: * wsdl).
083: */
084: public class ServiceDescriptorImpl extends ServiceDescriptor {
085:
086: private final List<Source> wsdls;
087: private final List<Source> schemas;
088:
089: // holds nodes that are missing location attributes
090: private final List<Node> importsToPatch;
091:
092: // holds sysId for wsdls, key is wsdl targetNamespace
093: private final Map<String, String> nsToSysIdMap;
094:
095: private static final String LOCATION = "location";
096: private static final String NAMESPACE = "namespace";
097:
098: private static final Logger logger = Logger
099: .getLogger(ServiceDescriptorImpl.class.getName());
100:
101: /**
102: * The ServiceDescriptorImpl constructor does the work of
103: * parsing the data in the Metadata object.
104: */
105: public ServiceDescriptorImpl(Metadata mData) {
106: wsdls = new ArrayList<Source>();
107: schemas = new ArrayList<Source>();
108: importsToPatch = new ArrayList<Node>();
109: nsToSysIdMap = new HashMap<String, String>();
110: populateLists(mData);
111: if (!importsToPatch.isEmpty()) {
112: patchImports();
113: }
114: }
115:
116: /*
117: * This will be called recursively for metadata sections
118: * that contain metadata references. A metadata section can
119: * contain the xml of metadata itself (the default case), a
120: * metadata reference that needs to be retrieved, or a
121: * mex location which can be retrieved with http GET call.
122: */
123: private void populateLists(final Metadata mData) {
124: for (MetadataSection section : mData.getMetadataSection()) {
125: if (section.getMetadataReference() != null) {
126: handleReference(section);
127: } else if (section.getLocation() != null) {
128: handleLocation(section);
129: } else {
130: handleXml(section);
131: }
132: }
133: }
134:
135: /*
136: * This is the normal case where a metadata section contains
137: * xml representing a wsdl, schema, etc.
138: */
139: private void handleXml(final MetadataSection section) {
140: final String dialect = section.getDialect();
141: final String identifier = section.getIdentifier();
142: if (dialect.equals(WSDL_DIALECT)) {
143: wsdls.add(createSource(section, identifier));
144: } else if (dialect.equals(SCHEMA_DIALECT)) {
145: schemas.add(createSource(section, identifier));
146: } else {
147: logger.warning(MessagesMessages
148: .MEX_0002_UNKNOWN_DIALECT_WITH_ID(dialect,
149: identifier));
150: }
151: }
152:
153: /*
154: * If the metadata section contains a metadata reference,
155: * retrieve the new metadata and add it to the lists. This
156: * method recursively calls the the populateLists method.
157: */
158: private void handleReference(final MetadataSection section) {
159: final MetadataReference ref = section.getMetadataReference();
160: populateLists(new MetadataClient().retrieveMetadata(ref));
161: }
162:
163: /*
164: * A mex location is simply the url of a document that can
165: * be retrieved with an http GET call.
166: */
167: private void handleLocation(final MetadataSection section) {
168: final String location = section.getLocation();
169: final String dialect = section.getDialect();
170: final String identifier = section.getIdentifier();
171: if (dialect.equals(WSDL_DIALECT)) {
172: wsdls.add(getSourceFromLocation(location, identifier));
173: } else if (dialect.equals(SCHEMA_DIALECT)) {
174: schemas.add(getSourceFromLocation(location, identifier));
175: } else {
176: logger.warning(MessagesMessages
177: .MEX_0002_UNKNOWN_DIALECT_WITH_ID(dialect,
178: identifier));
179: }
180: }
181:
182: public List<Source> getWSDLs() {
183: return wsdls;
184: }
185:
186: public List<Source> getSchemas() {
187: return schemas;
188: }
189:
190: /*
191: * Helper method used by handleXml() to turn the xml DOM nodes
192: * into Sources objects. This method is also responsible for
193: * adding data to the nsToSysIdMap map for wsdl sections in
194: * case there are wsdl:import elements that need to be patched.
195: */
196: private Source createSource(final MetadataSection section,
197: final String identifier) {
198:
199: final Node node = (Node) section.getAny();
200: String sysId = identifier;
201: if (section.getDialect().equals(WSDL_DIALECT)) {
202: final String targetNamespace = getNamespaceFromNode(node);
203: if (sysId == null) {
204: sysId = targetNamespace;
205: }
206: nsToSysIdMap.put(targetNamespace, sysId);
207: checkWsdlImports(node);
208: } else {
209: if (sysId == null) {
210: sysId = getNamespaceFromNode(node);
211: }
212: }
213: final Source source = new DOMSource(node);
214: source.setSystemId(sysId);
215: return source;
216: }
217:
218: /*
219: * Turn the address of a document into a source. The document
220: * referred to in a mex location element must be retrievable
221: * with an HTTP GET call.
222: */
223: private Source getSourceFromLocation(final String address,
224: final String identifier) {
225:
226: try {
227: final HttpPoster poster = new HttpPoster();
228: final InputStream response = poster.makeGetCall(address);
229: if (identifier != null) {
230: final StreamSource source = new StreamSource(response);
231: source.setSystemId(identifier);
232: return source;
233: }
234: return parseAndConvertStream(address, response);
235: } catch (IOException ioe) {
236: final String exceptionMessage = MessagesMessages
237: .MEX_0014_RETRIEVAL_FROM_ADDRESS_FAILURE(address);
238: logger.log(Level.SEVERE, exceptionMessage, ioe);
239: throw new WebServiceException(exceptionMessage, ioe);
240: }
241: }
242:
243: /*
244: * This method used when metadata section did not include
245: * an identifier. The node passed in must be a wsdl:definitions
246: * or an xsd:schema node.
247: */
248: private String getNamespaceFromNode(final Node node) {
249: final Node namespace = node.getAttributes().getNamedItem(
250: "targetNamespace");
251: if (namespace == null) {
252: // bug in the server? want to avoid NPE if so
253: logger
254: .warning(MessagesMessages
255: .MEX_0003_UNKNOWN_WSDL_NAMESPACE(node
256: .getNodeName()));
257: return null;
258: }
259: return namespace.getNodeValue();
260: }
261:
262: /*
263: * This method will check the wsdl for import nodes
264: * that have no location attribute and add them to
265: * the list to be patched.
266: */
267: private void checkWsdlImports(final Node wsdl) {
268: final NodeList kids = wsdl.getChildNodes();
269: for (int i = 0; i < kids.getLength(); i++) {
270: final Node importNode = kids.item(i);
271: if (importNode.getLocalName() != null
272: && importNode.getLocalName().equals("import")) {
273:
274: final Node location = importNode.getAttributes()
275: .getNamedItem(LOCATION);
276: if (location == null) {
277: importsToPatch.add(importNode);
278: }
279: }
280: }
281: }
282:
283: /*
284: * This method used when metadata location section did not include
285: * an identifier. Since we need to read some of this information
286: * to get the namespace and then return it to be read again by
287: * jax-ws, we cannot use the InputStream itself (cannot call
288: * mark/reset on InputStream).
289: *
290: * It is not expected that a wsdl retrieved with mex location
291: * will import another wsdl in the mex response, so this
292: * wsdl is not checked for empty wsdl import locations.
293: */
294: private Source parseAndConvertStream(final String address,
295: final InputStream stream) {
296:
297: try {
298: final Transformer xFormer = TransformerFactory
299: .newInstance().newTransformer();
300: Source source = new StreamSource(stream);
301: final DOMResult result = new DOMResult();
302: xFormer.transform(source, result);
303: final Node wsdlDoc = result.getNode();
304: source = new DOMSource(wsdlDoc);
305: source.setSystemId(getNamespaceFromNode(wsdlDoc
306: .getFirstChild()));
307: return source;
308: } catch (TransformerException te) {
309: final String exceptionMessage = MessagesMessages
310: .MEX_0004_TRANSFORMING_FAILURE(address);
311: logger.log(Level.SEVERE, exceptionMessage, te);
312: throw new WebServiceException(exceptionMessage, te);
313: }
314: }
315:
316: /*
317: * For wsdl:import statements that have no location attribute,
318: * add a location with the value of the sysId of the imported
319: * wsdl.
320: */
321: private void patchImports() throws DOMException {
322: for (Node importNode : importsToPatch) {
323: final NamedNodeMap atts = importNode.getAttributes();
324: final String targetNamespace = atts.getNamedItem(NAMESPACE)
325: .getNodeValue();
326: final String sysId = nsToSysIdMap.get(targetNamespace);
327: if (sysId == null) {
328: logger
329: .warning(MessagesMessages
330: .MEX_0005_WSDL_NOT_FOUND_WITH_NAMESPACE(targetNamespace));
331: continue;
332: }
333: final Attr locationAtt = importNode.getOwnerDocument()
334: .createAttribute(LOCATION);
335: locationAtt.setValue(sysId);
336: atts.setNamedItem(locationAtt);
337: }
338: }
339:
340: }
|