org.ops4j.pax.url.mvn.internal.Connection Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of pax-url-mvn Show documentation
Show all versions of pax-url-mvn Show documentation
OPS4J Pax Url - mvn: protocol handler.
Detailed information to be found at http://wiki.ops4j.org/confluence/x/CoA6.
/*
* Copyright 2007 Alin Dreghiciu.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied.
*
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.ops4j.pax.url.mvn.internal;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;
import org.ops4j.lang.NullArgumentException;
import org.ops4j.net.URLUtils;
import org.ops4j.pax.url.maven.commons.MavenConfiguration;
import org.ops4j.pax.url.maven.commons.MavenRepositoryURL;
import org.ops4j.util.xml.XmlUtils;
/**
* An URLConnextion that supports mvn: protocol.
* Syntax:
* mvn:[repository_url!]groupId/artifactId[/version[/type]]
* where:
* - repository_url = an url that points to a maven 2 repository; optional, if not sepecified the repositories are
* resolved based on the repository/localRepository.
* - groupId = group id of maven artifact; mandatory
* - artifactId = artifact id of maven artifact; mandatory
* - version = version of maven artifact; optional, if not specified uses LATEST and will try to resolve the version
* from available maven metadata. If version is a SNAPSHOT version, SNAPSHOT will be resolved from available maven
* metadata
* - type = type of maven artifact; optional, if not specified uses JAR
* Examples:
* mvn:http://repository.ops4j.org/mvn-releases!org.ops4j.pax.runner/runner/0.4.0 - an artifact from an http repository
* mvn:http://user:[email protected]/mvn-releases!org.ops4j.pax.runner/runner/0.4.0 - an artifact from an http
* repository with authentication
* mvn:file://c:/localRepo!org.ops4j.pax.runner/runner/0.4.0 - an artifact from a directory
* mvn:jar:file://c:/repo.zip!/repository!org.ops4j.pax.runner/runner/0.4.0 - an artifact from a zip file
* mvn:org.ops4j.pax.runner/runner/0.4.0 - an artifact that will be resolved based on the configured repositories
*
* The service can be configured in two ways: via configuration admin if available and via framework/system properties
* where the configuration via config admin has priority.
* Service configuration:
* - org.ops4j.pax.url.mvn.settings = the path to settings.xml;
* - org.ops4j.pax.url.mvn.localRepository = the path to local repository directory;
* - org.ops4j.pax.url.mvn.repository = a comma separated list for repositories urls;
* - org.ops4j.pax.url.mvn.certicateCheck = true/false if the SSL certificate check should be done.
* Default false.
*
* @author Alin Dreghiciu
* @since August 10, 2007
*/
public class Connection
extends URLConnection
{
/**
* Logger.
*/
private static final Log LOG = LogFactory.getLog( Connection.class );
/**
* 2 spacess indent;
*/
private static final String Ix2 = " ";
/**
* 4 spacess indent;
*/
private static final String Ix4 = " ";
/**
* Parsed url.
*/
private Parser m_parser;
/**
* Service configuration.
*/
private final MavenConfiguration m_configuration;
/**
* Creates a new connection.
*
* @param url the url; cannot be null.
* @param configuration service configuration; cannot be null
*
* @throws MalformedURLException in case of a malformed url
*/
public Connection( final URL url, final MavenConfiguration configuration )
throws MalformedURLException
{
super( url );
NullArgumentException.validateNotNull( url, "URL cannot be null" );
NullArgumentException.validateNotNull( configuration, "Service configuration" );
m_configuration = configuration;
m_parser = new Parser( url.getPath() );
}
/**
* Does nothing.
*
* @see java.net.URLConnection#connect()
*/
@Override
public void connect()
{
// do nothing
}
/**
* Returns the input stream denoted by the url.
* If the url does not contain a repository the resource is searched in every repository if available, in the order
* provided by the repository setting.
*
* @return the input stream for the resource denoted by url
*
* @throws IOException in case of an exception during accessing the resource
* @see java.net.URLConnection#getInputStream()
*/
@Override
public InputStream getInputStream()
throws IOException
{
connect();
LOG.debug( "Resolving [" + url.toExternalForm() + "]" );
final Set defaultDownloadables = collectDefaultPossibleDownloads();
if( LOG.isTraceEnabled() )
{
LOG.trace( "Possible default download locations for [" + url.toExternalForm() + "]" );
for( DownloadableArtifact artifact : defaultDownloadables )
{
LOG.trace( " " + artifact );
}
}
for( DownloadableArtifact artifact : defaultDownloadables )
{
LOG.trace( "Downloading [" + artifact + "]" );
try
{
m_configuration.enableProxy( artifact.getArtifactURL() );
return artifact.getInputStream();
}
catch( IOException ignore )
{
// go on with next repository
LOG.debug( Ix2 + "Could not download [" + artifact + "]" );
LOG.trace( Ix2 + "Reason [" + ignore.getClass().getName() + ": " + ignore.getMessage() + "]" );
}
}
final Set downloadables = collectPossibleDownloads();
if( LOG.isTraceEnabled() )
{
LOG.trace( "Possible download locations for [" + url.toExternalForm() + "]" );
for( DownloadableArtifact artifact : downloadables )
{
LOG.trace( " " + artifact );
}
}
for( DownloadableArtifact artifact : downloadables )
{
LOG.trace( "Downloading [" + artifact + "]" );
try
{
m_configuration.enableProxy( artifact.getArtifactURL() );
return artifact.getInputStream();
}
catch( IOException ignore )
{
// go on with next repository
LOG.debug( Ix2 + "Could not download [" + artifact + "]" );
LOG.trace( Ix2 + "Reason [" + ignore.getClass().getName() + ": " + ignore.getMessage() + "]" );
}
}
// no artifact found
throw new RuntimeException(
"URL [" + url.toExternalForm() + "] could not be resolved."
);
}
/**
* Searches all available repositories for possible artifacts to download. The returned set of downloadable
* artifacts (never null, but maybe empty) will be sorted descending by version of the artifact and by positon of
* repository in the list of repositories to be searched.
*
* @return a non null sorted set of artifacts
*
* @throws java.net.MalformedURLException re-thrown
*/
private Set collectPossibleDownloads()
throws MalformedURLException
{
final List repositories = new ArrayList();
repositories.addAll( m_configuration.getRepositories() );
// if the url contains a prefered repository add that repository as the first repository to be searched
if( m_parser.getRepositoryURL() != null )
{
repositories.add(
repositories.size() == 0 ? 0 : 1,
m_parser.getRepositoryURL()
);
}
return doCollectPossibleDownloads( repositories );
}
/**
* Search the default repositories for possible artifacts to download.
*/
private Set collectDefaultPossibleDownloads()
throws MalformedURLException
{
return doCollectPossibleDownloads( m_configuration.getDefaultRepositories() );
}
private Set doCollectPossibleDownloads( final List repositories )
throws MalformedURLException
{
final Set downloadables = new TreeSet( new DownloadComparator() );
// find artifact type
final boolean isLatest = m_parser.getVersion().contains( "LATEST" );
final boolean isSnapshot = m_parser.getVersion().endsWith( "SNAPSHOT" );
VersionRange versionRange = null;
if( !isLatest && !isSnapshot )
{
try
{
versionRange = new VersionRange( m_parser.getVersion() );
}
catch( Exception ignore )
{
// well, we do not have a range of versions
}
}
final boolean isVersionRange = versionRange != null;
final boolean isExactVersion = !( isLatest || isSnapshot || isVersionRange );
int priority = 0;
for( MavenRepositoryURL repositoryURL : repositories )
{
LOG.debug( "Collecting versions from repository [" + repositoryURL + "]" );
priority++;
try
{
if( isExactVersion )
{
downloadables.add( resolveExactVersion( repositoryURL, priority ) );
}
else if( isSnapshot )
{
final DownloadableArtifact snapshot =
resolveSnapshotVersion( repositoryURL, priority, m_parser.getVersion() );
downloadables.add( snapshot );
// if we have a local built snapshot we skip the rest of repositories
if( snapshot.isLocalSnapshotBuild() )
{
break;
}
}
else
{
final Document metadata = getMetadata( repositoryURL.getURL(),
new String[]
{
m_parser.getArtifactLocalMetdataPath(),
m_parser.getArtifactMetdataPath()
}
);
if( isLatest )
{
downloadables.add( resolveLatestVersion( metadata, repositoryURL, priority ) );
}
else
{
downloadables.addAll( resolveRangeVersions( metadata, repositoryURL, priority, versionRange ) );
}
}
}
catch( IOException ignore )
{
// if metadata cannot be found we go on with the next repository. Maybe we have better luck.
LOG.debug( Ix2 + "Skipping repository [" + repositoryURL + "], reason: " + ignore.getMessage() );
}
}
return downloadables;
}
/**
* Returns maven metadata by looking first for a local metatdata xml file and then for a remote one.
* If no metadata file is found or cannot be used an IOException is thrown.
*
* @param repositoryURL url of the repository from where the metadata should be parsed
* @param metadataLocations array of location paths to try as metadata
*
* @return parsed xml document for the metadata file
*
* @throws java.io.IOException if:
* metadata file cannot be located
*/
private Document getMetadata( final URL repositoryURL,
final String[] metadataLocations )
throws IOException
{
LOG.debug( Ix2 + "Resolving metadata" );
InputStream inputStream = null;
String foundLocation = null;
for( String location : metadataLocations )
{
try
{
// first try to get the artifact local metadata
inputStream = prepareInputStream( repositoryURL, location );
// get out at first found location
foundLocation = location;
LOG.trace( Ix4 + "Metadata found: [" + location + "]" );
break;
}
catch( IOException ignore )
{
LOG.trace( Ix4 + "Metadata not found: [" + location + "]" );
}
}
if( inputStream == null )
{
throw new IOException( "Metadata not found in repository [" + repositoryURL + "]" );
}
try
{
return XmlUtils.parseDoc( inputStream );
}
catch( ParserConfigurationException e )
{
throw initIOException( "Metadata [" + foundLocation + "] could not be parsed.", e );
}
catch( SAXException e )
{
throw initIOException( "Metadata [" + foundLocation + "] could not be parsed.", e );
}
}
/**
* Returns a downloadable artifact where the version is fully specified.
*
* @param repositoryURL the url of the repository to download from
* @param priority repository priority
*
* @return a downloadable artifact
*
* @throws IOException re-thrown
*/
private DownloadableArtifact resolveExactVersion( final MavenRepositoryURL repositoryURL,
final int priority )
throws IOException
{
if( !repositoryURL.isReleasesEnabled() )
{
throw new IOException( "Releases not enabled" );
}
LOG.debug( Ix2 + "Resolving exact version" );
return new DownloadableArtifact(
m_parser.getVersion(),
priority,
repositoryURL.getURL(),
m_parser.getArtifactPath(),
false, // no local built snapshot
m_configuration.getCertificateCheck()
);
}
/**
* Resolves the latest version of the artifact.
*
* @param metadata parsed metadata xml
* @param repositoryURL the url of the repository to download from
* @param priority repository priority
*
* @return a downloadable artifact or throw an IOException if latest version cannot be determined.
*
* @throws IOException if the artifact could not be resolved
*/
private DownloadableArtifact resolveLatestVersion( final Document metadata,
final MavenRepositoryURL repositoryURL,
final int priority )
throws IOException
{
LOG.debug( Ix2 + "Resolving latest version" );
final String version = XmlUtils.getTextContentOfElement( metadata, "versioning/versions/version[last]" );
if( version != null )
{
if( version.endsWith( "SNAPSHOT" ) )
{
return resolveSnapshotVersion( repositoryURL, priority, version );
}
else
{
return new DownloadableArtifact(
version,
priority,
repositoryURL.getURL(),
m_parser.getArtifactPath( version ),
false, // no local built snapshot
m_configuration.getCertificateCheck()
);
}
}
throw new IOException( "LATEST version could not be resolved." );
}
/**
* Resolves snapshot version of the artifact.
* Snapshot versions are resolved by parsing the metadata within the directory that contains the version as:
* 1. if the metadata containes entries like "versioning/snapshot/timestamp (most likely on remote repos) it will
* use the timestamp and buildnumber to point the real version
* 2. if the metatdata does not contain the above (most likely a local repo) it will use as version the
* versioning/lastUpdated
*
* @param repositoryURL the url of the repository to download from
* @param priority repository priority
* @param version snapshot version to resolve
*
* @return an input stream to the artifact
*
* @throws IOException if the artifact could not be resolved
*/
private DownloadableArtifact resolveSnapshotVersion( final MavenRepositoryURL repositoryURL,
final int priority,
final String version )
throws IOException
{
if( !repositoryURL.isSnapshotsEnabled() )
{
throw new IOException( "Snapshots not enabled" );
}
LOG.debug( Ix2 + "Resolving snapshot version [" + version + "]" );
try
{
final Document snapshotMetadata = getMetadata( repositoryURL.getURL(),
new String[]
{
m_parser.getVersionLocalMetadataPath( version ),
m_parser.getVersionMetadataPath( version )
}
);
final String timestamp =
XmlUtils.getTextContentOfElement( snapshotMetadata, "versioning/snapshot/timestamp" );
final String buildNumber =
XmlUtils.getTextContentOfElement( snapshotMetadata, "versioning/snapshot/buildNumber" );
final String localSnapshot =
XmlUtils.getTextContentOfElement( snapshotMetadata, "versioning/snapshot/localCopy" );
if( timestamp != null && buildNumber != null )
{
return new DownloadableArtifact(
m_parser.getSnapshotVersion( version, timestamp, buildNumber ),
priority,
repositoryURL.getURL(),
m_parser.getSnapshotPath( version, timestamp, buildNumber ),
localSnapshot != null,
m_configuration.getCertificateCheck()
);
}
else
{
String lastUpdated = XmlUtils.getTextContentOfElement( snapshotMetadata, "versioning/lastUpdated" );
if( lastUpdated != null )
{
// last updated should contain in the first 8 chars the date and then the time,
// fact that is not compatible with timeStamp from remote repos which has a "." after date
if( lastUpdated.length() > 8 )
{
lastUpdated = lastUpdated.substring( 0, 8 ) + "." + lastUpdated.substring( 8 );
return new DownloadableArtifact(
m_parser.getSnapshotVersion( version, lastUpdated, "0" ),
priority,
repositoryURL.getURL(),
m_parser.getArtifactPath( version ),
localSnapshot != null,
m_configuration.getCertificateCheck()
);
}
}
}
}
catch( IOException ignore )
{
// in this case we could not find any metadata so try to get the *-SNAPSHOT file directly
}
return new DownloadableArtifact(
m_parser.getVersion(),
priority,
repositoryURL.getURL(),
m_parser.getArtifactPath(),
false, // no local built snapshot
m_configuration.getCertificateCheck()
);
}
/**
* Resolves all versions that fits the provided range.
*
* @param metadata parsed metadata xml
* @param repositoryURL the url of the repository to download from
* @param priority repository priority
* @param versionRange version range to fulfill
*
* @return list of downloadable artifacts that match the range
*
* @throws IOException re-thrown
*/
private List resolveRangeVersions( final Document metadata,
final MavenRepositoryURL repositoryURL,
final int priority,
final VersionRange versionRange )
throws IOException
{
LOG.debug( Ix2 + "Resolving versions in range [" + versionRange + "]" );
final List downladables = new ArrayList();
final List elements = XmlUtils.getElements( metadata, "versioning/versions/version" );
if( elements != null && elements.size() > 0 )
{
for( Element element : elements )
{
final String versionString = XmlUtils.getTextContent( element );
if( versionString != null )
{
final Version version = new Version( versionString );
if( versionRange.includes( version ) )
{
if( versionString.endsWith( "SNAPSHOT" ) )
{
downladables.add(
resolveSnapshotVersion( repositoryURL, priority, versionString )
);
}
else
{
downladables.add(
new DownloadableArtifact(
versionString,
priority,
repositoryURL.getURL(),
m_parser.getArtifactPath( versionString ),
false, // no local built snapshot
m_configuration.getCertificateCheck()
)
);
}
}
}
}
}
return downladables;
}
/**
* @param repositoryURL url to reporsitory
* @param path a path to the artifact jar file
*
* @return prepared input stream
*
* @throws IOException re-thrown
* @see org.ops4j.net.URLUtils#prepareInputStream(java.net.URL,boolean)
*/
private InputStream prepareInputStream( URL repositoryURL, final String path )
throws IOException
{
String repository = repositoryURL.toExternalForm();
if( !repository.endsWith( Parser.FILE_SEPARATOR ) )
{
repository = repository + Parser.FILE_SEPARATOR;
}
m_configuration.enableProxy( repositoryURL );
final URL url = new URL( repository + path );
LOG.trace( "Reading " + url.toExternalForm() );
return URLUtils.prepareInputStream( url, !m_configuration.getCertificateCheck() );
}
/**
* Creates an IOException with a message and a cause.
*
* @param message exception message
* @param cause exception cause
*
* @return the created IO Exception
*/
private IOException initIOException( final String message, final Exception cause )
{
IOException exception = new IOException( message );
exception.initCause( cause );
return exception;
}
/**
* Sorting comparator for downladable artifacts.
* The sorting is done by:
* 1. descending version
* 2. ascending priority.
*/
private static class DownloadComparator
implements Comparator
{
public int compare( final DownloadableArtifact first,
final DownloadableArtifact second )
{
// first descending by version
int result = -1 * first.getVersion().compareTo( second.getVersion() );
if( result == 0 )
{
// then ascending by priority
if( first.getPriority() < second.getPriority() )
{
result = -1;
}
else if( first.getPriority() > second.getPriority() )
{
result = 1;
}
}
return result;
}
}
}