blob: 95b60742ff50a239978c9bd9420e50f87d423d41 [file] [log] [blame]
/****************************************************************************
*
* Copyright (c) 2008-2011, EBM WebSourcing
*
* This source code is available under agreement available at
* http://www.petalslink.com/legal/licenses/petals-studio
*
* You should have received a copy of the agreement along with this program.
* If not, write to EBM WebSourcing (4, rue Amelie - 31200 Toulouse, France).
*
*****************************************************************************/
package org.eclipse.bpel.common.wsdl.importhelpers;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
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.net.URLConnection;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.eclipse.bpel.common.wsdl.SoaWsdlCommonPlugin;
import org.eclipse.bpel.common.wsdl.helpers.UriAndUrlHelper;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
/**
* A class that can import a WSDL (1.1 and 2.0) and all its dependencies (XSD, XML, WSDL).
* @author Vincent Zurczak - EBM WebSourcing
*/
public class WsdlImportHelper {
public final static String RELOCATED_DIRECTORY = "relocated";
public final static String WSDL20_NAMESPACE = "http://www.w3.org/ns/wsdl";
public final static String WSDL11_NAMESPACE = "http://schemas.xmlsoap.org/wsdl/";
public final static String SCHEMA_NAMESPACE = "http://www.w3.org/2001/XMLSchema";
public final static String XSI_NAMESPACE = "http://http://www.w3.org/2001/XMLSchema-instance";
private final DocumentBuilder documentBuilder;
/**
* Constructor.
* @throws ParserConfigurationException if a parser could not be created
*/
public WsdlImportHelper() throws ParserConfigurationException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware( true );
this.documentBuilder = factory.newDocumentBuilder();
}
/**
* Imports a WSDL or a XSD will all its dependencies.
* @param targetDirectory
* @param wsdlUris
* @return
* @throws IOException if a resource could not be read or imported
* @throws IllegalArgumentException if an import could not be resolved into an URI
* @throws URISyntaxException if an import could not be resolved into an URI
* @throws SAXException if an import could not be parsed
*/
public Map<String,File> importWsdlOrXsdAndDependencies( File targetDirectory, String... wsdlUris )
throws IllegalArgumentException, IOException, URISyntaxException, SAXException {
// Analyze the resources
Map<String,WsdlImportBean> resourceUriToWIBean = new HashMap<String,WsdlImportBean> ();
analyzeResources( resourceUriToWIBean, null, wsdlUris );
// Now, we have to find the target path and solve conflicts in target files
Set<File> targetFiles = new HashSet<File> ();
IPath rootPath = new Path( targetDirectory.getAbsolutePath());
for( Map.Entry<String,WsdlImportBean> entry : resourceUriToWIBean.entrySet()) {
// Get the real relative file path
String relativeFilePath = entry.getValue().getRelativePathToTargetDirectory();
if( relativeFilePath.length() == 0 )
relativeFilePath = UriAndUrlHelper.extractFileName( entry.getKey());
// Imported files which are above the target directory will be put inside a specific directory
IPath path = rootPath.append( relativeFilePath );
if( ! rootPath.isPrefixOf( path ))
relativeFilePath = RELOCATED_DIRECTORY + "/" + UriAndUrlHelper.extractFileName( entry.getKey());
// Prevent conflicts with existing files and files to be written
File targetFile;
String newRelativeFilePath = relativeFilePath;
int cpt = 0;
while(( targetFile = new File( targetDirectory, newRelativeFilePath )).exists()
|| targetFiles.contains( targetFile )) {
newRelativeFilePath = insertSuffixBeforeFileExtension( relativeFilePath, "_" + cpt );
cpt ++;
}
entry.getValue().setRelativePathToTargetDirectory( newRelativeFilePath );
targetFiles.add( targetFile );
}
// Last steps:
// Resolve the final target files
// And update the import declarations in the files
// And import the file
Map<String,File> originUriToTargetFile = new HashMap<String,File> ();
for( WsdlImportBean bean : resourceUriToWIBean.values()) {
File targetFile = new File( targetDirectory, bean.getRelativePathToTargetDirectory());
URI uri = UriAndUrlHelper.urlToUri( bean.getOriginUri());
StringBuilder fileContent = new StringBuilder( readResourceContent( uri ));
for( Map.Entry<String,String> entry : bean.getImportDeclarationToImportFullUri().entrySet()) {
WsdlImportBean importBean = resourceUriToWIBean.get( entry.getValue());
File importFile = new File( targetDirectory, importBean.getRelativePathToTargetDirectory());
String relLoc = getRelativeLocationToFile( targetFile, importFile );
// Make the replacement - XSD imports are more complicated
String pattern = "(\\s|\")" + Pattern.quote( entry.getKey()) + "(\\s|\")";
Matcher matcher = Pattern.compile( pattern, Pattern.MULTILINE | Pattern.DOTALL ).matcher( fileContent.toString());
while( matcher.find()) {
int start = matcher.start() + 1; // Skip the white space or double quote
int end = matcher.end() - 1; // Skip the white space or double quote
fileContent.replace( start, end, relLoc );
}
}
if( ! targetFile.getParentFile().exists()
&& ! targetFile.getParentFile().mkdirs())
throw new IOException( "Could not create " + targetFile.getParentFile());
OutputStream os = new FileOutputStream( targetFile );
ByteArrayInputStream is;
try {
is = new ByteArrayInputStream( fileContent.toString().getBytes( "UTF-8" ));
} catch( UnsupportedEncodingException e ) {
is = new ByteArrayInputStream( fileContent.toString().getBytes());
}
copyStream( is, os );
os.close ();
originUriToTargetFile.put( bean.getOriginUri(), targetFile );
}
return originUriToTargetFile;
}
/**
* Analyzes a set of resources and gets all the import declarations.
* @param wsdlUris a set of URIs to WSDL or XML schemas
* @param referentUri the URI to use to determine the relative location of the target file
* @param resourceUriToWIBean a map (key = resource full URI, value = {@link WsdlImportBean})
* @throws URISyntaxException if an import could not be resolved to an URI
* @throws SAXException if an import could not be parsed
* @throws IOException if an import could not be read
*/
private void analyzeResources( Map<String,WsdlImportBean> resourceUriToWIBean, URI referentUri, String... wsdlUris )
throws URISyntaxException, IOException, SAXException {
for( String wsdlUri : wsdlUris ) {
// Original URI: add it to the root
if( ! resourceUriToWIBean.containsKey( wsdlUri )) {
WsdlImportBean bean = new WsdlImportBean();
bean.setOriginUri( wsdlUri );
bean.setRelativePathToTargetDirectory( "" );
resourceUriToWIBean.put( wsdlUri, bean );
}
// Now, parse it and get its dependencies
WsdlImportBean bean = resourceUriToWIBean.get( wsdlUri );
bean.setAnalyzed( true );
URI uri = UriAndUrlHelper.urlToUri( wsdlUri );
referentUri = referentUri != null ? referentUri : uri;
Set<String> brutImports = findImports( uri );
for( String brutImport : brutImports ) {
// Absolute URI: we will put them at the same level than the original URI
URI importFullUri = null;
String importFullUriAsString = null;
if( UriAndUrlHelper.isAbsoluteUri( brutImport ))
importFullUriAsString = brutImport;
else {
importFullUri = UriAndUrlHelper.buildNewURI( uri, brutImport );
importFullUriAsString = importFullUri.toString();
}
// Keep a trace of the import declaration
bean.registerImport( brutImport, importFullUriAsString );
// Remember the import for further processing
if( ! resourceUriToWIBean.containsKey( importFullUriAsString )) {
WsdlImportBean childBean = new WsdlImportBean();
childBean.setOriginUri( importFullUriAsString );
URI relativeUri = UriAndUrlHelper.getRelativeLocationToUri( referentUri, importFullUri );
String relPath = relativeUri.toString();
if( UriAndUrlHelper.isAbsoluteUri( relPath ))
relPath = "";
childBean.setRelativePathToTargetDirectory( relPath );
resourceUriToWIBean.put( importFullUriAsString, childBean );
}
// And we need to process recursively all the new imports
WsdlImportBean childBean = resourceUriToWIBean.get( importFullUriAsString );
if( ! childBean.wasAnalyzed())
analyzeResources( resourceUriToWIBean, referentUri, importFullUriAsString );
}
}
}
/**
* Finds all the import declarations in a given document.
* @param referencerUri the URI of the XML file
* @return a non-list of import declarations (as found in the document)
* @throws IOException if an import could not be read
* @throws SAXException if an import could not be parsed
*/
private Set<String> findImports( final URI referencerUri ) throws IOException, SAXException {
Set<String> result = new HashSet<String> ();
// Parse the XML document
URLConnection connection = referencerUri.toURL().openConnection();
InputStream is = connection.getInputStream();
Document referencerDocument = this.documentBuilder.parse( is );
// Check the root element is a valid one...
Element docRoot = referencerDocument.getDocumentElement();
String rootNs = docRoot.getNamespaceURI();
String rootName = docRoot.getLocalName();
int index = rootName.lastIndexOf( ':' );
if( index != -1 )
rootName = rootName.substring( ++ index );
// ... and if so, get its import declarations
if( "definitions".equals( rootName ) && WSDL11_NAMESPACE.equals( rootNs )
|| "description".equals( rootName ) && WSDL20_NAMESPACE.equals( rootNs )
|| "schema".equals( rootName ) && SCHEMA_NAMESPACE.equals( rootNs )) {
result.addAll( findImports( docRoot ));
}
return result;
}
/**
* Gets all the import declarations from a XML document (XSD, WSDL).
* @param docRoot the root element
* @return a non-null list of import locations, as defined in the document
*/
private Set<String> findImports( Element docRoot ) {
// Get the imports
List<NodeList> importElements = new ArrayList<NodeList> ();
importElements.add( docRoot.getElementsByTagNameNS( WSDL11_NAMESPACE, "import" ));
importElements.add( docRoot.getElementsByTagNameNS( WSDL20_NAMESPACE, "import" ));
importElements.add( docRoot.getElementsByTagNameNS( WSDL20_NAMESPACE, "include" ));
importElements.add( docRoot.getElementsByTagNameNS( SCHEMA_NAMESPACE, "import" ));
importElements.add( docRoot.getElementsByTagNameNS( SCHEMA_NAMESPACE, "include" ));
// Now, get the import locations
Set<String> importLocations = new HashSet<String> ();
for( NodeList nodeList : importElements ) {
for( int i=0; i<nodeList.getLength(); i++ ) {
Element importElement = (Element) nodeList.item( i );
String location = importElement.hasAttribute( "location" ) ? importElement.getAttribute( "location" ) : null;
if( location == null ) {
location = importElement.getAttribute( "schemaLocation" );
int index = location.lastIndexOf( ' ' );
if( index != -1 )
location = location.substring( ++ index );
}
if( location != null )
importLocations.add( location );
else
SoaWsdlCommonPlugin.log( "An import location could not be found.", IStatus.WARNING );
}
}
return importLocations;
}
/**
* Return the relative position of <code>file</code> with respect to originFile.
* <p>
* Legacy and more complete than {@link #getBasicRelativePath(File, File)}.
* </p>
*
* @param originFile the absolute file which acts as the <i>origin</i>.
* @param file the file whose relative path must be computed with respect to originFile.
* @return the relative path of <code>file</code> with respect to originFile.
* @see UriUtils#getRelativeLocationToUri(URI, URI)
*/
private static String getRelativeLocationToFile( File originFile, File file ) {
String result = file.getAbsolutePath();
try {
URI originUri = UriAndUrlHelper.urlToUri( originFile.toURI().toURL());
URI fileUri = UriAndUrlHelper.urlToUri( file.toURI().toURL());
result = UriAndUrlHelper.getRelativeLocationToUri( originUri, fileUri ).toString();
} catch( MalformedURLException e ) {
SoaWsdlCommonPlugin.log( e, IStatus.ERROR );
}
return result;
}
/**
* Reads the content of a resource and returns it as a string.
* <p>
* The content is tried to be read with UTF-8 encoding.
* If it fails, the default system encoding is used.
* </p>
*
* @param uri the URI of the resource whose content must be loaded
* @return the resource's content
* @throws IOException if the resource's content could not be read
*/
public static String readResourceContent( URI uri ) throws IOException {
String result = null;
ByteArrayOutputStream os = new ByteArrayOutputStream();
InputStream is = null;
try {
is = uri.toURL().openStream();
copyStream( is, os );
} finally {
if( is != null )
is.close();
}
try {
result = os.toString( "UTF-8" );
} catch( UnsupportedEncodingException e ) {
result = os.toString();
}
return result;
}
/**
* Copies the content from in into os.
* <p>
* Neither <i>in</i> nor <i>os</i> are closed by this method.<br />
* They must be explicitly closed after this method is called.
* </p>
*
* @param in
* @param os
* @throws IOException
*/
private static void copyStream( InputStream in, OutputStream os ) throws IOException {
byte[] buf = new byte[ 1024 ];
int len;
while((len = in.read( buf )) > 0) {
os.write( buf, 0, len );
}
}
/**
* Inserts a suffix before the file extension in a given string.
* @param string a string, file name or file path
* @param suffix a suffix to insert before the file extension
* @return a new string
*/
private static String insertSuffixBeforeFileExtension( String string, String suffix ) {
int index = string.lastIndexOf( '.' );
String result;
if( index == -1 )
result = string + suffix;
else
result = string.substring( 0, index ) + suffix + string.substring( index );
return result;
}
}