/**
* Copyright (c) 2008-2011 Sonatype, Inc.
* All rights reserved. Includes the third-party code listed at http://www.sonatype.com/products/nexus/attributions.
*
* This program is free software: you can redistribute it and/or modify it only under the terms of the GNU Affero General
* Public License Version 3 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License Version 3
* for more details.
*
* You should have received a copy of the GNU Affero General Public License Version 3 along with this program. If not, see
* http://www.gnu.org/licenses.
*
* Sonatype Nexus (TM) Open Source Version is available from Sonatype, Inc. Sonatype and Sonatype Nexus are trademarks of
* Sonatype, Inc. Apache Maven is a trademark of the Apache Foundation. M2Eclipse is a trademark of the Eclipse Foundation.
* All other trademarks are the property of their respective owners.
*/
package org.sonatype.nexus.plugins;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.codehaus.plexus.DefaultPlexusContainer;
import org.codehaus.plexus.classworlds.realm.ClassRealm;
import org.codehaus.plexus.classworlds.realm.DuplicateRealmException;
import org.codehaus.plexus.classworlds.realm.NoSuchRealmException;
import org.sonatype.guice.bean.reflect.ClassSpace;
import org.sonatype.guice.bean.reflect.URLClassSpace;
import org.sonatype.guice.nexus.binders.NexusAnnotatedBeanModule;
import org.sonatype.guice.plexus.binders.PlexusXmlBeanModule;
import org.sonatype.guice.plexus.config.PlexusBeanModule;
import org.sonatype.inject.Parameters;
import org.sonatype.nexus.mime.MimeUtil;
import org.sonatype.nexus.plugins.events.PluginActivatedEvent;
import org.sonatype.nexus.plugins.events.PluginRejectedEvent;
import org.sonatype.nexus.plugins.repository.NoSuchPluginRepositoryArtifactException;
import org.sonatype.nexus.plugins.repository.PluginRepositoryArtifact;
import org.sonatype.nexus.plugins.repository.PluginRepositoryManager;
import org.sonatype.nexus.plugins.rest.NexusResourceBundle;
import org.sonatype.nexus.plugins.rest.StaticResource;
import org.sonatype.nexus.proxy.registry.RepositoryTypeDescriptor;
import org.sonatype.nexus.proxy.registry.RepositoryTypeRegistry;
import org.sonatype.plexus.appevents.ApplicationEventMulticaster;
import org.sonatype.plexus.appevents.Event;
import org.sonatype.plugin.metadata.GAVCoordinate;
import org.sonatype.plugins.model.ClasspathDependency;
import org.sonatype.plugins.model.PluginDependency;
import org.sonatype.plugins.model.PluginMetadata;
import com.google.inject.AbstractModule;
import com.google.inject.Module;
import com.google.inject.name.Names;
/**
* Default {@link NexusPluginManager} implementation backed by a {@link PluginRepositoryManager}.
*/
@Named
@Singleton
public final class DefaultNexusPluginManager
implements NexusPluginManager
{
// ----------------------------------------------------------------------
// Implementation fields
// ----------------------------------------------------------------------
@Inject
private PluginRepositoryManager repositoryManager;
@Inject
private ApplicationEventMulticaster eventMulticaster;
@Inject
private RepositoryTypeRegistry repositoryTypeRegistry;
@Inject
private MimeUtil mimeUtil;
@Inject
private DefaultPlexusContainer container;
@Inject
@Parameters
private Map<String, String> variables;
private final Map<GAVCoordinate, PluginDescriptor> activePlugins = new HashMap<GAVCoordinate, PluginDescriptor>();
private final Map<GAVCoordinate, PluginResponse> pluginResponses = new HashMap<GAVCoordinate, PluginResponse>();
// ----------------------------------------------------------------------
// Public methods
// ----------------------------------------------------------------------
public Map<GAVCoordinate, PluginDescriptor> getActivatedPlugins()
{
return new HashMap<GAVCoordinate, PluginDescriptor>( activePlugins );
}
public Map<GAVCoordinate, PluginMetadata> getInstalledPlugins()
{
return repositoryManager.findAvailablePlugins();
}
public Map<GAVCoordinate, PluginResponse> getPluginResponses()
{
return new HashMap<GAVCoordinate, PluginResponse>( pluginResponses );
}
public Collection<PluginManagerResponse> activateInstalledPlugins()
{
final List<PluginManagerResponse> result = new ArrayList<PluginManagerResponse>();
for ( final GAVCoordinate gav : repositoryManager.findAvailablePlugins().keySet() )
{
result.add( activatePlugin( gav ) );
}
return result;
}
public boolean isActivatedPlugin( final GAVCoordinate gav )
{
return activePlugins.containsKey( gav );
}
public PluginManagerResponse activatePlugin( final GAVCoordinate gav )
{
final PluginManagerResponse response = new PluginManagerResponse( gav, PluginActivationRequest.ACTIVATE );
if ( !activePlugins.containsKey( gav ) )
{
try
{
activatePlugin( repositoryManager.resolveArtifact( gav ), response );
}
catch ( final NoSuchPluginRepositoryArtifactException e )
{
reportMissingPlugin( response, e );
}
}
return response;
}
public PluginManagerResponse deactivatePlugin( final GAVCoordinate gav )
{
throw new UnsupportedOperationException(); // TODO
}
public boolean installPluginBundle( final URL bundle )
throws IOException
{
throw new UnsupportedOperationException(); // TODO
}
public boolean uninstallPluginBundle( final GAVCoordinate gav )
throws IOException
{
throw new UnsupportedOperationException(); // TODO
}
// ----------------------------------------------------------------------
// Implementation methods
// ----------------------------------------------------------------------
private void activatePlugin( final PluginRepositoryArtifact plugin, final PluginManagerResponse response )
throws NoSuchPluginRepositoryArtifactException
{
final GAVCoordinate pluginGAV = plugin.getCoordinate();
final PluginMetadata metadata = plugin.getPluginMetadata();
final PluginDescriptor descriptor = new PluginDescriptor( pluginGAV );
descriptor.setPluginMetadata( metadata );
final PluginResponse result = new PluginResponse( pluginGAV, PluginActivationRequest.ACTIVATE );
result.setPluginDescriptor( descriptor );
activePlugins.put( pluginGAV, descriptor );
final List<GAVCoordinate> importList = new ArrayList<GAVCoordinate>();
for ( final PluginDependency pd : metadata.getPluginDependencies() )
{
final GAVCoordinate gav = new GAVCoordinate( pd.getGroupId(), pd.getArtifactId(), pd.getVersion() );
response.addPluginManagerResponse( activatePlugin( gav ) );
importList.add( gav );
}
descriptor.setImportedPlugins( importList );
if ( !response.isSuccessful() )
{
result.setAchievedGoal( PluginActivationResult.BROKEN );
}
else
{
try
{
createPluginInjector( plugin, descriptor );
result.setAchievedGoal( PluginActivationResult.ACTIVATED );
}
catch ( final Throwable e )
{
result.setThrowable( e );
}
}
reportActivationResult( response, result );
}
private void createPluginInjector( final PluginRepositoryArtifact plugin, final PluginDescriptor descriptor )
throws NoSuchPluginRepositoryArtifactException
{
final String realmId = descriptor.getPluginCoordinates().toString();
final ClassRealm containerRealm = container.getContainerRealm();
ClassRealm pluginRealm;
try
{
pluginRealm = containerRealm.createChildRealm( realmId );
}
catch ( final DuplicateRealmException e1 )
{
try
{
pluginRealm = containerRealm.getWorld().getRealm( realmId );
}
catch ( final NoSuchRealmException e2 )
{
throw new IllegalStateException();
}
}
final List<URL> scanList = new ArrayList<URL>();
final URL pluginURL = toURL( plugin );
if ( null != pluginURL )
{
pluginRealm.addURL( pluginURL );
scanList.add( pluginURL );
}
for ( final ClasspathDependency d : descriptor.getPluginMetadata().getClasspathDependencies() )
{
final GAVCoordinate gav =
new GAVCoordinate( d.getGroupId(), d.getArtifactId(), d.getVersion(), d.getClassifier(), d.getType() );
final URL url = toURL( repositoryManager.resolveDependencyArtifact( plugin, gav ) );
if ( null != url )
{
pluginRealm.addURL( url );
if ( d.isHasComponents() )
{
scanList.add( url );
}
}
}
for ( final GAVCoordinate gav : descriptor.getImportedPlugins() )
{
final String importId = gav.toString();
for ( final String classname : activePlugins.get( gav ).getExportedClassnames() )
{
try
{
pluginRealm.importFrom( importId, classname );
}
catch ( final NoSuchRealmException e )
{
// should never happen
}
}
}
final List<String> exportedClassNames = new ArrayList<String>();
final List<RepositoryTypeDescriptor> repositoryTypes = new ArrayList<RepositoryTypeDescriptor>();
final List<StaticResource> staticResources = new ArrayList<StaticResource>();
final NexusResourceBundle resourceBundle = new NexusResourceBundle()
{
public List<StaticResource> getContributedResouces()
{
return staticResources;
}
};
final Module resourceModule = new AbstractModule()
{
@Override
protected void configure()
{
bind( NexusResourceBundle.class ).annotatedWith( Names.named( realmId ) ).toInstance( resourceBundle );
}
};
final List<PlexusBeanModule> beanModules = new ArrayList<PlexusBeanModule>();
final ClassSpace pluginSpace = new URLClassSpace( pluginRealm );
beanModules.add( new PlexusXmlBeanModule( pluginSpace, variables ) );
final ClassSpace annSpace = new URLClassSpace( pluginRealm, scanList.toArray( new URL[scanList.size()] ) );
beanModules.add( new NexusAnnotatedBeanModule( annSpace, variables, exportedClassNames, repositoryTypes ) );
container.addPlexusInjector( beanModules, resourceModule );
for ( final RepositoryTypeDescriptor r : repositoryTypes )
{
repositoryTypeRegistry.registerRepositoryTypeDescriptors( r );
}
final Enumeration<URL> e = pluginSpace.findEntries( "static/", null, true );
while ( e.hasMoreElements() )
{
final URL url = e.nextElement();
final String path = getPublishedPath( url );
if ( path != null )
{
staticResources.add( new PluginStaticResource( url, path, mimeUtil.getMimeType( url ) ) );
}
}
descriptor.setExportedClassnames( exportedClassNames );
descriptor.setRepositoryTypes( repositoryTypes );
descriptor.setStaticResources( staticResources );
}
private URL toURL( final PluginRepositoryArtifact artifact )
{
try
{
return artifact.getFile().toURI().toURL();
}
catch ( final MalformedURLException e )
{
return null; // should never happen
}
}
private String getPublishedPath( final URL resourceURL )
{
final String path = resourceURL.toExternalForm();
final int index = path.indexOf( "jar!/" );
return index > 0 ? path.substring( index + 4 ) : null;
}
private void reportMissingPlugin( final PluginManagerResponse response,
final NoSuchPluginRepositoryArtifactException cause )
{
final GAVCoordinate gav = cause.getCoordinate();
final PluginResponse result = new PluginResponse( gav, response.getRequest() );
result.setThrowable( cause );
result.setAchievedGoal( PluginActivationResult.MISSING );
response.addPluginResponse( result );
pluginResponses.put( gav, result );
}
private void reportActivationResult( final PluginManagerResponse response, final PluginResponse result )
{
final Event<NexusPluginManager> pluginEvent;
final GAVCoordinate gav = result.getPluginCoordinates();
if ( result.isSuccessful() )
{
pluginEvent = new PluginActivatedEvent( this, result.getPluginDescriptor() );
}
else
{
pluginEvent = new PluginRejectedEvent( this, gav, result.getThrowable() );
activePlugins.remove( gav );
}
response.addPluginResponse( result );
pluginResponses.put( gav, result );
eventMulticaster.notifyEventListeners( pluginEvent );
}
}