blob: f9bdff84441d81ac6313ae4fb81bcf4736ad117e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007 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
*******************************************************************************/
package org.eclipse.wst.ws.internal.util;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
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.factory.WSDLFactory;
import javax.wsdl.xml.WSDLWriter;
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.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
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.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.wsdl4j.WSDLFactoryImpl;
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.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 workspace. 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;
/**
* 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;
}
}
private WebServicesParser parser = null; // parser to parse the wsdl and xsd files
private String sourceURI = ""; // uri of the starting wsdl
private Definition definition = null; // representation of the starting wsdl
private IFolder targetFolder = null; // 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
/**
* 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;
}
/**
* 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(String 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.
*/
public void setSourceURI(String uri, Definition definition) {
if (uri != null)
uri = uri.replace('\\', '/');
sourceURI = uri;
this.definition = definition;
}
/**
* Specify the target folder for WSDLCopier to copy the wsdl to. The entire
* directory structure with all the files that the wsdl imports will be duplicated
* under this folder. The target folder must be specified before the copying
* code runs and it the folder must exist.
*
* @param folder The target folder where the wsdl structure is copied to.
*/
public void setTargetFolder(IFolder folder) {
targetFolder = folder;
}
/**
* 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.endsWith(XSD))
analyzeXSD(uri.resolve(wsdlImportLocation));
else
analyzeWSDL(uri.resolve(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
continue;
try {
XSDSchema xsdSchema = XSDSchemaImpl.createSchema(element);
// analyze the inlined schema at the current uri
analyzeXSD(uri, xsdSchema);
}
catch (Throwable 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))
analyzeXSD(uri.resolve(xsdSchemaDirectiveLocation));
}
}
/*
* 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());
// 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();
}
}
if (xsdSchema != null) {
XMLObjectInfo info = new XMLObjectInfo(new Path(uri.getPath()), xsdSchema);
xmlObjectInfos.put(uri.toString(), info);
updatePathPrefix(info);
// analyze its imports and includes
analyzeXSD(uri, xsdSchema);
}
}
/*
* Creates a folder including its parent folders.
*/
private void createFolder(IContainer folder, IProgressMonitor monitor) throws CoreException {
if (!(folder instanceof IFolder) || folder.exists())
return;
createFolder(folder.getParent(), monitor);
((IFolder) folder).create(true, true, monitor);
}
/*
* Write the bytes to the file. The file is created if it does not exist. It is replaced
* if it already exist.
*/
private void writeFile(IFile file, byte[] bytes, IProgressMonitor monitor) throws CoreException {
createFolder(file.getParent(), monitor);
if (file.exists())
file.setContents(new ByteArrayInputStream(bytes), true, true, monitor);
else
file.create(new ByteArrayInputStream(bytes), true, monitor);
}
/*
* Writes the wsdl definition to the file at the given path, relative to the target folder.
*/
private IFile writeXMLObj(IPath path, Definition definition, IProgressMonitor monitor) throws
WSDLException, IOException, CoreException {
WSDLFactory wsdlFactory = new WSDLFactoryImpl();
WSDLWriter wsdlWriter = wsdlFactory.newWSDLWriter();
ByteArrayOutputStream os = new ByteArrayOutputStream();
wsdlWriter.writeWSDL(definition, os);
os.close();
IFile targetFile = targetFolder.getFile(path);
writeFile(targetFile, os.toByteArray(), monitor);
return targetFile;
}
/*
* Writes the xsd schema to the file at the given path, relative to the target folder.
*/
private IFile writeXMLObj(IPath path, XSDSchema xsdSchema, IProgressMonitor monitor) throws
TransformerConfigurationException, TransformerException, IOException, CoreException {
Transformer serializer = TransformerFactory.newInstance().newTransformer();
serializer.setOutputProperty(OutputKeys.INDENT, "yes");
serializer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
serializer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
Element e = xsdSchema.getElement();
DOMSource domSource = new DOMSource(e);
ByteArrayOutputStream os = new ByteArrayOutputStream();
serializer.transform(domSource, new StreamResult(os));
os.close();
IFile targetFile = targetFolder.getFile(path);
writeFile(targetFile, os.toByteArray(), monitor);
return targetFile;
}
/*
* Compares the 2 uris and see if they point to the same file. uri1 could
* be a platform uri, while uri2 is always a filesystem uri. Therefore,
* we need to convert uri1 to filesystem uri in order to compare.
*/
private boolean isSameLocation(URI uri1, URI uri2) {
// if either uri is null, we cannot make any meaningful comparison
if (uri1 == null || uri2 == null)
return false;
return UniversalPathTransformer.toLocation(uri1.toString()).equals(uri2.toString());
}
/**
* Executes the copying action.
*
* @param monitor An optional progress monitor.
* @throws CoreException Thrown if the copying is unsuccessful. Possible causes include:
* target folder was not specified; target folder does not exist; 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 and exist
if (targetFolder == null)
throw new CoreException(StatusUtils.errorStatus(WstWSPluginMessages.MSG_ERROR_TARGET_FOLDER_NOT_SPECIFIED));
else if (!targetFolder.exists()) {
throw new CoreException(StatusUtils.errorStatus(
NLS.bind(WstWSPluginMessages.MSG_ERROR_TARGET_FOLDER_NOT_FOUND, new String[] {targetFolder.toString()})));
}
try {
URI uri = new URI(URIEncoder.encode(sourceURI, "UTF-8"));
analyzeWSDL(uri, 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;
IFile file = writeXMLObj(relPath, definition, monitor);
if (definition == this.definition && isSameLocation(uri, file.getLocationURI()))
return;
}
else
writeXMLObj(relPath, (XSDSchema) info.content, monitor);
}
} catch (Throwable t) {
throw new CoreException(StatusUtils.errorStatus(
NLS.bind(WstWSPluginMessages.MSG_ERROR_COPY_WSDL, new String[] {sourceURI, targetFolder.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;
}
}