| /******************************************************************************* |
| * Copyright (c) 2007, 2009 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * yyyymmdd bug Email and other contact information |
| * -------- -------- ----------------------------------------------------------- |
| * 20070125 171071 makandre@ca.ibm.com - Andrew Mak, Create public utility method for copying WSDL files |
| * 20070409 181635 makandre@ca.ibm.com - Andrew Mak, WSDLCopier utility should create target folder |
| * 20071205 211262 ericdp@ca.ibm.com - Eric Peters, CopyWSDLTreeCommand fails to copy ?wsdl |
| * 20080324 215552 makandre@ca.ibm.com - Andrew Mak, WSDLCopier expects not encoded URI |
| * 20080501 229728 makandre@ca.ibm.com - Andrew Mak, uppercase .WSDL cannot be found by the Web Service Client wizard |
| * 20090128 262639 ericdp@ca.ibm.com - Eric D. Peters, WSDLCopier gives NPE in projects with spaces |
| * 20090204 262913 ericdp@ca.ibm.com - Eric D. Peters, Top-down WS fails when .wsdl imports .xsd or .wsdl which resides in folder containing URI encoded chars like " ", ")" |
| * 20091106 294486 yenlu@ca.ib,.com - Yen Lu, WSDLCopier may not write WSDLs and XSDs with the correct XML encoding |
| *******************************************************************************/ |
| |
| package org.eclipse.wst.ws.internal.util; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.io.UnsupportedEncodingException; |
| import java.net.MalformedURLException; |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.util.Iterator; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import javax.wsdl.Definition; |
| import javax.wsdl.Import; |
| import javax.wsdl.Types; |
| import javax.wsdl.WSDLException; |
| import javax.wsdl.extensions.ExtensibilityElement; |
| import javax.wsdl.extensions.UnknownExtensibilityElement; |
| import javax.wsdl.extensions.schema.Schema; |
| import javax.xml.transform.OutputKeys; |
| import javax.xml.transform.Transformer; |
| import javax.xml.transform.TransformerConfigurationException; |
| import javax.xml.transform.TransformerException; |
| import javax.xml.transform.TransformerFactory; |
| import javax.xml.transform.dom.DOMSource; |
| import javax.xml.transform.stream.StreamResult; |
| |
| import org.eclipse.core.resources.IWorkspaceRunnable; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.Path; |
| import org.eclipse.osgi.util.NLS; |
| import org.eclipse.wst.command.internal.env.core.common.StatusUtils; |
| import org.eclipse.wst.common.environment.EnvironmentService; |
| import org.eclipse.wst.common.environment.uri.IURIScheme; |
| import org.eclipse.wst.common.environment.uri.SimpleURIFactory; |
| import org.eclipse.wst.common.environment.uri.URIException; |
| import org.eclipse.wst.common.uriresolver.internal.util.URIEncoder; |
| import org.eclipse.wst.ws.internal.WstWSPluginMessages; |
| import org.eclipse.wst.ws.internal.parser.discovery.NetUtils; |
| import org.eclipse.wst.ws.internal.parser.wsil.WWWAuthenticationException; |
| import org.eclipse.wst.ws.internal.parser.wsil.WebServicesParser; |
| import org.eclipse.wst.wsdl.XSDSchemaExtensibilityElement; |
| import org.eclipse.wst.wsdl.internal.impl.DefinitionImpl; |
| import org.eclipse.wst.wsdl.util.WSDLResourceImpl; |
| import org.eclipse.xsd.XSDSchema; |
| import org.eclipse.xsd.XSDSchemaDirective; |
| import org.eclipse.xsd.impl.XSDSchemaImpl; |
| import org.eclipse.xsd.util.XSDParser; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| |
| /** |
| * <p>This is a utility class that can be used to copy a WSDL file and its relative imports |
| * (both wsdl:import and xsd:import) to a folder in the filesystem. The code will analyze |
| * the directory structure and does the following: |
| * <ol> |
| * <li>find all files that needs to be copied and tag them as such.</li> |
| * <li>find the common parent directory of all the files that are tagged; this is the |
| * root of the directory structure.</li> |
| * </ol> |
| * During the copy, the root of the directory structure is be mapped to the |
| * specified target folder and all tagged files will be duplicated under it.</p> |
| * |
| * <p>If the WSDL source and the target location are the same, this utility only guarantees |
| * that the starting WSDL file is "re-copied" onto itself (since the WSDL definition could |
| * have been altered). No guarantee is made for the other files that the WSDL file imports.</p> |
| * |
| * <p>Note that WSDLCopier does not do a direct file copy. The source files are read into |
| * a model and then written out to the target folder, hence the output file might not be |
| * syntactically identical to the source file but they will be structurally identical.</p> |
| * |
| * <p>Since this utility implements IWorkspaceRunnable, it can be run by the workspace as |
| * an atomic workspace operation.</p> |
| */ |
| public class WSDLCopier implements IWorkspaceRunnable { |
| |
| private static final String XSD = ".xsd"; |
| |
| /** |
| * Helper class holding information about the files we need to copy |
| */ |
| private class XMLObjectInfo { |
| |
| public IPath path; |
| public Object content; |
| public String encoding; |
| |
| /** |
| * Constructor. |
| * |
| * @param path The path of the source file. |
| * @param content A representation of the source file (either wsdl or xsd) |
| */ |
| public XMLObjectInfo(IPath path, Object content) { |
| this.path = path.setDevice(null); // we want a device independent path |
| this.content = content; |
| } |
| |
| /** |
| * Constructor. |
| * |
| * @param path The path of the source file. |
| * @param content A representation of the source file (either wsdl or xsd) |
| * @param encoding The encoding of the content. |
| */ |
| public XMLObjectInfo(IPath path, Object content, String encoding) |
| { |
| this(path, content); |
| this.encoding = encoding; |
| } |
| } |
| |
| private WebServicesParser parser = null; // parser to parse the wsdl and xsd files |
| |
| private URI sourceURI = URI.create(""); // uri of the starting wsdl |
| private Definition definition = null; // representation of the starting wsdl |
| |
| private URI targetFolderURI = null; // uri of the folder to copy to |
| private String targetFilename = null; // optional new filename for the starting wsdl |
| |
| private IPath pathPrefix = null; // the shortest common path of all files we need to copy |
| private IPath wsdlRelPath = null; // relative path of the starting wsdl to the target folder |
| |
| private Map xmlObjectInfos = new LinkedHashMap(); // table to store info about the files we need to copy |
| |
| private SimpleURIFactory uriFactory = null; |
| |
| /** |
| * Default constructor. A new WebServicesParser is constructed for use. |
| */ |
| public WSDLCopier() { |
| this(null); |
| } |
| |
| /** |
| * Constructor. The given WebServicesParser will be used. |
| * |
| * @param parser A WebServicesParser for WSDLCopier to use. If null, a new one will be constructed. |
| */ |
| public WSDLCopier(WebServicesParser parser) { |
| if (parser == null) |
| this.parser = new WebServicesParser(); |
| else |
| this.parser = parser; |
| |
| IURIScheme eclipseScheme = EnvironmentService.getEclipseScheme(); |
| IURIScheme fileScheme = EnvironmentService.getFileScheme(); |
| uriFactory = new SimpleURIFactory(); |
| uriFactory.registerScheme("platform", eclipseScheme); |
| uriFactory.registerScheme("file", fileScheme); |
| } |
| |
| /** |
| * Sets the URI of the starting wsdl document. The URI must be an absolute URI |
| * with a valid protocol. For local files, both file:/ and platform:/resource |
| * protocol are acceptable. |
| * |
| * @param uri The URI of the starting wsdl document. |
| * |
| * @deprecated |
| */ |
| public void setSourceURI(String uri) { |
| setSourceURI(uri, null); |
| } |
| |
| /** |
| * Sets the URI of the starting wsdl document. The URI must be an absolute URI |
| * with a valid protocol. For local files, both file:/ and platform:/resource |
| * protocol are acceptable. |
| * |
| * @param uri The URI of the starting wsdl document. |
| */ |
| public void setSourceURI(URI uri) { |
| setSourceURI(uri, null); |
| } |
| |
| /** |
| * Same as setSourceURI(String uri) version, except that an already parsed |
| * representation of the wsdl document can be passed to this method to avoid |
| * having to parse it a second time. |
| * |
| * @param uri The URI of the starting wsdl document. |
| * @param definition A parsed representation of the starting wsdl document. |
| * |
| * @deprecated |
| */ |
| public void setSourceURI(String uri, Definition definition) { |
| if (uri != null) |
| uri = uri.replace('\\', '/'); |
| URI uriObj = null; |
| try { |
| try { |
| uriObj = new URI(uri); |
| } |
| catch (URISyntaxException e) { |
| uriObj = URI.create(URIEncoder.encode(uri, "UTF-8")); |
| } |
| setSourceURI(uriObj, definition); |
| } |
| catch (UnsupportedEncodingException e) { |
| // ignore, default URI("") is used, operation will be a no-op |
| } |
| } |
| |
| /** |
| * Same as setSourceURI(String uri) version, except that an already parsed |
| * representation of the wsdl document can be passed to this method to avoid |
| * having to parse it a second time. |
| * |
| * @param uri The URI of the starting wsdl document. |
| * @param definition A parsed representation of the starting wsdl document. |
| */ |
| public void setSourceURI(URI uri, Definition definition) { |
| sourceURI = uri; |
| this.definition = definition; |
| } |
| |
| /** |
| * Specify the target folder URI for WSDLCopier to copy the wsdl to. The URI must be |
| * an absolute URI with a valid protocol. Both file:/ and platform:/resource protocol |
| * are acceptable. The entire directory structure with all the files that the wsdl |
| * imports will be duplicated under this folder. If the target folder does not exist, |
| * it will be created. |
| * |
| * @param uri The target folder URI where the wsdl structure is copied to. |
| * |
| * @deprecated |
| */ |
| public void setTargetFolderURI(String uri) { |
| if (uri != null) |
| uri = uri.replace('\\', '/'); |
| URI uriObj = null; |
| try { |
| try { |
| uriObj = new URI(uri); |
| } |
| catch (URISyntaxException e) { |
| uriObj = URI.create(URIEncoder.encode(uri, "UTF-8")); |
| } |
| setTargetFolderURI(uriObj); |
| } |
| catch (UnsupportedEncodingException e) { |
| // ignore, targetFolderURI will be checked for null later |
| } |
| } |
| |
| /** |
| * Specify the target folder URI for WSDLCopier to copy the wsdl to. The URI must be |
| * an absolute URI with a valid protocol. Both file:/ and platform:/resource protocol |
| * are acceptable. The entire directory structure with all the files that the wsdl |
| * imports will be duplicated under this folder. If the target folder does not exist, |
| * it will be created. |
| * |
| * @param uri The target folder URI where the wsdl structure is copied to. |
| */ |
| public void setTargetFolderURI(URI uri) { |
| targetFolderURI = uri; |
| } |
| |
| /** |
| * Optionally set a new filename for the starting wsdl document to use after it is |
| * copied. If not specified, the original name of the wsdl is used. |
| * |
| * @param filename A filename. |
| */ |
| public void setTargetFilename(String filename) { |
| targetFilename = filename; |
| } |
| |
| /* |
| * Compare the path prefix with the path in the info object, |
| * modify the path prefix accordingly. |
| */ |
| private void updatePathPrefix(XMLObjectInfo info) { |
| if (pathPrefix == null) |
| pathPrefix = info.path.removeLastSegments(1); |
| else { |
| int matching = pathPrefix.matchingFirstSegments(info.path); |
| |
| if (matching < pathPrefix.segmentCount()) |
| pathPrefix = pathPrefix.uptoSegment(matching); |
| } |
| } |
| |
| /* |
| * Determine if the given URI is a relative URI. |
| */ |
| private boolean isRelative(String uri) { |
| return uri.indexOf(':') == -1; |
| } |
| |
| /* |
| * Analyze the wsdl document at the given URI, and traverse any relative files that |
| * it imports. Can optionally pass in a parsed Definition if one's available so |
| * we don't have to parse the wsdl again (otherwise just pass in null). |
| */ |
| private void analyzeWSDL(URI uri, Definition definition) throws |
| MalformedURLException, IOException, WSDLException, WWWAuthenticationException { |
| |
| uri = uri.normalize(); |
| |
| // already seen this wsdl, skip |
| if (xmlObjectInfos.containsKey(uri.toString())) |
| return; |
| |
| // need to parse the wsdl ourselves |
| if (definition == null) |
| definition = parser.getWSDLDefinitionVerbose(uri.toString()); |
| |
| // save a reference to the starting wsdl |
| if (this.definition == null) |
| this.definition = definition; |
| |
| IPath path = new Path(uri.getPath()); |
| |
| // a target filename was given, so we need to modify the path with the new name |
| if (definition == this.definition && targetFilename != null) |
| path = path.removeLastSegments(1).append(targetFilename); |
| |
| XMLObjectInfo info = new XMLObjectInfo(path, definition); |
| xmlObjectInfos.put(uri.toString(), info); |
| updatePathPrefix(info); |
| |
| // now look at wsdl imports |
| |
| for (Iterator it = definition.getImports().values().iterator(); it.hasNext();) { |
| |
| List list = (List) it.next(); |
| |
| for (Iterator listIt = list.iterator(); listIt.hasNext();) { |
| |
| Import wsdlImport = (Import) listIt.next(); |
| String wsdlImportLocation = wsdlImport.getLocationURI(); |
| |
| // analyze any relative imports we find |
| if (wsdlImportLocation != null && isRelative(wsdlImportLocation)) { |
| |
| // bad form, importing xsd with wsdl:import, but need to handle |
| if (wsdlImportLocation.toLowerCase().endsWith(XSD)) |
| analyzeXSD(uri.resolve(uriCreate(wsdlImportLocation))); |
| else |
| analyzeWSDL(uri.resolve(uriCreate(wsdlImportLocation)), null); |
| } |
| } |
| } |
| |
| // now look at xsd imports |
| |
| Types types = definition.getTypes(); |
| |
| // there's no wsdl:types, we're done |
| if (types == null) |
| return; |
| |
| for (Iterator it = types.getExtensibilityElements().iterator(); it.hasNext();) { |
| |
| ExtensibilityElement extElement = (ExtensibilityElement) it.next(); |
| Element element; |
| |
| // we'll try to parse any UnknownExtensibilityElements and |
| // XSDSchemaExtensibilityElements into an XSD schema |
| |
| if (extElement instanceof UnknownExtensibilityElement) |
| element = ((UnknownExtensibilityElement) extElement).getElement(); |
| else if (extElement instanceof XSDSchemaExtensibilityElement) |
| element = ((XSDSchemaExtensibilityElement) extElement).getElement(); |
| else if (extElement instanceof Schema) |
| element = ((Schema) extElement).getElement(); |
| else |
| continue; |
| |
| try { |
| XSDSchema xsdSchema = XSDSchemaImpl.createSchema(element); |
| |
| // analyze the inlined schema at the current uri |
| analyzeXSD(uri, xsdSchema); |
| } |
| catch (Exception t) { |
| // ignore any extensibility elements that cannot be parsed into a |
| // XSDSchema instance |
| } |
| } |
| } |
| |
| /* |
| * Analyze the schema at the given URI, traverse its imports and includes. |
| * The schema information is not stored in XMLObjectInfos because it's |
| * either not neccessary (schema inlined in a wsdl) or it has already |
| * been stored. |
| */ |
| private void analyzeXSD(URI uri, XSDSchema schema) { |
| |
| if (schema == null) |
| return; |
| |
| // look at the imports and includes |
| |
| for (Iterator it = schema.getContents().iterator(); it.hasNext();) { |
| |
| Object content = it.next(); |
| |
| if (!(content instanceof XSDSchemaDirective)) |
| continue; |
| |
| XSDSchemaDirective xsdSchemaDirective = (XSDSchemaDirective) content; |
| String xsdSchemaDirectiveLocation = xsdSchemaDirective.getSchemaLocation(); |
| |
| // analyze any relative imports and includes we find |
| if (xsdSchemaDirectiveLocation != null && isRelative(xsdSchemaDirectiveLocation)) |
| try { |
| analyzeXSD(uri.resolve(uriCreate(xsdSchemaDirectiveLocation))); |
| } catch (Exception e) { |
| // ignore any xsd's we cannot resolve |
| } |
| } |
| } |
| |
| /* |
| * Analyze the schema at the given URI, the schema is parsed and stored in |
| * XMLObjectInfos. |
| */ |
| private void analyzeXSD(URI uri) { |
| |
| uri = uri.normalize(); |
| |
| // already seen this xsd, skip it |
| if (xmlObjectInfos.containsKey(uri.toString())) |
| return; |
| |
| XSDSchema xsdSchema = XSDSchemaImpl.getSchemaForSchema(uri.toString()); |
| |
| String encoding = null; |
| // if schema is not cached, parse it |
| if (xsdSchema == null) { |
| XSDParser p = new XSDParser(null); |
| InputStream is = NetUtils.getURLInputStream(uri.toString()); |
| |
| if (is != null) { |
| p.parse(is); |
| xsdSchema = p.getSchema(); |
| encoding = p.getEncoding(); |
| } |
| } |
| else |
| encoding = xsdSchema.getDocument().getXmlEncoding(); |
| |
| if (xsdSchema != null) { |
| |
| if (encoding == null) |
| encoding = "UTF-8"; |
| |
| XMLObjectInfo info = new XMLObjectInfo(new Path(uri.getPath()), xsdSchema, encoding); |
| xmlObjectInfos.put(uri.toString(), info); |
| updatePathPrefix(info); |
| |
| // analyze its imports and includes |
| analyzeXSD(uri, xsdSchema); |
| } |
| } |
| |
| /* |
| * Appends a path to a URI and returns the new combined URI in an array of 2 elements, |
| * where the first element is in decoded form and the second element is in encoded form. |
| */ |
| private String[] appendPathToURI(URI uriObj, IPath path) { |
| |
| String schemeSpecificPart = uriObj.getSchemeSpecificPart(); |
| |
| if (schemeSpecificPart.endsWith("/")) |
| path = path.makeRelative(); |
| else |
| path = path.makeAbsolute(); |
| |
| return new String[] { |
| uriObj.getScheme() + ':' + schemeSpecificPart + path, |
| uriObj.getScheme() + ':' + uriObj.getRawSchemeSpecificPart() + path |
| }; |
| } |
| |
| /* |
| * Writes the wsdl definition to the file at the given path, relative to the target folder. |
| */ |
| private String writeXMLObj(IPath path, Definition definition, IProgressMonitor monitor) throws |
| WSDLException, URIException, IOException, CoreException { |
| |
| String[] targetURI = appendPathToURI(targetFolderURI, path); |
| |
| OutputStream os = uriFactory.newURI(targetURI[0]).getOutputStream(); |
| |
| DefinitionImpl definitionImpl = (DefinitionImpl)definition; |
| WSDLResourceImpl resource = (WSDLResourceImpl) definitionImpl.eResource(); |
| Document document = definitionImpl.getDocument(); |
| resource.serialize(os, document, document.getXmlEncoding()); |
| |
| os.close(); |
| |
| return targetURI[1]; |
| } |
| |
| /* |
| * Writes the xsd schema to the file at the given path, relative to the target folder. |
| */ |
| private String writeXMLObj(IPath path, XSDSchema xsdSchema, String encoding, IProgressMonitor monitor) throws |
| TransformerConfigurationException, TransformerException, URIException, IOException, CoreException { |
| Transformer serializer = TransformerFactory.newInstance().newTransformer(); |
| Document document = xsdSchema.getDocument(); |
| serializer.setOutputProperty(OutputKeys.INDENT, "yes"); |
| serializer.setOutputProperty(OutputKeys.ENCODING, encoding); |
| serializer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); |
| |
| Element e = xsdSchema.getElement(); |
| DOMSource domSource = new DOMSource(e); |
| |
| String[] targetURI = appendPathToURI(targetFolderURI, path); |
| |
| OutputStream os = uriFactory.newURI(targetURI[0]).getOutputStream(); |
| serializer.transform(domSource, new StreamResult(os)); |
| os.close(); |
| |
| return targetURI[1]; |
| } |
| |
| /* |
| * Compares the 2 uris and see if they point to the same file. |
| * We need to convert both uris to file: protocal uris in order to compare. |
| */ |
| private boolean isSameLocation(String uri1, String uri2) { |
| |
| // if either uri is null, we cannot make any meaningful comparison |
| if (uri1 == null || uri2 == null) |
| return false; |
| try { |
| //we have an encoded URI and want to compare if they resolve to the same file on the file system |
| uri1 = UniversalPathTransformer.uriToLocation(uri1); |
| uri2 = UniversalPathTransformer.uriToLocation(uri2); |
| if (uri1 == null || uri2 == null || uri1.length() == 0 || uri2.length() == 0) |
| return false; |
| return uri1.equals(uri2); |
| } catch (Exception e) { |
| return false; |
| } |
| } |
| |
| /** |
| * Executes the copying action. |
| * |
| * @param monitor An optional progress monitor. |
| * @throws CoreException Thrown if the copying is unsuccessful. Possible causes include: |
| * target folder URI was not specified; source URI has incorrect format; problem parsing wsdl |
| * or xsd documents; problem writing to filesystem. |
| */ |
| public void run(IProgressMonitor monitor) throws CoreException { |
| |
| xmlObjectInfos.clear(); |
| |
| // target folder must be set |
| if (targetFolderURI == null) |
| throw new CoreException(StatusUtils.errorStatus(WstWSPluginMessages.MSG_ERROR_TARGET_FOLDER_NOT_SPECIFIED)); |
| |
| try { |
| analyzeWSDL(sourceURI, definition); |
| |
| // begin writing out files |
| |
| Iterator iter = xmlObjectInfos.values().iterator(); |
| |
| while (iter.hasNext()) { |
| XMLObjectInfo info = (XMLObjectInfo) iter.next(); |
| IPath relPath = info.path.removeFirstSegments(pathPrefix.segmentCount()); |
| |
| if (info.content instanceof Definition) { |
| Definition definition = (Definition) info.content; |
| |
| // if this is the starting wsdl, remember its path relative to the target folder |
| if (definition == this.definition) |
| wsdlRelPath = relPath; |
| |
| String targetURI = writeXMLObj(relPath, definition, monitor); |
| |
| if (definition == this.definition && isSameLocation(sourceURI.toString(), targetURI)) |
| return; |
| } |
| else |
| writeXMLObj(relPath, (XSDSchema) info.content, info.encoding, monitor); |
| } |
| } catch (Exception t) { |
| throw new CoreException(StatusUtils.errorStatus( |
| NLS.bind(WstWSPluginMessages.MSG_ERROR_COPY_WSDL, new String[] {sourceURI.toString(), targetFolderURI.toString()}), t)); |
| } |
| } |
| |
| /** |
| * Returns the path of the starting wsdl document, relative to the target folder after |
| * copying completes. |
| * |
| * @return The relative path of the starting wsdl document. |
| */ |
| public IPath getRelativePath() { |
| return wsdlRelPath; |
| } |
| |
| private static java.net.URI uriCreate(String uri) { |
| if (uri != null) |
| uri = uri.replace('\\', '/'); |
| try { |
| try { |
| return java.net.URI.create(uri); |
| } |
| catch (IllegalArgumentException e) { |
| return java.net.URI.create(URIEncoder.encode(uri, "UTF-8")); |
| } |
| } |
| catch (UnsupportedEncodingException e) { |
| return java.net.URI.create(""); |
| } |
| } |
| } |