blob: 4b9609f6229eb63b1546f978c93487d7eca9cbae [file] [log] [blame]
/*******************************************************************************
* 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
*******************************************************************************/
package org.eclipse.sisu.plexus;
import java.lang.annotation.Annotation;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.eclipse.sisu.bean.BeanProperty;
import org.eclipse.sisu.inject.DeferredClass;
import org.eclipse.sisu.inject.DeferredProvider;
import org.eclipse.sisu.space.ClassSpace;
import org.eclipse.sisu.space.LoadedClass;
import org.eclipse.sisu.space.URLClassSpace;
import org.eclipse.sisu.space.asm.ClassWriter;
import org.eclipse.sisu.space.asm.MethodVisitor;
import org.eclipse.sisu.space.asm.Opcodes;
import com.google.inject.TypeLiteral;
import junit.framework.TestCase;
public class PlexusXmlScannerTest
extends TestCase
{
static class NamedProperty
implements BeanProperty<Object>
{
final String name;
final TypeLiteral<Object> type;
public NamedProperty( final String name )
{
this.name = name;
type = TypeLiteral.get( Object.class );
}
@SuppressWarnings( { "unchecked", "rawtypes" } )
public NamedProperty( final String name, final TypeLiteral type )
{
this.name = name;
this.type = type;
}
public <A extends Annotation> A getAnnotation( final Class<A> annotationType )
{
return null;
}
public String getName()
{
return name;
}
public TypeLiteral<Object> getType()
{
return type;
}
public <B> void set( final B bean, final Object value )
{
}
}
interface Bean
{
}
protected static class DefaultBean
{
}
static class DebugBean
{
}
static class AnotherBean
{
}
static class EmptyClassSpace
implements ClassSpace
{
public Class<?> loadClass( final String name )
{
try
{
return Class.forName( name );
}
catch ( final ClassNotFoundException e )
{
throw new TypeNotPresentException( name, e );
}
}
@SuppressWarnings( { "unchecked", "rawtypes" } )
public DeferredClass<?> deferLoadClass( final String name )
{
return new LoadedClass( loadClass( name ) );
}
public URL getResource( final String name )
{
return null;
}
public Enumeration<URL> getResources( final String name )
{
// hide components.xml so we can just test plexus.xml parsing
return Collections.enumeration( Collections.<URL> emptyList() );
}
public Enumeration<URL> findEntries( final String path, final String glob, final boolean recurse )
{
// hide components.xml so we can just test plexus.xml parsing
return Collections.enumeration( Collections.<URL> emptyList() );
}
}
public void testLoadOnStart()
{
final URL plexusXml = getClass().getResource( "/META-INF/plexus/plexus.xml" );
final PlexusXmlScanner scanner = new PlexusXmlScanner( null, plexusXml, null );
final Map<Component, DeferredClass<?>> componentMap = scanner.scan( new EmptyClassSpace(), true );
assertEquals( 2, componentMap.size() );
final Component component1 =
new ComponentImpl( DefaultBean.class, Hints.DEFAULT_HINT, Strategies.LOAD_ON_START, "" );
assertEquals( DefaultBean.class, componentMap.get( component1 ).load() );
final Component component2 =
new ComponentImpl( Bean.class, "debug", Strategies.LOAD_ON_START, "For debugging" );
assertEquals( DebugBean.class, componentMap.get( component2 ).load() );
}
public void testBadPlexusXml()
{
final ClassSpace space = new URLClassSpace( PlexusXmlScannerTest.class.getClassLoader() );
final URL plexusXml = getClass().getResource( "/META-INF/plexus/bad_plexus_1.xml" );
new PlexusXmlScanner( null, plexusXml, null ).scan( space, true );
}
@SuppressWarnings( "deprecation" )
public void testComponents()
{
final ClassSpace space = new URLClassSpace( PlexusXmlScannerTest.class.getClassLoader() );
final Map<String, PlexusBeanMetadata> metadata = new HashMap<String, PlexusBeanMetadata>();
final PlexusXmlScanner scanner = new PlexusXmlScanner( null, null, metadata );
final Map<Component, DeferredClass<?>> componentMap = scanner.scan( space, true );
assertEquals( 6, componentMap.size() );
final Component component1 =
new ComponentImpl( DefaultBean.class, Hints.DEFAULT_HINT, Strategies.PER_LOOKUP, "" );
assertEquals( DefaultBean.class, componentMap.get( component1 ).load() );
final Component component2 = new ComponentImpl( Bean.class, "debug", Strategies.SINGLETON, "For debugging" );
assertEquals( DebugBean.class, componentMap.get( component2 ).load() );
final Component component3 = new ComponentImpl( Bean.class, Hints.DEFAULT_HINT, Strategies.SINGLETON, "" );
assertEquals( AnotherBean.class, componentMap.get( component3 ).load() );
final Component component4 = new ComponentImpl( Bean.class, "clone", Strategies.SINGLETON, "" );
assertEquals( DefaultBean.class, componentMap.get( component4 ).load().getSuperclass() );
final Class<?> proxy = CustomTestClassLoader.proxy( componentMap.get( component4 ).load() );
try
{
assertNotNull( proxy.getMethod( "TestMe" ) );
}
catch ( final NoSuchMethodException e )
{
fail( "Proxied class is missing 'TestMe' method" );
}
final PlexusBeanMetadata metadata1 = metadata.get( DefaultBean.class.getName() );
assertFalse( metadata1.isEmpty() );
assertEquals( new ConfigurationImpl( "someFieldName",
"<some-field-name><item>PRIMARY</item></some-field-name>" ),
metadata1.getConfiguration( new NamedProperty( "someFieldName" ) ) );
assertEquals( new ConfigurationImpl( "simple", "value" ),
metadata1.getConfiguration( new NamedProperty( "simple" ) ) );
assertEquals( new ConfigurationImpl( "value", "<value with=\"attribute\"></value>" ),
metadata1.getConfiguration( new NamedProperty( "value" ) ) );
assertEquals( new ConfigurationImpl( "emptyValue1", "<empty-value1 with=\"attribute\" />" ),
metadata1.getConfiguration( new NamedProperty( "emptyValue1" ) ) );
assertEquals( new ConfigurationImpl( "emptyValue2", "" ),
metadata1.getConfiguration( new NamedProperty( "emptyValue2" ) ) );
assertFalse( metadata1.isEmpty() );
assertEquals( new RequirementImpl( Bean.class, true, "debug" ),
metadata1.getRequirement( new NamedProperty( "bean", TypeLiteral.get( Bean.class ) ) ) );
assertFalse( metadata1.isEmpty() );
metadata1.getConfiguration( new NamedProperty( "foo" ) );
assertEquals( new RequirementImpl( Bean.class, false, Hints.DEFAULT_HINT, "debug" ),
metadata1.getRequirement( new NamedProperty( "beanMap" ) ) );
assertFalse( metadata1.isEmpty() );
assertEquals( new RequirementImpl( Bean.class, false ),
metadata1.getRequirement( new NamedProperty( "beanField" ) ) );
assertTrue( metadata1.isEmpty() );
assertNotNull( metadata.get( AnotherBean.class.getName() ) );
assertNull( metadata.get( DebugBean.class.getName() ) );
}
static class FixedClassSpace
implements ClassSpace
{
final String fixedResourceName;
FixedClassSpace( final String fixedResourceName )
{
this.fixedResourceName = fixedResourceName;
}
public Class<?> loadClass( final String name )
{
try
{
return Class.forName( name );
}
catch ( final ClassNotFoundException e )
{
throw new TypeNotPresentException( name, e );
}
}
@SuppressWarnings( "rawtypes" )
public DeferredClass<?> deferLoadClass( final String name )
{
return new DeferredClass()
{
public Class load()
{
return loadClass( name );
}
public String getName()
{
return name;
}
public DeferredProvider asProvider()
{
throw new UnsupportedOperationException();
}
};
}
public URL getResource( final String name )
{
return getClass().getResource( fixedResourceName );
}
public Enumeration<URL> getResources( final String name )
{
return Collections.enumeration( Collections.singleton( getClass().getResource( fixedResourceName ) ) );
}
public Enumeration<URL> findEntries( final String path, final String glob, final boolean recurse )
{
return Collections.enumeration( Collections.singleton( getClass().getResource( fixedResourceName ) ) );
}
}
public void testBadComponentsXml()
{
ClassSpace space;
space = new FixedClassSpace( "/META-INF/plexus/bad_components_1.xml" );
new PlexusXmlScanner( null, null, null ).scan( space, true );
space = new FixedClassSpace( "/META-INF/plexus/bad_components_2.xml" );
new PlexusXmlScanner( null, null, null ).scan( space, true );
try
{
space = new FixedClassSpace( "/META-INF/plexus/bad_components_3.xml" );
final Map<String, PlexusBeanMetadata> metadata = new HashMap<String, PlexusBeanMetadata>();
final PlexusXmlScanner scanner = new PlexusXmlScanner( null, null, metadata );
scanner.scan( space, true );
final Requirement badReq =
metadata.get( DefaultBean.class.getName() ).getRequirement( new NamedProperty( "no.such.class" ) );
badReq.role();
fail( "Expected TypeNotPresentException" );
}
catch ( final TypeNotPresentException e )
{
}
space = new FixedClassSpace( "/META-INF/plexus/bad_components_4.xml" );
final PlexusXmlScanner scanner = new PlexusXmlScanner( null, null, null );
assertTrue( scanner.scan( space, true ).isEmpty() );
}
public void testInterpolatedComponentsXml()
{
final ClassSpace space = new FixedClassSpace( "/META-INF/plexus/variable_components.xml" );
final Map<String, PlexusBeanMetadata> metadata = new HashMap<String, PlexusBeanMetadata>();
new PlexusXmlScanner( null, null, metadata ).scan( space, true );
assertEquals( "${some.value}",
metadata.get( DefaultBean.class.getName() ).getConfiguration( new NamedProperty( "variable" ) ).value() );
final Map<?, ?> variables = Collections.singletonMap( "some.value", "INTERPOLATED" );
new PlexusXmlScanner( variables, null, metadata ).scan( space, true );
assertEquals( "INTERPOLATED",
metadata.get( DefaultBean.class.getName() ).getConfiguration( new NamedProperty( "variable" ) ).value() );
}
public void testLocalizedXmlScanning()
{
final ClassSpace space = new URLClassSpace( PlexusXmlScannerTest.class.getClassLoader(), null );
assertFalse( new PlexusXmlScanner( null, null, null ).scan( space, true ).isEmpty() );
assertTrue( new PlexusXmlScanner( null, null, null ).scan( space, false ).isEmpty() );
}
public void testOptionalLogging()
throws Exception
{
final Level level = Logger.getLogger( "" ).getLevel();
try
{
Logger.getLogger( "" ).setLevel( Level.SEVERE );
// check everything still works without any SLF4J jars
final ClassLoader noLoggingLoader =
new URLClassLoader( new URLClassSpace( getClass().getClassLoader() ).getURLs(), null )
{
@Override
protected synchronized Class<?> loadClass( final String name, final boolean resolve )
throws ClassNotFoundException
{
if ( name.contains( "slf4j" ) )
{
throw new ClassNotFoundException( name );
}
if ( name.contains( "cobertura" ) )
{
return PlexusXmlScannerTest.class.getClassLoader().loadClass( name );
}
return super.loadClass( name, resolve );
}
};
noLoggingLoader.loadClass( SimpleScanningExample.class.getName() ).newInstance();
}
finally
{
Logger.getLogger( "" ).setLevel( level );
}
}
static final class CustomTestClassLoader
extends ClassLoader
{
private static final String PROXY_MARKER = "$proxy";
CustomTestClassLoader( final ClassLoader parent )
{
super( parent );
}
static Class<?> proxy( final Class<?> clazz )
{
try
{
return new CustomTestClassLoader( clazz.getClassLoader() ).loadClass( clazz.getName() + PROXY_MARKER );
}
catch ( final ClassNotFoundException e )
{
throw new TypeNotPresentException( clazz.getName(), e );
}
}
@Override
protected synchronized Class<?> loadClass( final String name, final boolean resolve )
throws ClassNotFoundException
{
return super.loadClass( name, resolve );
}
@Override
protected Class<?> findClass( final String name )
throws ClassNotFoundException
{
final String proxyName = name.replace( '.', '/' );
final String superName = proxyName.substring( 0, proxyName.length() - PROXY_MARKER.length() );
final ClassWriter cw = new ClassWriter( ClassWriter.COMPUTE_MAXS );
cw.visit( Opcodes.V1_5, Modifier.PUBLIC | Modifier.FINAL, proxyName, null, superName, null );
MethodVisitor mv = cw.visitMethod( Modifier.PUBLIC, "<init>", "()V", null, null );
mv.visitCode();
mv.visitVarInsn( Opcodes.ALOAD, 0 );
mv.visitMethodInsn( Opcodes.INVOKESPECIAL, superName, "<init>", "()V", false );
mv.visitInsn( Opcodes.RETURN );
mv.visitMaxs( 0, 0 );
mv.visitEnd();
mv = cw.visitMethod( Modifier.PUBLIC, "TestMe", "()V", null, null );
mv.visitCode();
mv.visitInsn( Opcodes.RETURN );
mv.visitMaxs( 0, 0 );
mv.visitEnd();
cw.visitEnd();
final byte[] buf = cw.toByteArray();
return defineClass( name, buf, 0, buf.length );
}
}
}