/******************************************************************************* * Copyright (c) 2010-present Sonatype, Inc. * 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: * Stuart McCulloch (Sonatype, Inc.) - initial API and implementation * * Minimal facade required to be binary-compatible with legacy Plexus API *******************************************************************************/ package org.codehaus.plexus; import java.io.File; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.IdentityHashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import javax.inject.Provider; import org.codehaus.plexus.classworlds.ClassWorld; import org.codehaus.plexus.classworlds.realm.ClassRealm; import org.codehaus.plexus.classworlds.realm.DuplicateRealmException; import org.codehaus.plexus.classworlds.realm.NoSuchRealmException; import org.codehaus.plexus.component.repository.ComponentDescriptor; import org.codehaus.plexus.component.repository.exception.ComponentLookupException; import org.codehaus.plexus.context.Context; import org.codehaus.plexus.context.ContextMapAdapter; import org.codehaus.plexus.context.DefaultContext; import org.codehaus.plexus.logging.Logger; import org.codehaus.plexus.logging.LoggerManager; import org.codehaus.plexus.logging.console.ConsoleLoggerManager; import org.eclipse.sisu.bean.BeanManager; import org.eclipse.sisu.bean.LifecycleManager; import org.eclipse.sisu.inject.DefaultBeanLocator; import org.eclipse.sisu.inject.DefaultRankingFunction; import org.eclipse.sisu.inject.DeferredClass; import org.eclipse.sisu.inject.DeferredProvider; import org.eclipse.sisu.inject.InjectorBindings; import org.eclipse.sisu.inject.MutableBeanLocator; import org.eclipse.sisu.inject.RankingFunction; import org.eclipse.sisu.plexus.ComponentDescriptorBeanModule; import org.eclipse.sisu.plexus.DefaultPlexusBeanLocator; import org.eclipse.sisu.plexus.Hints; import org.eclipse.sisu.plexus.PlexusAnnotatedBeanModule; import org.eclipse.sisu.plexus.PlexusBean; import org.eclipse.sisu.plexus.PlexusBeanConverter; import org.eclipse.sisu.plexus.PlexusBeanLocator; import org.eclipse.sisu.plexus.PlexusBeanModule; import org.eclipse.sisu.plexus.PlexusBindingModule; import org.eclipse.sisu.plexus.PlexusDateTypeConverter; import org.eclipse.sisu.plexus.PlexusLifecycleManager; import org.eclipse.sisu.plexus.PlexusXmlBeanConverter; import org.eclipse.sisu.plexus.PlexusXmlBeanModule; import org.eclipse.sisu.plexus.RealmManager; import org.eclipse.sisu.space.BeanScanning; import org.eclipse.sisu.space.ClassSpace; import org.eclipse.sisu.space.LoadedClass; import org.eclipse.sisu.space.URLClassSpace; import org.eclipse.sisu.wire.EntryListAdapter; import org.eclipse.sisu.wire.EntryMapAdapter; import org.eclipse.sisu.wire.MergedModule; import org.eclipse.sisu.wire.ParameterKeys; import org.eclipse.sisu.wire.WireModule; import com.google.inject.Binder; import com.google.inject.Guice; import com.google.inject.Inject; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.Module; import com.google.inject.TypeLiteral; import com.google.inject.name.Names; import com.google.inject.util.Providers; /** * {@link PlexusContainer} shim that delegates to a Plexus-aware Guice {@link Injector}. */ @SuppressWarnings( { "unchecked", "rawtypes" } ) public final class DefaultPlexusContainer implements MutablePlexusContainer { // ---------------------------------------------------------------------- // Static initialization // ---------------------------------------------------------------------- static { System.setProperty( "guice.disable.misplaced.annotation.check", "true" ); } // ---------------------------------------------------------------------- // Constants // ---------------------------------------------------------------------- private static final String DEFAULT_REALM_NAME = "plexus.core"; private static final Module[] NO_CUSTOM_MODULES = {}; // ---------------------------------------------------------------------- // Implementation fields // ---------------------------------------------------------------------- final AtomicInteger plexusRank = new AtomicInteger(); final Map<ClassRealm, List<ComponentDescriptor<?>>> descriptorMap = new IdentityHashMap<ClassRealm, List<ComponentDescriptor<?>>>(); final ThreadLocal<ClassRealm> lookupRealm = new ThreadLocal<ClassRealm>(); final LoggerManagerProvider loggerManagerProvider = new LoggerManagerProvider(); final MutableBeanLocator qualifiedBeanLocator = new DefaultBeanLocator(); final Context context; final Map<?, ?> variables; final ClassRealm containerRealm; final RealmManager realmManager; final PlexusBeanLocator plexusBeanLocator; final BeanManager plexusBeanManager; private final String componentVisibility; private final boolean isAutoWiringEnabled; private final BeanScanning scanning; private final Module containerModule = new ContainerModule(); private final Module defaultsModule = new DefaultsModule(); private LoggerManager loggerManager = new ConsoleLoggerManager(); private Logger logger; private boolean disposing; // ---------------------------------------------------------------------- // Constructors // ---------------------------------------------------------------------- public DefaultPlexusContainer() throws PlexusContainerException { this( new DefaultContainerConfiguration() ); } public DefaultPlexusContainer( final ContainerConfiguration configuration ) throws PlexusContainerException { this( configuration, NO_CUSTOM_MODULES ); } @SuppressWarnings( "finally" ) public DefaultPlexusContainer( final ContainerConfiguration configuration, final Module... customModules ) throws PlexusContainerException { final URL plexusXml = lookupPlexusXml( configuration ); context = getContextComponent( configuration ); context.put( PlexusConstants.PLEXUS_KEY, this ); variables = new ContextMapAdapter( context ); containerRealm = lookupContainerRealm( configuration ); realmManager = new RealmManager( qualifiedBeanLocator ); containerRealm.getWorld().addListener( realmManager ); componentVisibility = configuration.getComponentVisibility(); isAutoWiringEnabled = configuration.getAutoWiring(); scanning = parseScanningOption( configuration.getClassPathScanning() ); plexusBeanLocator = new DefaultPlexusBeanLocator( qualifiedBeanLocator, realmManager, componentVisibility ); final BeanManager jsr250Lifecycle = configuration.getJSR250Lifecycle() ? new LifecycleManager() : null; plexusBeanManager = new PlexusLifecycleManager( Providers.of( context ), loggerManagerProvider, // new SLF4JLoggerFactoryProvider(), jsr250Lifecycle ); setLookupRealm( containerRealm ); final List<PlexusBeanModule> beanModules = new ArrayList<PlexusBeanModule>(); final ClassSpace space = new URLClassSpace( containerRealm ); beanModules.add( new PlexusXmlBeanModule( space, variables, plexusXml ) ); final BeanScanning global = BeanScanning.INDEX == scanning ? BeanScanning.GLOBAL_INDEX : scanning; beanModules.add( new PlexusAnnotatedBeanModule( space, variables, global ) ); try { addPlexusInjector( beanModules, new BootModule( customModules ) ); } catch ( final RuntimeException e ) { try { dispose(); // cleanup as much as possible } finally { throw e; // always report original failure } } } // ---------------------------------------------------------------------- // Context methods // ---------------------------------------------------------------------- public Context getContext() { return context; } // ---------------------------------------------------------------------- // Lookup methods // ---------------------------------------------------------------------- public Object lookup( final String role ) throws ComponentLookupException { return lookup( role, "" ); } public Object lookup( final String role, final String hint ) throws ComponentLookupException { return lookup( null, role, hint ); } public <T> T lookup( final Class<T> role ) throws ComponentLookupException { return lookup( role, "" ); } public <T> T lookup( final Class<T> role, final String hint ) throws ComponentLookupException { return lookup( role, null, hint ); } public <T> T lookup( final Class<T> type, final String role, final String hint ) throws ComponentLookupException { try { return locate( role, type, hint ).iterator().next().getValue(); } catch ( final RuntimeException e ) { throw new ComponentLookupException( e, null != type ? type.getName() : role, hint ); } } public List<Object> lookupList( final String role ) throws ComponentLookupException { return new EntryListAdapter<Object>( locate( role, null ) ); } public <T> List<T> lookupList( final Class<T> role ) throws ComponentLookupException { return new EntryListAdapter<T>( locate( null, role ) ); } public Map<String, Object> lookupMap( final String role ) throws ComponentLookupException { return new EntryMapAdapter<String, Object>( locate( role, null ) ); } public <T> Map<String, T> lookupMap( final Class<T> role ) throws ComponentLookupException { return new EntryMapAdapter<String, T>( locate( null, role ) ); } // ---------------------------------------------------------------------- // Query methods // ---------------------------------------------------------------------- public boolean hasComponent( final String role ) { return hasComponent( role, "" ); } public boolean hasComponent( final String role, final String hint ) { return hasComponent( null, role, hint ); } public boolean hasComponent( final Class role ) { return hasComponent( role, "" ); } public boolean hasComponent( final Class role, final String hint ) { return hasComponent( role, null, hint ); } public boolean hasComponent( final Class type, final String role, final String hint ) { return hasPlexusBeans( locate( role, type, hint ) ); } // ---------------------------------------------------------------------- // Component descriptor methods // ---------------------------------------------------------------------- public void addComponent( final Object component, final String role ) { try { addComponent( component, component.getClass().getClassLoader().loadClass( role ), Hints.DEFAULT_HINT ); } catch ( final ClassNotFoundException e ) { throw new TypeNotPresentException( role, e ); } } public <T> void addComponent( final T component, final Class<?> role, final String hint ) { // this is only used in Maven3 tests, so keep it simple... qualifiedBeanLocator.add( new InjectorBindings( Guice.createInjector( new Module() { public void configure( final Binder binder ) { if ( Hints.isDefaultHint( hint ) ) { binder.bind( (Class) role ).toInstance( component ); } else { binder.bind( (Class) role ).annotatedWith( Names.named( hint ) ).toInstance( component ); } } } ), new DefaultRankingFunction( plexusRank.incrementAndGet() ) ) ); } public <T> void addComponentDescriptor( final ComponentDescriptor<T> descriptor ) { ClassRealm realm = descriptor.getRealm(); if ( null == realm ) { realm = containerRealm; descriptor.setRealm( realm ); } synchronized ( descriptorMap ) { List<ComponentDescriptor<?>> descriptors = descriptorMap.get( realm ); if ( null == descriptors ) { descriptors = new ArrayList<ComponentDescriptor<?>>(); descriptorMap.put( realm, descriptors ); } descriptors.add( descriptor ); } if ( containerRealm == realm ) { discoverComponents( containerRealm ); // for Maven3 testing } } public ComponentDescriptor<?> getComponentDescriptor( final String role, final String hint ) { return getComponentDescriptor( null, role, hint ); } public <T> ComponentDescriptor<T> getComponentDescriptor( final Class<T> type, final String role, final String hint ) { final Iterator<PlexusBean<T>> i = locate( role, type, hint ).iterator(); if ( i.hasNext() ) { final PlexusBean<T> bean = i.next(); if ( bean.getImplementationClass() != null ) { return newComponentDescriptor( role, bean ); } } return null; } public List getComponentDescriptorList( final String role ) { return getComponentDescriptorList( null, role ); } public <T> List<ComponentDescriptor<T>> getComponentDescriptorList( final Class<T> type, final String role ) { final List<ComponentDescriptor<T>> tempList = new ArrayList<ComponentDescriptor<T>>(); for ( final PlexusBean<T> bean : locate( role, type ) ) { tempList.add( newComponentDescriptor( role, bean ) ); } return tempList; } public Map getComponentDescriptorMap( final String role ) { return getComponentDescriptorMap( null, role ); } public <T> Map<String, ComponentDescriptor<T>> getComponentDescriptorMap( final Class<T> type, final String role ) { final Map<String, ComponentDescriptor<T>> tempMap = new LinkedHashMap<String, ComponentDescriptor<T>>(); for ( final PlexusBean<T> bean : locate( role, type ) ) { tempMap.put( bean.getKey(), newComponentDescriptor( role, bean ) ); } return tempMap; } public List<ComponentDescriptor<?>> discoverComponents( final ClassRealm realm ) { return discoverComponents( realm, NO_CUSTOM_MODULES ); } public List<ComponentDescriptor<?>> discoverComponents( final ClassRealm realm, final Module... customModules ) { try { final List<PlexusBeanModule> beanModules = new ArrayList<PlexusBeanModule>(); synchronized ( descriptorMap ) { final ClassSpace space = new URLClassSpace( realm ); final List<ComponentDescriptor<?>> descriptors = descriptorMap.remove( realm ); if ( null != descriptors ) { beanModules.add( new ComponentDescriptorBeanModule( space, descriptors ) ); } if ( containerRealm != realm && !realmManager.isManaged( realm ) ) { beanModules.add( new PlexusXmlBeanModule( space, variables ) ); final BeanScanning local = BeanScanning.GLOBAL_INDEX == scanning ? BeanScanning.INDEX : scanning; beanModules.add( new PlexusAnnotatedBeanModule( space, variables, local ) ); } } if ( !beanModules.isEmpty() ) { realmManager.manage( realm, addPlexusInjector( beanModules, customModules ) ); } } catch ( final RuntimeException e ) { getLogger().warn( realm.toString(), e ); } return null; // no-one actually seems to use or check the returned component list! } public Injector addPlexusInjector( final List<? extends PlexusBeanModule> beanModules, final Module... customModules ) { final List<Module> modules = new ArrayList<Module>(); modules.add( containerModule ); Collections.addAll( modules, customModules ); modules.add( new PlexusBindingModule( plexusBeanManager, beanModules ) ); modules.add( defaultsModule ); return Guice.createInjector( isAutoWiringEnabled ? new WireModule( modules ) : new MergedModule( modules ) ); } // ---------------------------------------------------------------------- // Class realm methods // ---------------------------------------------------------------------- public ClassWorld getClassWorld() { return containerRealm.getWorld(); } public ClassRealm getContainerRealm() { return containerRealm; } public ClassRealm setLookupRealm( final ClassRealm realm ) { final ClassRealm oldRealm = lookupRealm.get(); lookupRealm.set( realm ); return oldRealm; } public ClassRealm getLookupRealm() { return lookupRealm.get(); } public ClassRealm createChildRealm( final String id ) { try { return containerRealm.createChildRealm( id ); } catch ( final DuplicateRealmException e1 ) { try { return getClassWorld().getRealm( id ); } catch ( final NoSuchRealmException e2 ) { return null; // should never happen! } } } // ---------------------------------------------------------------------- // Logger methods // ---------------------------------------------------------------------- public synchronized LoggerManager getLoggerManager() { return loggerManager; } @Inject( optional = true ) public synchronized void setLoggerManager( final LoggerManager loggerManager ) { if ( null != loggerManager ) { this.loggerManager = loggerManager; } else { this.loggerManager = new ConsoleLoggerManager(); } logger = null; // refresh our local logger } public synchronized Logger getLogger() { if ( null == logger ) { logger = loggerManager.getLoggerForComponent( PlexusContainer.class.getName(), null ); } return logger; } // ---------------------------------------------------------------------- // Shutdown methods // ---------------------------------------------------------------------- public void release( final Object component ) { plexusBeanManager.unmanage( component ); } public void releaseAll( final Map<String, ?> components ) { for ( final Object o : components.values() ) { release( o ); } } public void releaseAll( final List<?> components ) { for ( final Object o : components ) { release( o ); } } public void dispose() { disposing = true; plexusBeanManager.unmanage(); containerRealm.setParentRealm( null ); qualifiedBeanLocator.clear(); lookupRealm.remove(); containerRealm.getWorld().removeListener( realmManager ); } // ---------------------------------------------------------------------- // Implementation methods // ---------------------------------------------------------------------- private static BeanScanning parseScanningOption( final String scanning ) { for ( final BeanScanning option : BeanScanning.values() ) { if ( option.name().equalsIgnoreCase( scanning ) ) { return option; } } return BeanScanning.OFF; } /** * Finds container {@link ClassRealm}, taking existing {@link ClassWorld}s or {@link ClassLoader}s into account. * * @param configuration The container configuration * @return Container class realm */ private static ClassRealm lookupContainerRealm( final ContainerConfiguration configuration ) throws PlexusContainerException { ClassRealm realm = configuration.getRealm(); if ( null == realm ) { ClassWorld world = configuration.getClassWorld(); if ( null == world ) { world = new ClassWorld( DEFAULT_REALM_NAME, Thread.currentThread().getContextClassLoader() ); } try { realm = world.getRealm( DEFAULT_REALM_NAME ); } catch ( final NoSuchRealmException e ) { final Iterator<?> realmIterator = world.getRealms().iterator(); if ( realmIterator.hasNext() ) { realm = (ClassRealm) realmIterator.next(); } } } if ( null == realm ) { throw new PlexusContainerException( "Missing container class realm: " + DEFAULT_REALM_NAME ); } return realm; } /** * Finds container configuration URL, may search the container {@link ClassRealm} and local file-system. * * @param configuration The container configuration * @return Local or remote URL */ private URL lookupPlexusXml( final ContainerConfiguration configuration ) { URL url = configuration.getContainerConfigurationURL(); if ( null == url ) { final String configurationPath = configuration.getContainerConfiguration(); if ( null != configurationPath ) { int index = 0; while ( index < configurationPath.length() && configurationPath.charAt( index ) == '/' ) { index++; } url = getClass().getClassLoader().getResource( configurationPath.substring( index ) ); if ( null == url ) { final File file = new File( configurationPath ); if ( file.isFile() ) { try { url = file.toURI().toURL(); } catch ( final MalformedURLException e ) // NOPMD { // drop through and recover } } } if ( null == url ) { getLogger().debug( "Missing container configuration: " + configurationPath ); } } } return url; } private static Context getContextComponent( final ContainerConfiguration configuration ) { final Map<?, ?> contextData = configuration.getContext(); final Context contextComponent = configuration.getContextComponent(); if ( null == contextComponent ) { return new DefaultContext( contextData ); } if ( null != contextData ) { for ( final Entry<?, ?> entry : contextData.entrySet() ) { contextComponent.put( entry.getKey(), entry.getValue() ); } } return contextComponent; } private <T> Iterable<PlexusBean<T>> locate( final String role, final Class<T> type, final String... hints ) { if ( disposing ) { return Collections.EMPTY_SET; } final String[] canonicalHints = Hints.canonicalHints( hints ); if ( null == role || null != type && type.getName().equals( role ) ) { return plexusBeanLocator.locate( TypeLiteral.get( type ), canonicalHints ); } final Set<Class> candidates = new HashSet<Class>(); for ( final ClassRealm realm : getVisibleRealms() ) { try { final Class clazz = realm.loadClass( role ); if ( candidates.add( clazz ) ) { final Iterable beans = plexusBeanLocator.locate( TypeLiteral.get( clazz ), canonicalHints ); if ( hasPlexusBeans( beans ) ) { return beans; } } } catch ( final Exception e ) { // drop through... } catch ( final LinkageError e ) { // drop through... } } return Collections.EMPTY_SET; } private Collection<ClassRealm> getVisibleRealms() { final Object[] realms = getClassWorld().getRealms().toArray(); final Set<ClassRealm> visibleRealms = new LinkedHashSet<ClassRealm>( realms.length ); final ClassRealm currentLookupRealm = getLookupRealm(); if ( null != currentLookupRealm ) { visibleRealms.add( currentLookupRealm ); } final ClassRealm threadContextRealm = RealmManager.contextRealm(); if ( null != threadContextRealm ) { visibleRealms.add( threadContextRealm ); } if ( PlexusConstants.REALM_VISIBILITY.equalsIgnoreCase( componentVisibility ) ) { final Collection<String> realmNames = realmManager.visibleRealmNames( threadContextRealm ); if ( null != realmNames && realmNames.size() > 0 ) { for ( int i = realms.length - 1; i >= 0; i-- ) { final ClassRealm r = (ClassRealm) realms[i]; if ( realmNames.contains( r.toString() ) ) { visibleRealms.add( r ); } } return visibleRealms; } } for ( int i = realms.length - 1; i >= 0; i-- ) { visibleRealms.add( (ClassRealm) realms[i] ); } return visibleRealms; } private static <T> boolean hasPlexusBeans( final Iterable<PlexusBean<T>> beans ) { final Iterator<PlexusBean<T>> i = beans.iterator(); return i.hasNext() && i.next().getImplementationClass() != null; } private static <T> ComponentDescriptor<T> newComponentDescriptor( final String role, final PlexusBean<T> bean ) { final ComponentDescriptor<T> cd = new ComponentDescriptor<T>(); cd.setRole( role ); cd.setRoleHint( bean.getKey() ); cd.setImplementationClass( bean.getImplementationClass() ); cd.setDescription( bean.getDescription() ); return cd; } final class BootModule implements Module { private final Module[] customBootModules; BootModule( final Module[] customBootModules ) { this.customBootModules = customBootModules; } public void configure( final Binder binder ) { binder.requestInjection( DefaultPlexusContainer.this ); for ( final Module m : customBootModules ) { binder.install( m ); } } } final class ContainerModule implements Module { public void configure( final Binder binder ) { binder.bind( Context.class ).toInstance( context ); binder.bind( ParameterKeys.PROPERTIES ).toInstance( context.getContextData() ); binder.bind( MutableBeanLocator.class ).toInstance( qualifiedBeanLocator ); binder.bind( PlexusBeanLocator.class ).toInstance( plexusBeanLocator ); binder.bind( BeanManager.class ).toInstance( plexusBeanManager ); binder.bind( PlexusContainer.class ).to( MutablePlexusContainer.class ); binder.bind( MutablePlexusContainer.class ).to( DefaultPlexusContainer.class ); // use provider wrapper to avoid repeated injections later on when configuring plugin injectors binder.bind( DefaultPlexusContainer.class ).toProvider( Providers.of( DefaultPlexusContainer.this ) ); } } final class DefaultsModule implements Module { private final LoggerProvider loggerProvider = new LoggerProvider(); private final PlexusDateTypeConverter dateConverter = new PlexusDateTypeConverter(); public void configure( final Binder binder ) { binder.bind( LoggerManager.class ).toProvider( loggerManagerProvider ); binder.bind( Logger.class ).toProvider( loggerProvider ); // allow plugins to override the default ranking function so we can support component profiles final Key<RankingFunction> plexusRankingKey = Key.get( RankingFunction.class, Names.named( "plexus" ) ); binder.bind( plexusRankingKey ).toInstance( new DefaultRankingFunction( plexusRank.incrementAndGet() ) ); binder.bind( RankingFunction.class ).to( plexusRankingKey ); binder.install( dateConverter ); binder.bind( PlexusBeanConverter.class ).to( PlexusXmlBeanConverter.class ); } } final class LoggerManagerProvider implements DeferredProvider<LoggerManager> { public LoggerManager get() { return getLoggerManager(); } public DeferredClass<LoggerManager> getImplementationClass() { return new LoadedClass<LoggerManager>( get().getClass() ); } } final class LoggerProvider implements DeferredProvider<Logger> { public Logger get() { return getLogger(); } public DeferredClass<Logger> getImplementationClass() { return new LoadedClass<Logger>( get().getClass() ); } } final class SLF4JLoggerFactoryProvider implements Provider<Object> { public Object get() { return plexusBeanLocator.locate( TypeLiteral.get( org.slf4j.ILoggerFactory.class ) ).iterator().next().getValue(); } } }