[MNG-4381] Allow extension plugins to contribute non-core components to be reused by other plugins

git-svn-id: https://svn.apache.org/repos/asf/maven/maven-3/trunk@819540 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Benjamin Bentmann 2009-09-28 13:56:25 +00:00
parent 51b6120810
commit 5950571180
17 changed files with 1161 additions and 104 deletions

View File

@ -43,6 +43,14 @@ public interface ClassRealmManager
*/
ClassRealm createProjectRealm( Model model );
/**
* Creates a new class realm for the specified build extension.
*
* @param plugin The extension plugin for which to create a realm, must not be {@code null}.
* @return The new extension realm, never {@code null}.
*/
ClassRealm createExtensionRealm( Plugin extension );
/**
* Creates a new class realm for the specified plugin.
*

View File

@ -197,6 +197,16 @@ public class DefaultClassRealmManager
return "project>" + model.getGroupId() + ":" + model.getArtifactId() + ":" + model.getVersion();
}
public ClassRealm createExtensionRealm( Plugin plugin )
{
if ( plugin == null )
{
throw new IllegalArgumentException( "extension plugin missing" );
}
return createRealm( getKey( plugin, true ), null, null );
}
public ClassRealm createPluginRealm( Plugin plugin, ClassLoader parent, List<String> imports )
{
if ( plugin == null )
@ -204,13 +214,14 @@ public class DefaultClassRealmManager
throw new IllegalArgumentException( "plugin missing" );
}
return createRealm( getKey( plugin ), parent, imports );
return createRealm( getKey( plugin, false ), parent, imports );
}
private String getKey( Plugin plugin )
private String getKey( Plugin plugin, boolean extension )
{
String version = ArtifactUtils.toSnapshotVersion( plugin.getVersion() );
return "plugin>" + plugin.getGroupId() + ":" + plugin.getArtifactId() + ":" + version;
return ( extension ? "extension>" : "plugin>" ) + plugin.getGroupId() + ":" + plugin.getArtifactId() + ":"
+ version;
}
private List<ClassRealmManagerDelegate> getDelegates()

View File

@ -0,0 +1,231 @@
package org.apache.maven.plugin;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.repository.RepositoryRequest;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.Exclusion;
import org.apache.maven.model.Plugin;
import org.apache.maven.project.ExtensionDescriptor;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.classworlds.realm.ClassRealm;
import org.codehaus.plexus.component.annotations.Component;
/**
* Default extension realm cache implementation. Assumes cached data does not change.
*/
@Component( role = ExtensionRealmCache.class )
public class DefaultExtensionRealmCache
implements ExtensionRealmCache
{
private static class CacheKey
{
private final Plugin extension;
private final List<ArtifactRepository> repositories = new ArrayList<ArtifactRepository>();
private final int hashCode;
public CacheKey( Plugin extension, RepositoryRequest repositoryRequest )
{
this.extension = extension.clone();
this.repositories.add( repositoryRequest.getLocalRepository() );
this.repositories.addAll( repositoryRequest.getRemoteRepositories() );
int hash = 17;
hash = hash * 31 + extensionHashCode( extension );
hash = hash * 31 + repositories.hashCode();
this.hashCode = hash;
}
@Override
public int hashCode()
{
return hashCode;
}
@Override
public boolean equals( Object o )
{
if ( o == this )
{
return true;
}
if ( !( o instanceof CacheKey ) )
{
return false;
}
CacheKey other = (CacheKey) o;
return extensionEquals( extension, other.extension ) && eq( repositories, other.repositories );
}
}
private final Map<CacheKey, CacheRecord> cache = new HashMap<CacheKey, CacheRecord>();
public CacheRecord get( Plugin extension, RepositoryRequest repositoryRequest )
{
return cache.get( new CacheKey( extension, repositoryRequest ) );
}
public void put( Plugin extension, RepositoryRequest repositoryRequest, ClassRealm extensionRealm,
List<Artifact> extensionArtifacts, ExtensionDescriptor extensionDescriptor )
{
if ( extensionRealm == null || extensionArtifacts == null )
{
throw new NullPointerException();
}
CacheKey key = new CacheKey( extension, repositoryRequest );
if ( cache.containsKey( key ) )
{
throw new IllegalStateException( "Duplicate extension realm for extension " + extension.getId() );
}
CacheRecord record = new CacheRecord( extensionRealm, extensionArtifacts, extensionDescriptor );
cache.put( key, record );
}
public void flush()
{
cache.clear();
}
protected static int extensionHashCode( Plugin extension )
{
int hash = 17;
hash = hash * 31 + extension.getGroupId().hashCode();
hash = hash * 31 + extension.getArtifactId().hashCode();
hash = hash * 31 + extension.getVersion().hashCode();
for ( Dependency dependency : extension.getDependencies() )
{
hash = hash * 31 + dependency.getGroupId().hashCode();
hash = hash * 31 + dependency.getArtifactId().hashCode();
hash = hash * 31 + dependency.getVersion().hashCode();
hash = hash * 31 + dependency.getType().hashCode();
hash = hash * 31 + ( dependency.getClassifier() != null ? dependency.getClassifier().hashCode() : 0 );
hash = hash * 31 + ( dependency.getScope() != null ? dependency.getScope().hashCode() : 0 );
for ( Exclusion exclusion : dependency.getExclusions() )
{
hash = hash * 31 + exclusion.getGroupId().hashCode();
hash = hash * 31 + exclusion.getArtifactId().hashCode();
}
}
return hash;
}
private static boolean extensionEquals( Plugin a, Plugin b )
{
return eq( a.getGroupId(), b.getGroupId() ) //
&& eq( a.getArtifactId(), b.getArtifactId() ) //
&& eq( a.getVersion(), b.getVersion() ) //
&& a.isExtensions() == b.isExtensions() //
&& dependenciesEquals( a.getDependencies(), b.getDependencies() );
}
private static boolean dependenciesEquals( List<Dependency> a, List<Dependency> b )
{
if ( a.size() != b.size() )
{
return false;
}
Iterator<Dependency> aI = a.iterator();
Iterator<Dependency> bI = b.iterator();
while ( aI.hasNext() )
{
Dependency aD = aI.next();
Dependency bD = bI.next();
boolean r = eq( aD.getGroupId(), bD.getGroupId() ) //
&& eq( aD.getArtifactId(), bD.getArtifactId() ) //
&& eq( aD.getVersion(), bD.getVersion() ) //
&& eq( aD.getType(), bD.getType() ) //
&& eq( aD.getClassifier(), bD.getClassifier() ) //
&& eq( aD.getScope(), bD.getScope() );
r &= exclusionsEquals( aD.getExclusions(), bD.getExclusions() );
if ( !r )
{
return false;
}
}
return true;
}
private static boolean exclusionsEquals( List<Exclusion> a, List<Exclusion> b )
{
if ( a.size() != b.size() )
{
return false;
}
Iterator<Exclusion> aI = a.iterator();
Iterator<Exclusion> bI = b.iterator();
while ( aI.hasNext() )
{
Exclusion aD = aI.next();
Exclusion bD = bI.next();
boolean r = eq( aD.getGroupId(), bD.getGroupId() ) //
&& eq( aD.getArtifactId(), bD.getArtifactId() );
if ( !r )
{
return false;
}
}
return true;
}
private static <T> boolean eq( T s1, T s2 )
{
return s1 != null ? s1.equals( s2 ) : s2 == null;
}
public void register( MavenProject project, ClassRealm extensionRealm )
{
// default cache does not track extension usage
}
}

View File

@ -0,0 +1,77 @@
package org.apache.maven.plugin;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
import java.util.List;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.repository.RepositoryRequest;
import org.apache.maven.model.Plugin;
import org.apache.maven.project.ExtensionDescriptor;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.classworlds.realm.ClassRealm;
/**
* Caches extension class realms. <strong>Warning:</strong> This is an internal utility interface that is only public
* for technical reasons, it is not part of the public API. In particular, this interface can be changed or deleted
* without prior notice.
*
* @author Igor Fedorenko
* @author Benjamin Bentmann
*/
public interface ExtensionRealmCache
{
public static class CacheRecord
{
public final ClassRealm realm;
public final List<Artifact> artifacts;
public final ExtensionDescriptor desciptor;
public CacheRecord( ClassRealm realm, List<Artifact> artifacts, ExtensionDescriptor descriptor )
{
this.realm = realm;
this.artifacts = artifacts;
this.desciptor = descriptor;
}
}
CacheRecord get( Plugin extension, RepositoryRequest repositoryRequest );
void put( Plugin extension, RepositoryRequest repositoryRequest, ClassRealm extensionRealm,
List<Artifact> extensionArtifacts, ExtensionDescriptor extensionDescriptor );
void flush();
/**
* Registers the specified extension realm for usage with the given project. Integrators can use the information
* collected from this method in combination with a custom cache implementation to dispose unused extension realms
* from the cache.
*
* @param project The project that employs the extension realm, must not be {@code null}.
* @param extensionRealm The extension realm being used for the project, must not be {@code null}.
*/
void register( MavenProject project, ClassRealm extensionRealm );
}

View File

@ -27,6 +27,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
@ -42,6 +43,7 @@ import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.artifact.resolver.ArtifactResolutionRequest;
import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
import org.apache.maven.artifact.resolver.ResolutionErrorHandler;
import org.apache.maven.artifact.resolver.filter.AndArtifactFilter;
import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
import org.apache.maven.artifact.resolver.filter.ScopeArtifactFilter;
import org.apache.maven.classrealm.ClassRealmManager;
@ -339,13 +341,16 @@ public class DefaultMavenPluginManager
throw new IllegalArgumentException( "incomplete plugin descriptor, plugin artifact missing" );
}
MavenProject project = session.getCurrentProject();
RepositoryRequest request = new DefaultRepositoryRequest();
request.setLocalRepository( session.getLocalRepository() );
request.setRemoteRepositories( session.getCurrentProject().getPluginArtifactRepositories() );
request.setRemoteRepositories( project.getPluginArtifactRepositories() );
request.setCache( session.getRepositoryCache() );
request.setOffline( session.isOffline() );
List<Artifact> pluginArtifacts = resolvePluginArtifacts( plugin, pluginArtifact, request );
List<Artifact> pluginArtifacts =
resolvePluginArtifacts( plugin, pluginArtifact, request, project.getExtensionArtifactFilter() );
ClassRealm pluginRealm = classRealmManager.createPluginRealm( plugin, parent, imports );
@ -417,7 +422,8 @@ public class DefaultMavenPluginManager
*/
// FIXME: only exposed to allow workaround for MNG-4194
protected List<Artifact> resolvePluginArtifacts( Plugin plugin, Artifact pluginArtifact,
RepositoryRequest repositoryRequest )
RepositoryRequest repositoryRequest,
ArtifactFilter extensionArtifactFilter )
throws PluginResolutionException
{
Set<Artifact> overrideArtifacts = new LinkedHashSet<Artifact>();
@ -430,6 +436,11 @@ public class DefaultMavenPluginManager
ArtifactFilter resolutionFilter = artifactFilterManager.getCoreArtifactFilter();
if ( extensionArtifactFilter != null )
{
resolutionFilter = new AndArtifactFilter( Arrays.asList( resolutionFilter, extensionArtifactFilter ) );
}
ArtifactResolutionRequest request = new ArtifactResolutionRequest( repositoryRequest );
request.setArtifact( pluginArtifact );
request.setArtifactDependencies( overrideArtifacts );

View File

@ -25,6 +25,7 @@ import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.repository.DefaultRepositoryRequest;
import org.apache.maven.artifact.repository.RepositoryRequest;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
import org.apache.maven.model.Model;
import org.apache.maven.model.building.AbstractModelBuildingListener;
import org.apache.maven.model.building.ModelBuildingEvent;
@ -46,6 +47,8 @@ class DefaultModelBuildingListener
private ClassRealm projectRealm;
private ArtifactFilter extensionArtifactFilter;
private List<ArtifactRepository> remoteRepositories;
private List<ArtifactRepository> pluginRepositories;
@ -78,6 +81,16 @@ class DefaultModelBuildingListener
return projectRealm;
}
/**
* Gets the artifact filter to exclude extension artifacts from plugin realms.
*
* @return The extension artifact filter or {@code null} if none.
*/
public ArtifactFilter getExtentionArtifactFilter()
{
return extensionArtifactFilter;
}
/**
* Gets the effective remote artifact repositories for the project. The repository list is created from the
* repositories given by {@link ProjectBuildingRequest#getRemoteRepositories()} and the repositories given in the
@ -111,17 +124,6 @@ class DefaultModelBuildingListener
{
Model model = event.getModel();
try
{
remoteRepositories =
projectBuildingHelper.createArtifactRepositories( model.getRepositories(), remoteRepositories,
projectBuildingRequest );
}
catch ( Exception e )
{
event.getProblems().addError( "Invalid artifact repository: " + e.getMessage(), e );
}
try
{
pluginRepositories =
@ -143,7 +145,11 @@ class DefaultModelBuildingListener
repositoryRequest.setRemoteRepositories( pluginRepositories );
repositoryRequest.setOffline( projectBuildingRequest.isOffline() );
projectRealm = projectBuildingHelper.createProjectRealm( model, repositoryRequest );
ProjectRealmCache.CacheRecord record =
projectBuildingHelper.createProjectRealm( model, repositoryRequest );
projectRealm = record.realm;
extensionArtifactFilter = record.extensionArtifactFilter;
}
catch ( ArtifactResolutionException e )
{
@ -164,6 +170,18 @@ class DefaultModelBuildingListener
Thread.currentThread().setContextClassLoader( projectRealm );
}
}
// build the regular repos after extensions are loaded to allow for custom layouts
try
{
remoteRepositories =
projectBuildingHelper.createArtifactRepositories( model.getRepositories(), remoteRepositories,
projectBuildingRequest );
}
catch ( Exception e )
{
event.getProblems().addError( "Invalid artifact repository: " + e.getMessage(), e );
}
}
}

View File

@ -486,6 +486,7 @@ public class DefaultProjectBuilder
project.setPluginArtifactRepositories( listener.getPluginRepositories() );
project.setClassRealm( listener.getProjectRealm() );
project.setExtensionArtifactFilter( listener.getExtentionArtifactFilter() );
Build build = project.getBuild();
project.addScriptSourceRoot( build.getScriptSourceDirectory() );

View File

@ -19,10 +19,14 @@ package org.apache.maven.project;
* under the License.
*/
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.maven.ArtifactFilterManager;
@ -35,6 +39,8 @@ import org.apache.maven.artifact.resolver.ArtifactResolutionRequest;
import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
import org.apache.maven.artifact.resolver.ResolutionErrorHandler;
import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
import org.apache.maven.artifact.resolver.filter.ExclusionSetFilter;
import org.apache.maven.artifact.resolver.filter.ScopeArtifactFilter;
import org.apache.maven.classrealm.ClassRealmManager;
import org.apache.maven.model.Build;
import org.apache.maven.model.Dependency;
@ -42,6 +48,7 @@ import org.apache.maven.model.Extension;
import org.apache.maven.model.Model;
import org.apache.maven.model.Plugin;
import org.apache.maven.model.Repository;
import org.apache.maven.plugin.ExtensionRealmCache;
import org.apache.maven.plugin.version.DefaultPluginVersionRequest;
import org.apache.maven.plugin.version.PluginVersionRequest;
import org.apache.maven.plugin.version.PluginVersionResolutionException;
@ -74,6 +81,12 @@ public class DefaultProjectBuildingHelper
@Requirement
private ClassRealmManager classRealmManager;
@Requirement
private ExtensionRealmCache extensionRealmCache;
@Requirement
private ProjectRealmCache projectRealmCache;
@Requirement
private RepositorySystem repositorySystem;
@ -86,6 +99,8 @@ public class DefaultProjectBuildingHelper
@Requirement
private PluginVersionResolver pluginVersionResolver;
private ExtensionDescriptorBuilder extensionDescriptorBuilder = new ExtensionDescriptorBuilder();
public List<ArtifactRepository> createArtifactRepositories( List<Repository> pomRepositories,
List<ArtifactRepository> externalRepositories,
ProjectBuildingRequest request )
@ -114,52 +129,49 @@ public class DefaultProjectBuildingHelper
return artifactRepositories;
}
public ClassRealm createProjectRealm( Model model, RepositoryRequest repositoryRequest )
public synchronized ProjectRealmCache.CacheRecord createProjectRealm( Model model,
RepositoryRequest repositoryRequest )
throws ArtifactResolutionException, PluginVersionResolutionException
{
ClassRealm projectRealm = null;
Build build = model.getBuild();
if ( build == null )
{
return projectRealm;
}
List<Plugin> extensionPlugins = new ArrayList<Plugin>();
for ( Plugin plugin : build.getPlugins() )
Build build = model.getBuild();
if ( build != null )
{
if ( plugin.isExtensions() )
for ( Extension extension : build.getExtensions() )
{
Plugin plugin = new Plugin();
plugin.setGroupId( extension.getGroupId() );
plugin.setArtifactId( extension.getArtifactId() );
plugin.setVersion( extension.getVersion() );
extensionPlugins.add( plugin );
}
}
if ( build.getExtensions().isEmpty() && extensionPlugins.isEmpty() )
{
return projectRealm;
}
projectRealm = classRealmManager.createProjectRealm( model );
for ( Extension extension : build.getExtensions() )
{
if ( extension.getVersion() == null )
for ( Plugin plugin : build.getPlugins() )
{
PluginVersionRequest versionRequest = new DefaultPluginVersionRequest( repositoryRequest );
versionRequest.setGroupId( extension.getGroupId() );
versionRequest.setArtifactId( extension.getArtifactId() );
extension.setVersion( pluginVersionResolver.resolve( versionRequest ).getVersion() );
if ( plugin.isExtensions() )
{
extensionPlugins.add( plugin );
}
}
Artifact artifact =
repositorySystem.createArtifact( extension.getGroupId(), extension.getArtifactId(),
extension.getVersion(), "jar" );
populateRealm( projectRealm, artifact, null, repositoryRequest );
}
if ( extensionPlugins.isEmpty() )
{
return new ProjectRealmCache.CacheRecord( null, null );
}
List<ClassRealm> extensionRealms = new ArrayList<ClassRealm>();
Map<ClassRealm, List<String>> exportedPackages = new HashMap<ClassRealm, List<String>>();
Map<ClassRealm, List<String>> exportedArtifacts = new HashMap<ClassRealm, List<String>>();
List<Artifact> publicArtifacts = new ArrayList<Artifact>();
for ( Plugin plugin : extensionPlugins )
{
if ( plugin.getVersion() == null )
@ -168,73 +180,206 @@ public class DefaultProjectBuildingHelper
plugin.setVersion( pluginVersionResolver.resolve( versionRequest ).getVersion() );
}
Artifact artifact = repositorySystem.createPluginArtifact( plugin );
ClassRealm extensionRealm;
List<Artifact> artifacts;
ExtensionDescriptor extensionDescriptor = null;
Set<Artifact> dependencies = new LinkedHashSet<Artifact>();
for ( Dependency dependency : plugin.getDependencies() )
ExtensionRealmCache.CacheRecord record = extensionRealmCache.get( plugin, repositoryRequest );
if ( record != null )
{
dependencies.add( repositorySystem.createDependencyArtifact( dependency ) );
extensionRealm = record.realm;
artifacts = record.artifacts;
extensionDescriptor = record.desciptor;
}
else
{
artifacts = resolveExtensionArtifacts( plugin, repositoryRequest );
extensionRealm = classRealmManager.createExtensionRealm( plugin );
if ( logger.isDebugEnabled() )
{
logger.debug( "Populating extension realm for " + plugin.getId() );
}
for ( Artifact artifact : artifacts )
{
if ( artifact.getFile() != null )
{
if ( logger.isDebugEnabled() )
{
logger.debug( " Included: " + artifact.getId() );
}
try
{
extensionRealm.addURL( artifact.getFile().toURI().toURL() );
}
catch ( MalformedURLException e )
{
// Not going to happen
}
}
else
{
if ( logger.isDebugEnabled() )
{
logger.debug( " Excluded: " + artifact.getId() );
}
}
}
try
{
container.discoverComponents( extensionRealm );
}
catch ( Exception e )
{
throw new IllegalStateException( "Failed to discover components in extension realm "
+ extensionRealm.getId(), e );
}
Artifact extensionArtifact = artifacts.get( 0 );
try
{
extensionDescriptor = extensionDescriptorBuilder.build( extensionArtifact.getFile() );
}
catch ( IOException e )
{
String message = "Invalid extension descriptor for " + plugin.getId() + ": " + e.getMessage();
if ( logger.isDebugEnabled() )
{
logger.error( message, e );
}
else
{
logger.error( message );
}
}
extensionRealmCache.put( plugin, repositoryRequest, extensionRealm, artifacts, extensionDescriptor );
}
populateRealm( projectRealm, artifact, dependencies, repositoryRequest );
extensionRealms.add( extensionRealm );
if ( extensionDescriptor != null )
{
exportedPackages.put( extensionRealm, extensionDescriptor.getExportedPackages() );
exportedArtifacts.put( extensionRealm, extensionDescriptor.getExportedArtifacts() );
}
if ( !plugin.isExtensions() && artifacts.size() == 1 && artifacts.get( 0 ).getFile() != null )
{
/*
* This is purely for backward-compat with 2.x where <extensions> consisting of a single artifact where
* loaded into the core and hence available to plugins, in contrast to bigger extensions that were
* loaded into a dedicated realm which is invisible to plugins.
*/
publicArtifacts.addAll( artifacts );
}
}
try
ProjectRealmCache.CacheRecord record = projectRealmCache.get( extensionRealms );
if ( record == null )
{
container.discoverComponents( projectRealm );
}
catch ( Exception e )
{
throw new IllegalStateException( "Failed to discover components in project realm " + projectRealm.getId(),
e );
projectRealm = classRealmManager.createProjectRealm( model );
if ( logger.isDebugEnabled() )
{
logger.debug( "Populating project realm for " + model.getId() );
}
for ( Artifact publicArtifact : publicArtifacts )
{
if ( logger.isDebugEnabled() )
{
logger.debug( " Included: " + publicArtifact.getId() );
}
try
{
projectRealm.addURL( publicArtifact.getFile().toURI().toURL() );
}
catch ( MalformedURLException e )
{
// can't happen
}
}
Set<String> exclusions = new LinkedHashSet<String>();
for ( ClassRealm extensionRealm : extensionRealms )
{
List<String> excludes = exportedArtifacts.get( extensionRealm );
if ( excludes != null )
{
exclusions.addAll( excludes );
}
List<String> exports = exportedPackages.get( extensionRealm );
if ( exports == null || exports.isEmpty() )
{
/*
* Most existing extensions don't define exported packages, i.e. no classes are to be exposed to
* plugins, yet the components provided by the extension (e.g. artifact handlers) must be
* accessible, i.e. we still must import the extension realm into the project realm.
*/
exports = Arrays.asList( extensionRealm.getId() );
}
for ( String export : exports )
{
projectRealm.importFrom( extensionRealm, export );
}
}
ArtifactFilter extensionArtifactFilter = null;
if ( !exclusions.isEmpty() )
{
extensionArtifactFilter = new ExclusionSetFilter( exclusions );
}
projectRealmCache.put( extensionRealms, projectRealm, extensionArtifactFilter );
record = new ProjectRealmCache.CacheRecord( projectRealm, extensionArtifactFilter );
}
return projectRealm;
return record;
}
private void populateRealm( ClassRealm realm, Artifact artifact, Set<Artifact> dependencies,
RepositoryRequest repositoryRequest )
private List<Artifact> resolveExtensionArtifacts( Plugin extensionPlugin, RepositoryRequest repositoryRequest )
throws ArtifactResolutionException
{
Artifact extensionArtifact = repositorySystem.createPluginArtifact( extensionPlugin );
Set<Artifact> overrideArtifacts = new LinkedHashSet<Artifact>();
for ( Dependency dependency : extensionPlugin.getDependencies() )
{
overrideArtifacts.add( repositorySystem.createDependencyArtifact( dependency ) );
}
ArtifactFilter collectionFilter = new ScopeArtifactFilter( Artifact.SCOPE_RUNTIME_PLUS_SYSTEM );
ArtifactFilter resolutionFilter = artifactFilterManager.getCoreArtifactFilter();
ArtifactResolutionRequest request = new ArtifactResolutionRequest( repositoryRequest );
request.setArtifact( artifact );
request.setArtifactDependencies( dependencies );
request.setArtifact( extensionArtifact );
request.setArtifactDependencies( overrideArtifacts );
request.setCollectionFilter( collectionFilter );
request.setResolutionFilter( resolutionFilter );
request.setResolveRoot( true );
request.setResolveTransitively( true );
// FIXME setTransferListener
ArtifactResolutionResult result = repositorySystem.resolve( request );
resolutionErrorHandler.throwErrors( request, result );
ArtifactFilter filter = artifactFilterManager.getCoreArtifactFilter();
List<Artifact> extensionArtifacts = new ArrayList<Artifact>( result.getArtifacts() );
for ( Artifact resultArtifact : result.getArtifacts() )
{
if ( filter.include( resultArtifact ) )
{
if ( logger.isDebugEnabled() )
{
logger.debug( " Included: " + resultArtifact.getId() );
}
try
{
realm.addURL( resultArtifact.getFile().toURI().toURL() );
}
catch ( MalformedURLException e )
{
throw new IllegalStateException( "Failed to populate project realm " + realm.getId() + " with "
+ artifact.getFile(), e );
}
}
else
{
if ( logger.isDebugEnabled() )
{
logger.debug( " Excluded: " + resultArtifact.getId() );
}
}
}
return extensionArtifacts;
}
}

View File

@ -0,0 +1,110 @@
package org.apache.maven.project;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
import org.codehaus.plexus.classworlds.realm.ClassRealm;
import org.codehaus.plexus.component.annotations.Component;
/**
* Default project realm cache implementation. Assumes cached data does not change.
*/
@Component( role = ProjectRealmCache.class )
public class DefaultProjectRealmCache
implements ProjectRealmCache
{
private static class CacheKey
{
private final List<? extends ClassRealm> extensionRealms;
private final int hashCode;
public CacheKey( List<? extends ClassRealm> extensionRealms )
{
this.extensionRealms = ( extensionRealms != null ) ? extensionRealms : Collections.<ClassRealm> emptyList();
this.hashCode = this.extensionRealms.hashCode();
}
@Override
public int hashCode()
{
return hashCode;
}
@Override
public boolean equals( Object o )
{
if ( o == this )
{
return true;
}
if ( !( o instanceof CacheKey ) )
{
return false;
}
CacheKey other = (CacheKey) o;
return extensionRealms.equals( other.extensionRealms );
}
}
private final Map<CacheKey, CacheRecord> cache = new HashMap<CacheKey, CacheRecord>();
public CacheRecord get( List<? extends ClassRealm> extensionRealms )
{
return cache.get( new CacheKey( extensionRealms ) );
}
public void put( List<? extends ClassRealm> extensionRealms, ClassRealm projectRealm,
ArtifactFilter extensionArtifactFilter )
{
if ( projectRealm == null )
{
throw new NullPointerException();
}
CacheKey key = new CacheKey( extensionRealms );
if ( cache.containsKey( key ) )
{
throw new IllegalStateException( "Duplicate project realm for extensions " + extensionRealms );
}
CacheRecord record = new CacheRecord( projectRealm, extensionArtifactFilter );
cache.put( key, record );
}
public void flush()
{
cache.clear();
}
}

View File

@ -0,0 +1,88 @@
package org.apache.maven.project;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
import java.util.ArrayList;
import java.util.List;
/**
* Provides metadata about a build extension. <strong>Warning:</strong> This is an internal utility class that is only
* public for technical reasons, it is not part of the public API. In particular, this class can be changed or deleted
* without prior notice.
*
* @author Benjamin Bentmann
*/
public class ExtensionDescriptor
{
private List<String> exportedPackages;
private List<String> exportedArtifacts;
ExtensionDescriptor()
{
// hide constructor
}
public List<String> getExportedPackages()
{
if ( exportedPackages == null )
{
exportedPackages = new ArrayList<String>();
}
return exportedPackages;
}
public void setExportedPackages( List<String> exportedPackages )
{
if ( exportedPackages == null )
{
this.exportedPackages = null;
}
else
{
this.exportedPackages = new ArrayList<String>( exportedPackages );
}
}
public List<String> getExportedArtifacts()
{
if ( exportedArtifacts == null )
{
exportedArtifacts = new ArrayList<String>();
}
return exportedArtifacts;
}
public void setExportedArtifacts( List<String> exportedArtifacts )
{
if ( exportedArtifacts == null )
{
this.exportedArtifacts = null;
}
else
{
this.exportedArtifacts = new ArrayList<String>( exportedArtifacts );
}
}
}

View File

@ -0,0 +1,159 @@
package org.apache.maven.project;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.ReaderFactory;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.codehaus.plexus.util.xml.Xpp3DomBuilder;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
/**
* Creates an extension descriptor from some XML stream.
*
* @author Benjamin Bentmann
*/
class ExtensionDescriptorBuilder
{
private String getExtensionDescriptorLocation()
{
return "META-INF/maven/extension.xml";
}
/**
* Extracts the extension descriptor (if any) from the specified JAR file.
*
* @param extensionJar The JAR file or directory to extract the descriptor from, must not be {@code null}.
* @return The extracted descriptor or {@code null} if no descriptor was found.
* @throws IOException If the descriptor is present but could not be parsed.
*/
public ExtensionDescriptor build( File extensionJar )
throws IOException
{
ExtensionDescriptor extensionDescriptor = null;
if ( extensionJar.isFile() )
{
JarFile pluginJar = new JarFile( extensionJar, false );
try
{
ZipEntry pluginDescriptorEntry = pluginJar.getEntry( getExtensionDescriptorLocation() );
if ( pluginDescriptorEntry != null )
{
InputStream is = pluginJar.getInputStream( pluginDescriptorEntry );
extensionDescriptor = build( is );
}
}
finally
{
pluginJar.close();
}
}
else
{
File pluginXml = new File( extensionJar, getExtensionDescriptorLocation() );
if ( pluginXml.canRead() )
{
InputStream is = new BufferedInputStream( new FileInputStream( pluginXml ) );
try
{
extensionDescriptor = build( is );
}
finally
{
IOUtil.close( is );
}
}
}
return extensionDescriptor;
}
ExtensionDescriptor build( InputStream is )
throws IOException
{
ExtensionDescriptor extensionDescriptor = new ExtensionDescriptor();
Xpp3Dom dom;
try
{
dom = Xpp3DomBuilder.build( ReaderFactory.newXmlReader( is ) );
}
catch ( XmlPullParserException e )
{
throw (IOException) new IOException( e.getMessage() ).initCause( e );
}
finally
{
IOUtil.close( is );
}
if ( !"extension".equals( dom.getName() ) )
{
throw new IOException( "Unexpected root element \"" + dom.getName() + "\", expected \"extension\"" );
}
extensionDescriptor.setExportedPackages( parseStrings( dom.getChild( "exportedPackages" ) ) );
extensionDescriptor.setExportedArtifacts( parseStrings( dom.getChild( "exportedArtifacts" ) ) );
return extensionDescriptor;
}
private List<String> parseStrings( Xpp3Dom dom )
{
List<String> strings = null;
if ( dom != null )
{
strings = new ArrayList<String>();
for ( Xpp3Dom child : dom.getChildren() )
{
String string = child.getValue();
if ( string != null )
{
string = string.trim();
if ( string.length() > 0 )
{
strings.add( string );
}
}
}
}
return strings;
}
}

View File

@ -167,6 +167,8 @@ public class MavenProject
private ClassRealm classRealm;
private ArtifactFilter extensionArtifactFilter;
//
public MavenProject()
@ -1948,7 +1950,9 @@ public class MavenProject
}
/**
* Sets the project's class realm.
* Sets the project's class realm. <strong>Warning:</strong> This is an internal utility method that is only public
* for technical reasons, it is not part of the public API. In particular, this method can be changed or deleted
* without prior notice and must not be used by plugins.
*
* @param classRealm The class realm hosting the build extensions of this project, may be {@code null}.
*/
@ -1959,6 +1963,9 @@ public class MavenProject
/**
* Gets the project's class realm. This class realm hosts the build extensions of the project.
* <strong>Warning:</strong> This is an internal utility method that is only public for technical reasons, it is not
* part of the public API. In particular, this method can be changed or deleted without prior notice and must not be
* used by plugins.
*
* @return The project's class realm or {@code null}.
*/
@ -1967,4 +1974,28 @@ public class MavenProject
return classRealm;
}
/**
* Sets the artifact filter used to exclude shared extension artifacts from plugin realms. <strong>Warning:</strong>
* This is an internal utility method that is only public for technical reasons, it is not part of the public API.
* In particular, this method can be changed or deleted without prior notice and must not be used by plugins.
*
* @param extensionArtifactFilter The artifact filter to apply to plugins, may be {@code null}.
*/
public void setExtensionArtifactFilter( ArtifactFilter extensionArtifactFilter )
{
this.extensionArtifactFilter = extensionArtifactFilter;
}
/**
* Gets the artifact filter used to exclude shared extension artifacts from plugin realms. <strong>Warning:</strong>
* This is an internal utility method that is only public for technical reasons, it is not part of the public API.
* In particular, this method can be changed or deleted without prior notice and must not be used by plugins.
*
* @return The artifact filter or {@code null}.
*/
public ArtifactFilter getExtensionArtifactFilter()
{
return extensionArtifactFilter;
}
}

View File

@ -28,7 +28,6 @@ import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.model.Model;
import org.apache.maven.model.Repository;
import org.apache.maven.plugin.version.PluginVersionResolutionException;
import org.codehaus.plexus.classworlds.realm.ClassRealm;
/**
* Assists the project builder. <strong>Warning:</strong> This is an internal utility interface that is only public for
@ -61,10 +60,10 @@ public interface ProjectBuildingHelper
*
* @param model The model to create the project realm for, must not be {@code null}
* @param repositoryRequest The repository request to use for artifact resolution, must not be {@code null}.
* @return The project realm or {@code null} if the project uses no extensions.
* @return The record with the project realm and extension artifact filter, never {@code null}.
* @throws ArtifactResolutionException If any build extension could not be resolved.
*/
ClassRealm createProjectRealm( Model model, RepositoryRequest repositoryRequest )
ProjectRealmCache.CacheRecord createProjectRealm( Model model, RepositoryRequest repositoryRequest )
throws ArtifactResolutionException, PluginVersionResolutionException;
}

View File

@ -0,0 +1,60 @@
package org.apache.maven.project;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
import java.util.List;
import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
import org.codehaus.plexus.classworlds.realm.ClassRealm;
/**
* Caches project class realms. <strong>Warning:</strong> This is an internal utility interface that is only public for
* technical reasons, it is not part of the public API. In particular, this interface can be changed or deleted without
* prior notice.
*
* @author Igor Fedorenko
* @author Benjamin Bentmann
*/
public interface ProjectRealmCache
{
public static class CacheRecord
{
public final ClassRealm realm;
public final ArtifactFilter extensionArtifactFilter;
public CacheRecord( ClassRealm realm, ArtifactFilter extensionArtifactFilter )
{
this.realm = realm;
this.extensionArtifactFilter = extensionArtifactFilter;
}
}
CacheRecord get( List<? extends ClassRealm> extensionRealms );
void put( List<? extends ClassRealm> extensionRealms, ClassRealm projectRealm,
ArtifactFilter extensionArtifactFilter );
void flush();
}

View File

@ -28,7 +28,6 @@ import org.apache.maven.artifact.repository.RepositoryRequest;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.model.Model;
import org.apache.maven.model.Repository;
import org.codehaus.plexus.classworlds.realm.ClassRealm;
import org.codehaus.plexus.component.annotations.Component;
/**
@ -56,10 +55,10 @@ public class EmptyProjectBuildingHelper
}
}
public ClassRealm createProjectRealm( Model model, RepositoryRequest repositoryRequest )
public ProjectRealmCache.CacheRecord createProjectRealm( Model model, RepositoryRequest repositoryRequest )
throws ArtifactResolutionException
{
return null;
return new ProjectRealmCache.CacheRecord( null, null );
}
}

View File

@ -0,0 +1,101 @@
package org.apache.maven.project;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import junit.framework.TestCase;
/**
* Tests {@link ExtensionDescriptorBuilder}.
*
* @author Benjamin Bentmann
*/
public class ExtensionDescriptorBuilderTest
extends TestCase
{
private ExtensionDescriptorBuilder builder;
@Override
protected void setUp()
throws Exception
{
super.setUp();
builder = new ExtensionDescriptorBuilder();
}
@Override
protected void tearDown()
throws Exception
{
builder = null;
super.tearDown();
}
private InputStream toStream( String xml )
{
try
{
return new ByteArrayInputStream( xml.getBytes( "UTF-8" ) );
}
catch ( UnsupportedEncodingException e )
{
throw new IllegalStateException( e );
}
}
public void testEmptyDescriptor()
throws Exception
{
String xml = "<extension></extension>";
ExtensionDescriptor ed = builder.build( toStream( xml ) );
assertNotNull( ed );
assertNotNull( ed.getExportedPackages() );
assertTrue( ed.getExportedPackages().isEmpty() );
assertNotNull( ed.getExportedArtifacts() );
assertTrue( ed.getExportedArtifacts().isEmpty() );
}
public void testCompleteDescriptor()
throws Exception
{
String xml =
"<?xml version='1.0' encoding='UTF-8'?>" + "<extension>" + "<exportedPackages>"
+ "<exportedPackage>a</exportedPackage>" + "<exportedPackage>b</exportedPackage>"
+ "<exportedPackage>c</exportedPackage>" + "</exportedPackages>" + "<exportedArtifacts>"
+ "<exportedArtifact>x</exportedArtifact>" + "<exportedArtifact>y</exportedArtifact>"
+ "<exportedArtifact> z </exportedArtifact>" + "</exportedArtifacts>" + "</extension>";
ExtensionDescriptor ed = builder.build( toStream( xml ) );
assertNotNull( ed );
assertEquals( Arrays.asList( "a", "b", "c" ), ed.getExportedPackages() );
assertEquals( Arrays.asList( "x", "y", "z" ), ed.getExportedArtifacts() );
}
}

12
pom.xml
View File

@ -38,11 +38,11 @@
<inceptionYear>2001</inceptionYear>
<properties>
<classWorldsVersion>2.1.0</classWorldsVersion>
<classWorldsVersion>2.2.0-SNAPSHOT</classWorldsVersion>
<commonsCliVersion>1.2</commonsCliVersion>
<easyMockVersion>1.2_Java1.3</easyMockVersion>
<junitVersion>3.8.2</junitVersion>
<plexusVersion>1.2.1</plexusVersion>
<plexusVersion>1.3.0-SNAPSHOT</plexusVersion>
<plexusInterpolationVersion>1.11</plexusInterpolationVersion>
<plexusPluginManagerVersion>1.0-alpha-1</plexusPluginManagerVersion>
<plexusUtilsVersion>2.0.0</plexusUtilsVersion>
@ -158,6 +158,14 @@
</site>
</distributionManagement>
<!-- FIXME: Remove once snapshots have been released -->
<repositories>
<repository>
<id>sonatype.public</id>
<url>http://repository.sonatype.org/content/groups/public/</url>
</repository>
</repositories>
<!--bootstrap-start-comment-->
<dependencyManagement>
<!--bootstrap-end-comment-->