| /******************************************************************************* |
| * 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 ); |
| } |
| } |
| } |