Browse Source

ClassLoader hierarchy changes:

* Use multi-parent ClassLoaders for antlibs and extensions, so that each
  extension jar is loaded by a single ClassLoader in the hierarchy.  Allows
  classes from extensions to be shared across dependent antlibs and extensions.

* Changed contract of ClassLoaderManager.createClassLoader( File[] ), so that
  it creates a new ClassLoader each time it is called.

* Changed ExtensionManager, so that it no longer extends PackageRepository.

* Added a few test cases for DefaultClassLoaderManager.

* Moved responsibility for checking myrmidon.home and building the various paths,
  from DefaultEmbeddor and DefaultExtensionManager to EmbeddedAnt.  Use the
  platform path separator for the paths, rather than the | char.

* Use EmbeddedAnt in AbstractProjectTest, rather than using an Embeddor directly.

* AbstractComponentTest was not parameterising or initialising the test
  components.


git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@272180 13f79535-47bb-0310-9956-ffa450edef68
master
adammurdoch 23 years ago
parent
commit
b91e4a3c1d
30 changed files with 1123 additions and 353 deletions
  1. +56
    -1
      proposal/myrmidon/build.xml
  2. +13
    -11
      proposal/myrmidon/docs/todo.html
  3. +154
    -87
      proposal/myrmidon/src/java/org/apache/myrmidon/components/classloader/DefaultClassLoaderManager.java
  4. +141
    -0
      proposal/myrmidon/src/java/org/apache/myrmidon/components/classloader/MultiParentURLClassLoader.java
  5. +5
    -6
      proposal/myrmidon/src/java/org/apache/myrmidon/components/classloader/Resources.properties
  6. +1
    -1
      proposal/myrmidon/src/java/org/apache/myrmidon/components/deployer/DefaultDeployer.java
  7. +2
    -104
      proposal/myrmidon/src/java/org/apache/myrmidon/components/embeddor/DefaultEmbeddor.java
  8. +0
    -4
      proposal/myrmidon/src/java/org/apache/myrmidon/components/embeddor/Resources.properties
  9. +25
    -6
      proposal/myrmidon/src/java/org/apache/myrmidon/components/extensions/DefaultExtensionManager.java
  10. +3
    -3
      proposal/myrmidon/src/java/org/apache/myrmidon/frontends/CLIMain.java
  11. +83
    -24
      proposal/myrmidon/src/java/org/apache/myrmidon/frontends/EmbeddedAnt.java
  12. +5
    -1
      proposal/myrmidon/src/java/org/apache/myrmidon/frontends/Resources.properties
  13. +15
    -6
      proposal/myrmidon/src/java/org/apache/myrmidon/interfaces/classloader/ClassLoaderManager.java
  14. +11
    -3
      proposal/myrmidon/src/java/org/apache/myrmidon/interfaces/extensions/ExtensionManager.java
  15. +35
    -29
      proposal/myrmidon/src/test/org/apache/myrmidon/AbstractMyrmidonTest.java
  16. +12
    -51
      proposal/myrmidon/src/test/org/apache/myrmidon/AbstractProjectTest.java
  17. +48
    -5
      proposal/myrmidon/src/test/org/apache/myrmidon/components/AbstractComponentTest.java
  18. +364
    -0
      proposal/myrmidon/src/test/org/apache/myrmidon/components/classloader/test/DefaultClassLoaderManagerTestCase.java
  19. +10
    -0
      proposal/myrmidon/src/test/org/apache/myrmidon/components/classloader/test/libs/cycle-extension-1.mf
  20. +10
    -0
      proposal/myrmidon/src/test/org/apache/myrmidon/components/classloader/test/libs/cycle-extension-2.mf
  21. +21
    -0
      proposal/myrmidon/src/test/org/apache/myrmidon/components/classloader/test/libs/extn/ExtnClass.java
  22. +1
    -0
      proposal/myrmidon/src/test/org/apache/myrmidon/components/classloader/test/libs/extn/extn.txt
  23. +3
    -0
      proposal/myrmidon/src/test/org/apache/myrmidon/components/classloader/test/libs/one-dependency.mf
  24. +18
    -0
      proposal/myrmidon/src/test/org/apache/myrmidon/components/classloader/test/libs/shared/SharedClass.java
  25. +1
    -0
      proposal/myrmidon/src/test/org/apache/myrmidon/components/classloader/test/libs/shared/shared.txt
  26. +7
    -0
      proposal/myrmidon/src/test/org/apache/myrmidon/components/classloader/test/libs/simple-extension.mf
  27. +21
    -0
      proposal/myrmidon/src/test/org/apache/myrmidon/components/classloader/test/libs/unshared/UnsharedClass.java
  28. +1
    -0
      proposal/myrmidon/src/test/org/apache/myrmidon/components/classloader/test/libs/unshared/unshared.txt
  29. +42
    -0
      proposal/myrmidon/src/test/org/apache/myrmidon/components/embeddor/test/DefaultEmbeddorTest.java
  30. +15
    -11
      proposal/myrmidon/src/xdocs/todo.xml

+ 56
- 1
proposal/myrmidon/build.xml View File

@@ -67,6 +67,7 @@ Legal:
<property name="test.dir" value="${build.dir}/test"/> <property name="test.dir" value="${build.dir}/test"/>
<property name="test.working.dir" value="${test.dir}/test"/> <property name="test.working.dir" value="${test.dir}/test"/>
<property name="test.classes" value="${test.dir}/classes"/> <property name="test.classes" value="${test.dir}/classes"/>
<property name="test.fork" value="true"/>


<property name="constants.file" value="org/apache/myrmidon/Constants.java"/> <property name="constants.file" value="org/apache/myrmidon/Constants.java"/>


@@ -544,6 +545,9 @@ Legal:


<!-- Compiles and runs the unit tests --> <!-- Compiles and runs the unit tests -->
<target name="run-tests" depends="dist-lite" if="junit.present"> <target name="run-tests" depends="dist-lite" if="junit.present">

<property name="test.classloader.pkg" value="org/apache/myrmidon/components/classloader/test/libs"/>

<!-- Compile the unit tests --> <!-- Compile the unit tests -->
<mkdir dir="${test.classes}"/> <mkdir dir="${test.classes}"/>
<javac srcdir="src/test" <javac srcdir="src/test"
@@ -558,6 +562,7 @@ Legal:


<exclude name="**/SmbFileSystemTestCase.java" unless="jcifs.present"/> <exclude name="**/SmbFileSystemTestCase.java" unless="jcifs.present"/>
<exclude name="**/FtpFileSystemTestCase.java" unless="netcomp.present"/> <exclude name="**/FtpFileSystemTestCase.java" unless="netcomp.present"/>
<exclude name="${test.classloader.pkg}/**"/>
</javac> </javac>


<!-- Prepare test files --> <!-- Prepare test files -->
@@ -602,6 +607,56 @@ Legal:
<fileset dir="${test.classes}" includes="org/apache/myrmidon/interfaces/type/test/MyType1.class"/> <fileset dir="${test.classes}" includes="org/apache/myrmidon/interfaces/type/test/MyType1.class"/>
</jar> </jar>


<!-- Prepare the class loader manager tests -->
<property name="test.classloader.dir" value="${test.working.dir}/${test.classloader.pkg}/.."/>
<property name="test.classloader.classes" value="${test.dir}/classloader"/>
<mkdir dir="${test.classloader.dir}"/>
<mkdir dir="${test.classloader.dir}/ext"/>
<mkdir dir="${test.classloader.classes}"/>
<javac srcdir="src/test"
destdir="${test.classloader.classes}"
debug="${debug}"
deprecation="${deprecation}">
<include name="${test.classloader.pkg}/**"/>
</javac>
<copy todir="${test.classloader.classes}">
<fileset dir="src/test">
<include name="${test.classloader.pkg}/**"/>
<exclude name="**/*.java"/>
</fileset>
</copy>
<jar jarfile="${test.classloader.dir}/common.jar">
<fileset dir="${test.classloader.classes}">
<include name="**/shared/**"/>
</fileset>
</jar>
<jar jarfile="${test.classloader.dir}/no-dependencies.jar">
<fileset dir="${test.classloader.classes}">
<include name="**/shared/**"/>
<include name="**/unshared/**"/>
</fileset>
</jar>
<jar jarfile="${test.classloader.dir}/one-dependency.jar"
manifest="src/test/${test.classloader.pkg}/one-dependency.mf">
<fileset dir="${test.classloader.classes}">
<include name="**/shared/**"/>
<include name="**/unshared/**"/>
</fileset>
</jar>
<copy file="${test.classloader.dir}/one-dependency.jar"
tofile="${test.classloader.dir}/one-dependency-2.jar"/>
<jar jarfile="${test.classloader.dir}/ext/simple-extension.jar"
manifest="src/test/${test.classloader.pkg}/simple-extension.mf" >
<fileset dir="${test.classloader.classes}">
<include name="**/shared/**"/>
<include name="**/extn/**"/>
</fileset>
</jar>
<jar jarfile="${test.classloader.dir}/ext/cycle-extension-1.jar"
manifest="src/test/${test.classloader.pkg}/cycle-extension-1.mf" />
<jar jarfile="${test.classloader.dir}/ext/cycle-extension-2.jar"
manifest="src/test/${test.classloader.pkg}/cycle-extension-2.mf" />

<!-- Prepare the project tests --> <!-- Prepare the project tests -->
<antlib-descriptor libName="unittests" <antlib-descriptor libName="unittests"
destdir="${gen.dir}" destdir="${gen.dir}"
@@ -616,7 +671,7 @@ Legal:


<!-- Run all the tests --> <!-- Run all the tests -->
<junit printsummary="on" <junit printsummary="on"
fork="true" failureProperty="test.failed">
fork="${test.fork}" failureProperty="test.failed">
<formatter type="brief" usefile="false"/> <formatter type="brief" usefile="false"/>
<classpath> <classpath>
<fileset dir="${test.working.dir}/dist/bin/lib" includes="**/*.jar"/> <fileset dir="${test.working.dir}/dist/bin/lib" includes="**/*.jar"/>


+ 13
- 11
proposal/myrmidon/docs/todo.html View File

@@ -837,23 +837,12 @@ public class MyrmidonSecurityManager
<code>&lt;socket&gt;</code> <code>&lt;socket&gt;</code>
conditions to an antlib. Need to resolve how these will be passed a logger. conditions to an antlib. Need to resolve how these will be passed a logger.
</li> </li>
<li>Make the
<code>&lt;uptodate&gt;</code> task a condition, and move to
an antlib.
</li>
<li>Split up
<code>&lt;is-set&gt;</code> condition into is-set and is-true conditions.
</li>
<li>Allow the <li>Allow the
<code>&lt;if&gt;</code> task to take any condition implementation. <code>&lt;if&gt;</code> task to take any condition implementation.
</li> </li>
<li>Add an else block to the <li>Add an else block to the
<code>&lt;if&gt;</code> task. <code>&lt;if&gt;</code> task.
</li> </li>
<li>Split the
<code>&lt;available&gt;</code> condition into separate conditions
that test for the availability of a class, or a resource.
</li>
<li>Move <li>Move
<code>crimson.jar</code> to <code>crimson.jar</code> to
<code>bin/lib</code> in the distribution, <code>bin/lib</code> in the distribution,
@@ -863,6 +852,19 @@ public class MyrmidonSecurityManager
<li>Add a <code>--type</code> command-line option, to allow <li>Add a <code>--type</code> command-line option, to allow
the project builder to be manually selected. the project builder to be manually selected.
</li> </li>
<li>Change <code>ProjectBuilder</code>
and <code>Embeddor</code> to throw something more
specialised than Exception.
</li>
<li>Change <code>DefaultClassLoaderManager</code> to handle
directories as part of a library classpath.
</li>
<li><code>&lt;condition&gt;</code> should set the property
value to <code>false</code> when the condition is false.</li>
<li>Split the <code>&lt;uptodate&gt;</code> condition into
a condition that checks against a single target file,
and one which checks using a destdir/mapper.</li>
<li>Add a task to unset a property.</li>
<li>Unit tests.</li> <li>Unit tests.</li>
</ul> </ul>
</blockquote> </blockquote>


+ 154
- 87
proposal/myrmidon/src/java/org/apache/myrmidon/components/classloader/DefaultClassLoaderManager.java View File

@@ -8,19 +8,17 @@
package org.apache.myrmidon.components.classloader; package org.apache.myrmidon.components.classloader;


import java.io.File; import java.io.File;
import java.net.JarURLConnection;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.List;
import java.util.Set;
import java.util.HashSet;
import java.util.jar.Manifest; import java.util.jar.Manifest;
import java.util.jar.JarFile;
import org.apache.avalon.excalibur.extension.Extension; import org.apache.avalon.excalibur.extension.Extension;
import org.apache.avalon.excalibur.extension.OptionalPackage; import org.apache.avalon.excalibur.extension.OptionalPackage;
import org.apache.avalon.excalibur.extension.PackageManager;
import org.apache.avalon.excalibur.i18n.ResourceManager; import org.apache.avalon.excalibur.i18n.ResourceManager;
import org.apache.avalon.excalibur.i18n.Resources; import org.apache.avalon.excalibur.i18n.Resources;
import org.apache.avalon.framework.activity.Initializable; import org.apache.avalon.framework.activity.Initializable;
@@ -28,8 +26,8 @@ import org.apache.avalon.framework.logger.AbstractLogEnabled;
import org.apache.avalon.framework.service.ServiceException; import org.apache.avalon.framework.service.ServiceException;
import org.apache.avalon.framework.service.ServiceManager; import org.apache.avalon.framework.service.ServiceManager;
import org.apache.avalon.framework.service.Serviceable; import org.apache.avalon.framework.service.Serviceable;
import org.apache.myrmidon.interfaces.classloader.ClassLoaderManager;
import org.apache.myrmidon.interfaces.classloader.ClassLoaderException; import org.apache.myrmidon.interfaces.classloader.ClassLoaderException;
import org.apache.myrmidon.interfaces.classloader.ClassLoaderManager;
import org.apache.myrmidon.interfaces.deployer.DeploymentException; import org.apache.myrmidon.interfaces.deployer.DeploymentException;
import org.apache.myrmidon.interfaces.extensions.ExtensionManager; import org.apache.myrmidon.interfaces.extensions.ExtensionManager;
import org.apache.tools.todo.types.PathUtil; import org.apache.tools.todo.types.PathUtil;
@@ -38,6 +36,7 @@ import org.apache.tools.todo.types.PathUtil;
* A default implementation of a ClassLoader manager. * A default implementation of a ClassLoader manager.
* *
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> * @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a>
* @version $Revision$ $Date$
*/ */
public class DefaultClassLoaderManager public class DefaultClassLoaderManager
extends AbstractLogEnabled extends AbstractLogEnabled
@@ -47,13 +46,22 @@ public class DefaultClassLoaderManager
ResourceManager.getPackageResources( DefaultClassLoaderManager.class ); ResourceManager.getPackageResources( DefaultClassLoaderManager.class );


/** /**
* Map from File to the ClassLoader for that file.
* Map from File/ArrayList to the ClassLoader for that file/files.
*/ */
private final Map m_fileDeployers = new HashMap();
private final Map m_classLoaders = new HashMap();


private PackageManager m_packageManager;
private ExtensionManager m_extensionManager;
private ClassLoader m_commonClassLoader; private ClassLoader m_commonClassLoader;


public DefaultClassLoaderManager()
{
}

public DefaultClassLoaderManager( final ClassLoader commonClassLoader )
{
m_commonClassLoader = commonClassLoader;
}

public void initialize() throws Exception public void initialize() throws Exception
{ {
if( null == m_commonClassLoader ) if( null == m_commonClassLoader )
@@ -68,18 +76,7 @@ public class DefaultClassLoaderManager
public void service( final ServiceManager serviceManager ) public void service( final ServiceManager serviceManager )
throws ServiceException throws ServiceException
{ {
final ExtensionManager extensionManager =
(ExtensionManager)serviceManager.lookup( ExtensionManager.ROLE );
m_packageManager = new PackageManager( extensionManager );
}

/**
* Sets the ClassLoader to use as the parent for all classloaders
* created by this ClassLoader manager.
*/
public void setCommonClassLoader( final ClassLoader classLoader )
{
m_commonClassLoader = classLoader;
m_extensionManager = (ExtensionManager)serviceManager.lookup( ExtensionManager.ROLE );
} }


/** /**
@@ -94,9 +91,27 @@ public class DefaultClassLoaderManager
/** /**
* Creates a class loader for a Jar file. * Creates a class loader for a Jar file.
*/ */
public ClassLoader createClassLoader( final File file ) throws ClassLoaderException
public ClassLoader getClassLoader( final File file ) throws ClassLoaderException
{ {
return createClassLoader( new File[] { file } );
try
{
final File canonFile = file.getCanonicalFile();

// Check for cached classloader, creating it if required
ClassLoader loader = (ClassLoader)m_classLoaders.get( canonFile );
if( loader == null )
{
checkFile( canonFile );
final OptionalPackage optionalPackage = toOptionalPackage( canonFile );
loader = buildClassLoader( optionalPackage, new HashSet() );
}
return loader;
}
catch( final Exception e )
{
final String message = REZ.getString( "create-classloader-for-file.error", file );
throw new ClassLoaderException( message, e );
}
} }


/** /**
@@ -106,113 +121,165 @@ public class DefaultClassLoaderManager
{ {
try try
{ {
// Build a list of canonical file names
final ArrayList canonFiles = new ArrayList( files.length );
for( int i = 0; i < files.length; i++ )
if( files == null || files.length == 0 )
{ {
canonFiles.add( files[ i ].getCanonicalFile() );
return m_commonClassLoader;
} }


// Locate cached classloader, creating it if necessary
ClassLoader classLoader = (ClassLoader)m_fileDeployers.get( canonFiles );
if( classLoader == null )
// Build a list of optional packages for the files
final OptionalPackage[] packages = new OptionalPackage[ files.length ];
for( int i = 0; i < files.length; i++ )
{ {
classLoader = buildClassLoader( canonFiles );
m_fileDeployers.put( canonFiles, classLoader );
final File canonFile = files[ i ].getCanonicalFile();
checkFile( canonFile );
packages[ i ] = toOptionalPackage( canonFile );
} }
return classLoader;

// Build the classloaders for the required extensions
final ClassLoader[] parentClassLoaders = buildParentClassLoaders( packages, new HashSet() );

// Build the classloader
final URL[] urls = buildClasspath( files );
return new MultiParentURLClassLoader( urls, parentClassLoaders );
} }
catch( final Exception e ) catch( final Exception e )
{ {
final String fileNames = PathUtil.formatPath( files ); final String fileNames = PathUtil.formatPath( files );
final String message = REZ.getString( "create-classloader-for-file.error", fileNames );
final String message = REZ.getString( "create-classloader-for-files.error", fileNames );
throw new ClassLoaderException( message, e ); throw new ClassLoaderException( message, e );
} }
} }


/** /**
* Builds the classloader for a set of files.
* Builds the classloader for an optional package.
*/ */
private ClassLoader buildClassLoader( final ArrayList files )
private ClassLoader buildClassLoader( final OptionalPackage pkg,
final Set pending )
throws Exception throws Exception
{ {
final ArrayList allFiles = new ArrayList( files );
final int count = files.size();
for( int i = 0; i < count; i++ )
final File jarFile = pkg.getFile();

// Check for cached classloader
ClassLoader classLoader = (ClassLoader)m_classLoaders.get( jarFile );
if( classLoader != null )
{ {
final File file = (File)files.get(i );
checkFile( file );
getOptionalPackagesFor( file, allFiles );
return classLoader;
} }


final URL[] urls = buildClasspath( allFiles );
return new URLClassLoader( urls, m_commonClassLoader );
// Check for cyclic dependency
if( pending.contains( jarFile ) )
{
final String message = REZ.getString( "dependency-cycle.error", jarFile );
throw new Exception( message );
}
pending.add( jarFile );

// Build the classloaders for the extensions required by this optional
// package
final ClassLoader[] parentClassLoaders =
buildParentClassLoaders( new OptionalPackage[] { pkg }, pending );

// Create and cache the classloader
final URL[] urls = { jarFile.toURL() };
classLoader = new MultiParentURLClassLoader( urls, parentClassLoaders );
m_classLoaders.put( jarFile, classLoader );
pending.remove( jarFile );
return classLoader;
}

/**
* Builds the parent classloaders for a set of optional packages. That is,
* the classloaders for all of the extensions required by the given set
* of optional packages.
*/
private ClassLoader[] buildParentClassLoaders( final OptionalPackage[] packages,
final Set pending )
throws Exception
{
final ArrayList classLoaders = new ArrayList();

// Include the common class loader
classLoaders.add( m_commonClassLoader );

// Build the classloader for each optional package, filtering out duplicates
for( int i = 0; i < packages.length; i++ )
{
final OptionalPackage optionalPackage = packages[ i ];

// Locate the dependencies for this jar file
final OptionalPackage[] requiredPackages = getOptionalPackagesFor( optionalPackage );

// Build the classloader for the package
for( int j = 0; j < requiredPackages.length; j++ )
{
final OptionalPackage requiredPackage = requiredPackages[j ];
final ClassLoader classLoader = buildClassLoader( requiredPackage, pending );
if( ! classLoaders.contains( classLoader ) )
{
classLoaders.add( classLoader );
}
}
}

return (ClassLoader[])classLoaders.toArray( new ClassLoader[classLoaders.size() ] );
} }


/** /**
* Assembles a set of files into a URL classpath. * Assembles a set of files into a URL classpath.
*/ */
private URL[] buildClasspath( final ArrayList files )
private URL[] buildClasspath( final File[] files )
throws MalformedURLException throws MalformedURLException
{ {
final URL[] urls = new URL[ files.size() ];
final int count = files.size();
for( int i = 0; i < count; i++ )
final URL[] urls = new URL[ files.length ];
for( int i = 0; i < files.length; i++ )
{ {
final File file = (File)files.get( i );
urls[ i ] = file.toURL();
urls[ i ] = files[i ].toURL();
} }


return urls; return urls;
} }


/** /**
* Retrieve the files for the optional packages required by
* the specified typeLibrary jar.
* Builds an OptionalPackage for a Jar file.
* *
* @param jarFile the typeLibrary
* @param packages used to return the files that need to be added to ClassLoader.
* @param file the jar.
*/ */
private void getOptionalPackagesFor( final File jarFile, final List packages )
private OptionalPackage toOptionalPackage( final File file )
throws Exception throws Exception
{ {
final URL url = new URL( "jar:" + jarFile.getCanonicalFile().toURL() + "!/" );
final JarURLConnection connection = (JarURLConnection)url.openConnection();
final Manifest manifest = connection.getManifest();
final Extension[] available = Extension.getAvailable( manifest );
// Determine the extensions required by this file
final JarFile jarFile = new JarFile( file );
final Manifest manifest = jarFile.getManifest();
final Extension[] required = Extension.getRequired( manifest ); final Extension[] required = Extension.getRequired( manifest );
return new OptionalPackage( file, new Extension[0], required );
}


if( getLogger().isDebugEnabled() )
{
final String message1 =
REZ.getString( "available-extensions.notice", Arrays.asList( available ) );
getLogger().debug( message1 );
final String message2 =
REZ.getString( "required-extensions.notice", Arrays.asList( required ) );
getLogger().debug( message2 );
}

final ArrayList dependencies = new ArrayList();
final ArrayList unsatisfied = new ArrayList();

m_packageManager.scanDependencies( required,
available,
dependencies,
unsatisfied );

if( 0 != unsatisfied.size() )
/**
* Locates the optional packages required by an optional package.
*/
private OptionalPackage[] getOptionalPackagesFor( final OptionalPackage pkg )
throws Exception
{
// Locate the optional packages that provide the required extesions
final Extension[] required = pkg.getRequiredExtensions();
final ArrayList packages = new ArrayList();
for( int i = 0; i < required.length; i++ )
{ {
final String message =
REZ.getString( "unsatisfied.extensions.error", new Integer( unsatisfied.size() ) );
throw new Exception( message );
final Extension extension = required[i ];
final OptionalPackage optionalPackage = m_extensionManager.getOptionalPackage( extension );
if( optionalPackage == null )
{
final String message =
REZ.getString( "unsatisfied.extension.error",
pkg.getFile(),
extension.getExtensionName(),
extension.getSpecificationVersion() );
throw new Exception( message );
}
packages.add( optionalPackage );
} }


final int count = dependencies.size();
for( int i = 0; i < count; i++ )
{
final OptionalPackage optionalPackage = (OptionalPackage)dependencies.get(i );
packages.add( optionalPackage.getFile() );
}
return (OptionalPackage[])packages.toArray( new OptionalPackage[packages.size() ] );
} }


/** /**


+ 141
- 0
proposal/myrmidon/src/java/org/apache/myrmidon/components/classloader/MultiParentURLClassLoader.java View File

@@ -0,0 +1,141 @@
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE.txt file.
*/
package org.apache.myrmidon.components.classloader;

import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Set;
import java.util.HashSet;

/**
* A URLClassLoader with more than one parent.
*
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a>
* @version $Revision$ $Date$
*/
public class MultiParentURLClassLoader
extends URLClassLoader
{
private final ClassLoader[] m_parents;

/**
* Constructs a new URLClassLoader for the given URLs.
*
* @param urls the URLs from which to load classes and resources
* @param parents the parent class loaderer for delegation
*/
public MultiParentURLClassLoader( final URL[] urls, final ClassLoader[] parents )
{
super( urls );
m_parents = parents;
}

/**
* Finds a class.
*
* @param name the name of the class
* @return the resulting class
* @exception ClassNotFoundException if the class could not be found
*/
protected Class findClass( final String name )
throws ClassNotFoundException
{
// Try the parent classloaders first
for( int i = 0; i < m_parents.length; i++ )
{
try
{
final ClassLoader parent = m_parents[ i ];
return parent.loadClass( name );
}
catch( ClassNotFoundException e )
{
// Ignore - continue to the next ClassLoader
}
}

// Now this classloader
return super.findClass( name );
}

/**
* Finds a resource.
*
* @param name the name of the resource
* @return a <code>URL</code> for the resource, or <code>null</code>
* if the resource could not be found.
*/
public URL findResource( final String name )
{
// Try the parent classloaders first
for( int i = 0; i < m_parents.length; i++ )
{
final ClassLoader parent = m_parents[ i ];
final URL resource = parent.getResource( name );
if( resource != null )
{
return resource;
}
}

// Now this classloader
return super.findResource( name );
}

/**
* Returns an Enumeration of URLs representing all of the resources
* having the specified name.
*
* @param name the resource name
* @throws IOException if an I/O exception occurs
* @return an <code>Enumeration</code> of <code>URL</code>s
*/
public Enumeration findResources( final String name ) throws IOException
{
// Need to filter out duplicate resources
final ArrayList urls = new ArrayList();
final Set urlSet = new HashSet();

// Gather the resources from the parent classloaders
for( int i = 0; i < m_parents.length; i++ )
{
final ClassLoader parent = m_parents[ i ];
final Enumeration enum = parent.getResources( name );
addUrls( enum, urls, urlSet );
}

// Gather the resources from this classloader
addUrls( super.findResources( name ), urls, urlSet );

return Collections.enumeration( urls );
}

/**
* Adds those URLs not already present.
*/
private void addUrls( final Enumeration enum,
final List urls,
final Set urlSet )
{
while( enum.hasMoreElements() )
{
final URL url = (URL)enum.nextElement();
final String urlStr = url.toExternalForm();
if( !urlSet.contains( urlStr ) )
{
urls.add( url );
urlSet.add( urlStr );
}
}
}
}

+ 5
- 6
proposal/myrmidon/src/java/org/apache/myrmidon/components/classloader/Resources.properties View File

@@ -1,6 +1,5 @@
create-classloader-for-file.error=Could not create ClassLoader for files: {0}.
available-extensions.notice=The list of available extensions for type library includes; {0}
required-extensions.notice=The list of required extensions for type library includes; {0}
unsatisfied.extensions.error=Missing {0} extensions for type library.
no-file.error=Could not find type library "{0}".
file-is-dir.error=Type library "{0}" is a directory.
create-classloader-for-file.error=Could not create a ClassLoader for "{0}".
create-classloader-for-files.error=Could not create a ClassLoader for {0}.
unsatisfied.extension.error=Library "{0}" requires unknown extension "{1}" ( version {2}).
no-file.error=Could not find library "{0}".
file-is-dir.error=Library "{0}" is a directory.

+ 1
- 1
proposal/myrmidon/src/java/org/apache/myrmidon/components/deployer/DefaultDeployer.java View File

@@ -108,7 +108,7 @@ public class DefaultDeployer
{ {
try try
{ {
final ClassLoader classLoader = m_classLoaderManager.createClassLoader( file );
final ClassLoader classLoader = m_classLoaderManager.getClassLoader( file );
return createDeployment( classLoader, file.toURL() ); return createDeployment( classLoader, file.toURL() );
} }
catch( final Exception e ) catch( final Exception e )


+ 2
- 104
proposal/myrmidon/src/java/org/apache/myrmidon/components/embeddor/DefaultEmbeddor.java View File

@@ -71,10 +71,7 @@ public class DefaultEmbeddor
private List m_components = new ArrayList(); private List m_components = new ArrayList();
private DefaultServiceManager m_serviceManager = new DefaultServiceManager(); private DefaultServiceManager m_serviceManager = new DefaultServiceManager();
private Parameters m_parameters; private Parameters m_parameters;
private Parameters m_defaults;


private File m_homeDir;
private File m_taskLibDir;
private static final String MYRMIDON_HOME = "myrmidon.home"; private static final String MYRMIDON_HOME = "myrmidon.home";


/** /**
@@ -163,9 +160,6 @@ public class DefaultEmbeddor
public void initialize() public void initialize()
throws Exception throws Exception
{ {
// setup default properties
m_defaults = createDefaultParameters();

// setup the root components // setup the root components
setupComponents(); setupComponents();


@@ -183,9 +177,6 @@ public class DefaultEmbeddor
m_workspaceServiceManager = new MultiSourceServiceManager(); m_workspaceServiceManager = new MultiSourceServiceManager();
m_workspaceServiceManager.add( projServiceManager ); m_workspaceServiceManager.add( projServiceManager );
m_workspaceServiceManager.add( m_serviceManager ); m_workspaceServiceManager.add( m_serviceManager );

// setup
setupFiles();
} }


public void start() public void start()
@@ -198,7 +189,8 @@ public class DefaultEmbeddor


// Deploy all type libraries in the lib directory // Deploy all type libraries in the lib directory
final ExtensionFileFilter filter = new ExtensionFileFilter( ".atl" ); final ExtensionFileFilter filter = new ExtensionFileFilter( ".atl" );
deployFromDirectory( m_deployer, m_taskLibDir, filter );
final File taskLibDir = new File( m_parameters.getParameter( "myrmidon.lib.path" ) );
deployFromDirectory( m_deployer, taskLibDir, filter );
} }


/** /**
@@ -230,26 +222,6 @@ public class DefaultEmbeddor
m_deployer = null; m_deployer = null;
m_serviceManager = null; m_serviceManager = null;
m_parameters = null; m_parameters = null;
m_defaults = null;
m_homeDir = null;
m_taskLibDir = null;
}

/**
* Create default properties which includes default names of all components.
* Overide this in sub-classes to change values.
*
* @return the Parameters
*/
private Parameters createDefaultParameters()
{
final Parameters defaults = new Parameters();

//create all the default properties for files/directories
defaults.setParameter( "myrmidon.bin.path", "bin" );
defaults.setParameter( "myrmidon.lib.path", "lib" );

return defaults;
} }


/** /**
@@ -296,80 +268,6 @@ public class DefaultEmbeddor
return component; return component;
} }


/**
* Setup all the files attributes.
*/
private void setupFiles()
throws Exception
{
String filepath = null;

filepath = getParameter( MYRMIDON_HOME );
m_homeDir = ( new File( filepath ) ).getAbsoluteFile();
checkDirectory( m_homeDir, "home-dir.name" );

filepath = getParameter( "myrmidon.lib.path" );
m_taskLibDir = resolveDirectory( filepath, "task-lib-dir.name" );
}

/**
* Retrieve value of named property.
* First access passed in properties and then the default properties.
*
* @param name the name of property
* @return the value of property or null
*/
private String getParameter( final String name )
{
String value = m_parameters.getParameter( name, null );

if( null == value )
{
value = m_defaults.getParameter( name, null );
}

return value;
}

/**
* Resolve a directory relative to another base directory.
*
* @param dir the base directory
* @param name the relative directory
* @return the created File
* @exception Exception if an error occurs
*/
private File resolveDirectory( final String dir, final String name )
throws Exception
{
final File file = FileUtil.resolveFile( m_homeDir, dir );
checkDirectory( file, name );
return file;
}

/**
* Verify file is a directory else throw an exception.
*
* @param file the File
* @param name the name of file type (used in error messages)
*/
private void checkDirectory( final File file, final String name )
throws Exception
{
if( !file.exists() )
{
final String nameStr = REZ.getString( name );
final String message = REZ.getString( "file-no-exist.error", nameStr, file );
throw new Exception( message );
}
else if( !file.isDirectory() )
{
final String nameStr = REZ.getString( name );
final String message = REZ.getString( "file-not-dir.error", nameStr, file );
throw new Exception( message );
}
}

/** /**
* Create a component that implements an interface. * Create a component that implements an interface.
* *


+ 0
- 4
proposal/myrmidon/src/java/org/apache/myrmidon/components/embeddor/Resources.properties View File

@@ -1,10 +1,6 @@
file-no-exist.error={0} ({1}) does not exist.
file-not-dir.error={0} ({1}) is not a directory.
bad-type.error=Object {0} is not an instance of {1}. bad-type.error=Object {0} is not an instance of {1}.
bad-ctor.error=Non-public constructor for {0} {1}. bad-ctor.error=Non-public constructor for {0} {1}.
no-instantiate.error=Error instantiating class for {0} {1}. no-instantiate.error=Error instantiating class for {0} {1}.
no-class.error=Could not find the class for {0} ({1}). no-class.error=Could not find the class for {0} ({1}).
bad-filename.error=Unable to retrieve filename for file {0}. bad-filename.error=Unable to retrieve filename for file {0}.
home-dir.name=Myrmidon home directory
task-lib-dir.name=Task library directory
create-project.error=Could not load the project definition from {0}. create-project.error=Could not load the project definition from {0}.

+ 25
- 6
proposal/myrmidon/src/java/org/apache/myrmidon/components/extensions/DefaultExtensionManager.java View File

@@ -50,7 +50,6 @@ public class DefaultExtensionManager
File.separator + "lib" + File.separator + "tools.jar"; File.separator + "lib" + File.separator + "tools.jar";


private Logger m_logger; private Logger m_logger;

private String m_path; private String m_path;


public DefaultExtensionManager() public DefaultExtensionManager()
@@ -58,6 +57,11 @@ public class DefaultExtensionManager
super( new File[ 0 ] ); super( new File[ 0 ] );
} }


public DefaultExtensionManager( final File[] path )
{
super( path );
}

public void enableLogging( final Logger logger ) public void enableLogging( final Logger logger )
{ {
m_logger = logger; m_logger = logger;
@@ -66,16 +70,13 @@ public class DefaultExtensionManager
public void parameterize( final Parameters parameters ) public void parameterize( final Parameters parameters )
throws ParameterException throws ParameterException
{ {
final String phoenixHome = parameters.getParameter( "myrmidon.home" );
final String defaultExtPath = phoenixHome + File.separator + "ext";
m_path = parameters.getParameter( "myrmidon.ext.path", defaultExtPath );
m_path = parameters.getParameter( "myrmidon.ext.path" );
} }


public void initialize() public void initialize()
throws Exception throws Exception
{ {
final String[] pathElements = StringUtil.split( m_path, "|" );

final String[] pathElements = StringUtil.split( m_path, File.pathSeparator );
final File[] dirs = new File[ pathElements.length ]; final File[] dirs = new File[ pathElements.length ];
for( int i = 0; i < dirs.length; i++ ) for( int i = 0; i < dirs.length; i++ )
{ {
@@ -86,6 +87,7 @@ public class DefaultExtensionManager


scanPath(); scanPath();


// Add the JVM's tools.jar as an extension
final Extension extension = createToolsExtension(); final Extension extension = createToolsExtension();
final File jar = getToolsJar(); final File jar = getToolsJar();
final Extension[] available = new Extension[]{extension}; final Extension[] available = new Extension[]{extension};
@@ -99,6 +101,23 @@ public class DefaultExtensionManager
clearCache(); clearCache();
} }


/**
* Locates the optional package which best matches a required extension.
*
* @param extension the extension to locate an optional package
* @return the optional package, or null if not found.
*/
public OptionalPackage getOptionalPackage( final Extension extension )
{
final OptionalPackage[] packages = getOptionalPackages( extension );

if( null == packages || 0 == packages.length ) return null;

//TODO: Use heurisitic to find which is best package

return packages[ 0 ];
}

protected void debug( final String message ) protected void debug( final String message )
{ {
m_logger.debug( message ); m_logger.debug( message );


+ 3
- 3
proposal/myrmidon/src/java/org/apache/myrmidon/frontends/CLIMain.java View File

@@ -277,10 +277,10 @@ public class CLIMain
break; break;


case LISTENER_OPT: case LISTENER_OPT:
m_embedded.setListener( option.getArgument() );
m_embedded.setProjectListener( option.getArgument() );
break; break;
case NO_PREFIX_OPT: case NO_PREFIX_OPT:
m_embedded.setListener( "noprefix" );
m_embedded.setProjectListener( "noprefix" );
break; break;


case DEFINE_OPT: case DEFINE_OPT:
@@ -310,7 +310,7 @@ public class CLIMain
try try
{ {
// Set system properties set up by launcher // Set system properties set up by launcher
m_embedded.setEmbeddorProperty( "myrmidon.home", properties.get( "myrmidon.home" ) );
m_embedded.setHomeDirectory( (File)properties.get( "myrmidon.home" ) );


// Command line // Command line
if( !parseCommandLineOptions( args ) ) if( !parseCommandLineOptions( args ) )


+ 83
- 24
proposal/myrmidon/src/java/org/apache/myrmidon/frontends/EmbeddedAnt.java View File

@@ -8,8 +8,10 @@
package org.apache.myrmidon.frontends; package org.apache.myrmidon.frontends;


import java.io.File; import java.io.File;
import java.util.ArrayList;
import org.apache.avalon.excalibur.i18n.ResourceManager; import org.apache.avalon.excalibur.i18n.ResourceManager;
import org.apache.avalon.excalibur.i18n.Resources; import org.apache.avalon.excalibur.i18n.Resources;
import org.apache.avalon.excalibur.io.FileUtil;
import org.apache.avalon.framework.activity.Disposable; import org.apache.avalon.framework.activity.Disposable;
import org.apache.avalon.framework.activity.Initializable; import org.apache.avalon.framework.activity.Initializable;
import org.apache.avalon.framework.activity.Startable; import org.apache.avalon.framework.activity.Startable;
@@ -47,12 +49,25 @@ public class EmbeddedAnt
private String m_projectFile = "build.ant"; private String m_projectFile = "build.ant";
private Project m_project; private Project m_project;
private String m_listenerName = "default"; private String m_listenerName = "default";
private ProjectListener m_listener;
private ArrayList m_listeners = new ArrayList();
private Parameters m_workspaceProps = new Parameters(); private Parameters m_workspaceProps = new Parameters();
private Parameters m_builderProps = new Parameters(); private Parameters m_builderProps = new Parameters();
private Parameters m_embeddorParameters = new Parameters(); private Parameters m_embeddorParameters = new Parameters();
private ClassLoader m_sharedClassLoader; private ClassLoader m_sharedClassLoader;
private Embeddor m_embeddor; private Embeddor m_embeddor;
private File m_homeDir;

/**
* Sets the Myrmidon home directory. Default is to use the current
* directory.
*
* @todo Autodetect myrmidon home, rather than using current directory
* as the default (which is a dud default).
*/
public void setHomeDirectory( final File homeDir )
{
m_homeDir = homeDir.getAbsoluteFile();
}


/** /**
* Sets the project file to execute. Default is 'build.ant'. * Sets the project file to execute. Default is 'build.ant'.
@@ -75,21 +90,20 @@ public class EmbeddedAnt
} }


/** /**
* Sets the name of the project listener to use.
* Sets the name of the project listener to use. Set to null to disable
* the project listener.
*/ */
public void setListener( final String listener )
public void setProjectListener( final String listener )
{ {
m_listenerName = listener; m_listenerName = listener;
m_listener = null;
} }


/** /**
* Sets the project listener to use.
* Adds a project listener.
*/ */
public void setListener( final ProjectListener listener )
public void addProjectListener( final ProjectListener listener )
{ {
m_listenerName = null;
m_listener = listener;
m_listeners.add( listener );
} }


/** /**
@@ -99,7 +113,7 @@ public class EmbeddedAnt
public void setWorkspaceProperty( final String name, final Object value ) public void setWorkspaceProperty( final String name, final Object value )
{ {
// TODO - Make properties Objects, not Strings // TODO - Make properties Objects, not Strings
m_workspaceProps.setParameter( name, (String)value );
m_workspaceProps.setParameter( name, value.toString() );
} }


/** /**
@@ -109,7 +123,7 @@ public class EmbeddedAnt
public void setBuilderProperty( final String name, final Object value ) public void setBuilderProperty( final String name, final Object value )
{ {
// TODO - Make properties Objects, not Strings // TODO - Make properties Objects, not Strings
m_builderProps.setParameter( name, (String)value );
m_builderProps.setParameter( name, value.toString() );
} }


/** /**
@@ -145,14 +159,13 @@ public class EmbeddedAnt


checkHomeDir(); checkHomeDir();


// Prepare the embeddor, project listener and project model
// Prepare the embeddor, and project model
final Embeddor embeddor = prepareEmbeddor(); final Embeddor embeddor = prepareEmbeddor();
final ProjectListener listener = prepareListener( embeddor );
final Project project = prepareProjectModel( embeddor ); final Project project = prepareProjectModel( embeddor );


// Create a new workspace // Create a new workspace
final Workspace workspace = embeddor.createWorkspace( m_workspaceProps ); final Workspace workspace = embeddor.createWorkspace( m_workspaceProps );
workspace.addProjectListener( listener );
prepareListeners( embeddor, workspace );


//execute the project //execute the project
executeTargets( workspace, project, targets ); executeTargets( workspace, project, targets );
@@ -181,7 +194,7 @@ public class EmbeddedAnt
{ {
m_embeddor = null; m_embeddor = null;
m_project = null; m_project = null;
m_listener = null;
m_listeners.clear();
} }
} }


@@ -213,19 +226,26 @@ public class EmbeddedAnt
*/ */
private void checkHomeDir() throws Exception private void checkHomeDir() throws Exception
{ {
final String home = m_embeddorParameters.getParameter( "myrmidon.home" );
final File homeDir = ( new File( home ) ).getAbsoluteFile();
if( !homeDir.isDirectory() )
if( m_homeDir == null )
{ {
final String message = REZ.getString( "home-not-dir.error", homeDir );
throw new Exception( message );
m_homeDir = new File( "." ).getAbsoluteFile();
} }
checkDirectory( m_homeDir, "home-dir.name" );
m_embeddorParameters.setParameter( "myrmidon.home", m_homeDir.getAbsolutePath() );


if( getLogger().isInfoEnabled() ) if( getLogger().isInfoEnabled() )
{ {
final String message = REZ.getString( "homedir.notice", homeDir );
final String message = REZ.getString( "homedir.notice", m_homeDir );
getLogger().info( message ); getLogger().info( message );
} }

String path = m_embeddorParameters.getParameter( "myrmidon.lib.path", "lib" );
File dir = resolveDirectory( m_homeDir, path, "task-lib-dir.name" );
m_embeddorParameters.setParameter( "myrmidon.lib.path", dir.getAbsolutePath() );

path = m_embeddorParameters.getParameter( "myrmidon.ext.path", "ext" );
dir = resolveDirectory( m_homeDir, path, "ext-dir.name" );
m_embeddorParameters.setParameter( "myrmidon.ext.path", dir.getAbsolutePath() );
} }


/** /**
@@ -267,14 +287,21 @@ public class EmbeddedAnt
/** /**
* Prepares and returns the project listener to use. * Prepares and returns the project listener to use.
*/ */
private ProjectListener prepareListener( final Embeddor embeddor )
private void prepareListeners( final Embeddor embeddor,
final Workspace workspace )
throws Exception throws Exception
{ {
if( m_listener == null )
if( m_listenerName != null )
{
final ProjectListener listener = embeddor.createListener( m_listenerName );
workspace.addProjectListener( listener );
}
final int count = m_listeners.size();
for( int i = 0; i < count; i++ )
{ {
m_listener = embeddor.createListener( m_listenerName );
final ProjectListener listener = (ProjectListener)m_listeners.get(i );
workspace.addProjectListener( listener );
} }
return m_listener;
} }


/** /**
@@ -310,4 +337,36 @@ public class EmbeddedAnt


return projectFile; return projectFile;
} }

/**
* Resolve a directory relative to another base directory.
*/
private File resolveDirectory( final File baseDir, final String dir, final String name )
throws Exception
{
final File file = FileUtil.resolveFile( baseDir, dir );
checkDirectory( file, name );
return file;
}

/**
* Verify file is a directory else throw an exception.
*/
private void checkDirectory( final File file, final String name )
throws Exception
{
if( !file.exists() )
{
final String nameStr = REZ.getString( name );
final String message = REZ.getString( "file-no-exist.error", nameStr, file );
throw new Exception( message );
}
else if( !file.isDirectory() )
{
final String nameStr = REZ.getString( name );
final String message = REZ.getString( "file-not-dir.error", nameStr, file );
throw new Exception( message );
}
}

} }

+ 5
- 1
proposal/myrmidon/src/java/org/apache/myrmidon/frontends/Resources.properties View File

@@ -16,7 +16,8 @@ define.opt=Define a property (ie -Dfoo=var).
build.opt=Define a builder parameter (ie -Bfoo=var). build.opt=Define a builder parameter (ie -Bfoo=var).
dry-run.opt=Do not execute tasks - just print them out. dry-run.opt=Do not execute tasks - just print them out.


home-not-dir.error=myrmidon-home ({0}) is not a directory.
file-no-exist.error={0} {1} does not exist.
file-not-dir.error={0} {1} is not a directory.
bad-file.error=File {0} is not a file or doesn't exist. bad-file.error=File {0} is not a file or doesn't exist.
bad-loglevel.error=Unknown log level - {0}. bad-loglevel.error=Unknown log level - {0}.
build-failed.error=BUILD FAILED. build-failed.error=BUILD FAILED.
@@ -26,3 +27,6 @@ repeat.notice=Continue ? (Enter no to stop)


homedir.notice=Ant Home Directory: {0} homedir.notice=Ant Home Directory: {0}
buildfile.notice=Ant Build File: {0} buildfile.notice=Ant Build File: {0}
home-dir.name=Ant home directory
task-lib-dir.name=Task library directory
ext-dir.name=Extension library directory

+ 15
- 6
proposal/myrmidon/src/java/org/apache/myrmidon/interfaces/classloader/ClassLoaderManager.java View File

@@ -13,6 +13,7 @@ import java.io.File;
* Manages a classloader hierarchy. * Manages a classloader hierarchy.
* *
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> * @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a>
* @version $Revision$ $Date$
*/ */
public interface ClassLoaderManager public interface ClassLoaderManager
{ {
@@ -20,15 +21,22 @@ public interface ClassLoaderManager
String ROLE = ClassLoaderManager.class.getName(); String ROLE = ClassLoaderManager.class.getName();


/** /**
* Builds the ClassLoader for a Jar file, resolving dependencies.
* Returns the ClassLoader for a Jar file. The ClassLoader is created,
* if necessary. The ClassLoader's parent will include the common
* ClassLoader, along with any extensions required by the Jar file.
* It is guaranteed that each extension will appear at most once in the
* ClassLoader hierarchy, so that classes from the extension can be
* shared across the ClassLoaders returned by this method.
*
* @param jar the jar file containing the classes to load * @param jar the jar file containing the classes to load
* @return the created classloader
* @return the classloader
* @throws ClassLoaderException on error * @throws ClassLoaderException on error
*/ */
ClassLoader createClassLoader( File jar ) throws ClassLoaderException;
ClassLoader getClassLoader( File jar ) throws ClassLoaderException;


/** /**
* Builds the ClassLoader for a set of files, resolving dependencies.
* Creates a ClassLoader for a set of files. See {@link #getClassLoader}
* for details.
* *
* @param jars The Jar/zip files to create the classloader for. Use null * @param jars The Jar/zip files to create the classloader for. Use null
* or an empty array to use the common classloader. * or an empty array to use the common classloader.
@@ -38,9 +46,10 @@ public interface ClassLoaderManager
ClassLoader createClassLoader( File[] jars ) throws ClassLoaderException; ClassLoader createClassLoader( File[] jars ) throws ClassLoaderException;


/** /**
* Provides the common ClassLoader, which is the parent of all classloaders
* Returns the common ClassLoader, which is the parent of all classloaders
* built by this ClassLoaderManager. * built by this ClassLoaderManager.
* @return the common ClassLoader
*
* @return the common ClassLoader.
*/ */
ClassLoader getCommonClassLoader(); ClassLoader getCommonClassLoader();
} }

+ 11
- 3
proposal/myrmidon/src/java/org/apache/myrmidon/interfaces/extensions/ExtensionManager.java View File

@@ -7,17 +7,25 @@
*/ */
package org.apache.myrmidon.interfaces.extensions; package org.apache.myrmidon.interfaces.extensions;


import org.apache.avalon.excalibur.extension.PackageRepository;
import org.apache.avalon.excalibur.extension.Extension;
import org.apache.avalon.excalibur.extension.OptionalPackage;


/** /**
* PackageRepository
* Maintains a set of optional packages.
* *
* @author <a href="mailto:peter@apache.org">Peter Donald</a> * @author <a href="mailto:peter@apache.org">Peter Donald</a>
* @version $Revision$ $Date$ * @version $Revision$ $Date$
*/ */
public interface ExtensionManager public interface ExtensionManager
extends PackageRepository
{ {
/** Role name for this interface. */ /** Role name for this interface. */
String ROLE = ExtensionManager.class.getName(); String ROLE = ExtensionManager.class.getName();

/**
* Locates the optional package which best matches a required extension.
*
* @param extension the extension to locate an optional package
* @return the optional package, or null if not found.
*/
public OptionalPackage getOptionalPackage( Extension extension );
} }

+ 35
- 29
proposal/myrmidon/src/test/org/apache/myrmidon/AbstractMyrmidonTest.java View File

@@ -29,28 +29,21 @@ public abstract class AbstractMyrmidonTest
private final File m_baseDir; private final File m_baseDir;
private Logger m_logger; private Logger m_logger;


protected static final Resources getResourcesForTested( final Class clazz )
public AbstractMyrmidonTest( final String name )
{ {
final Package pkg = clazz.getPackage();

String baseName;
if( null == pkg )
{
final String name = clazz.getName();
if( -1 == name.lastIndexOf( "." ) )
{
baseName = "";
}
else
{
baseName = name.substring( 0, name.lastIndexOf( "." ) );
}
}
else
{
baseName = pkg.getName();
}
super( name );
final String baseDirProp = System.getProperty( "test.basedir" );
m_baseDir = getCanonicalFile( new File( baseDirProp ) );
final String packagePath = getPackageName( getClass() ).replace( '.', File.separatorChar );
m_testBaseDir = getCanonicalFile( new File( m_baseDir, packagePath ) );
}


/**
* Locates the error message resources for a class.
*/
protected static final Resources getResourcesForTested( final Class clazz )
{
String baseName = getPackageName( clazz );
if( baseName.endsWith( ".test" ) ) if( baseName.endsWith( ".test" ) )
{ {
baseName = baseName.substring( 0, baseName.length() - 5 ); baseName = baseName.substring( 0, baseName.length() - 5 );
@@ -59,16 +52,29 @@ public abstract class AbstractMyrmidonTest
return ResourceManager.getBaseResources( baseName + ".Resources", AbstractMyrmidonTest.class.getClassLoader() ); return ResourceManager.getBaseResources( baseName + ".Resources", AbstractMyrmidonTest.class.getClassLoader() );
} }


public AbstractMyrmidonTest( String name )
/**
* Returns the name of the package containing a class.
*
* @return The . delimited package name, or an empty string if the class
* is in the default package.
*/
protected static String getPackageName( final Class clazz )
{ {
super( name );
final String baseDirProp = System.getProperty( "test.basedir" );
m_baseDir = getCanonicalFile( new File( baseDirProp ) );
String packagePath = getClass().getName();
int idx = packagePath.lastIndexOf( '.' );
packagePath = packagePath.substring( 0, idx );
packagePath = packagePath.replace( '.', File.separatorChar );
m_testBaseDir = getCanonicalFile( new File( m_baseDir, packagePath ) );
final Package pkg = clazz.getPackage();
if( null != pkg )
{
return pkg.getName();
}

final String name = clazz.getName();
if( -1 == name.lastIndexOf( "." ) )
{
return "";
}
else
{
return name.substring( 0, name.lastIndexOf( "." ) );
}
} }


/** /**


+ 12
- 51
proposal/myrmidon/src/test/org/apache/myrmidon/AbstractProjectTest.java View File

@@ -8,12 +8,7 @@
package org.apache.myrmidon; package org.apache.myrmidon;


import java.io.File; import java.io.File;
import org.apache.avalon.framework.logger.Logger;
import org.apache.avalon.framework.parameters.Parameters;
import org.apache.myrmidon.components.embeddor.DefaultEmbeddor;
import org.apache.myrmidon.interfaces.embeddor.Embeddor;
import org.apache.myrmidon.interfaces.model.Project;
import org.apache.myrmidon.interfaces.workspace.Workspace;
import org.apache.myrmidon.frontends.EmbeddedAnt;
import org.apache.myrmidon.listeners.ProjectListener; import org.apache.myrmidon.listeners.ProjectListener;


/** /**
@@ -25,50 +20,11 @@ import org.apache.myrmidon.listeners.ProjectListener;
public class AbstractProjectTest public class AbstractProjectTest
extends AbstractMyrmidonTest extends AbstractMyrmidonTest
{ {
private DefaultEmbeddor m_embeddor;

public AbstractProjectTest( final String name ) public AbstractProjectTest( final String name )
{ {
super( name ); super( name );
} }


/**
* Tear-down the test.
*/
protected void tearDown() throws Exception
{
if( m_embeddor != null )
{
m_embeddor.dispose();
m_embeddor = null;
}
}

/**
* Returns an embeddor which can be used to build and execute projects.
*/
protected Embeddor getEmbeddor() throws Exception
{
if( m_embeddor == null )
{
// Need to set the context classloader - The default embeddor uses it
Thread.currentThread().setContextClassLoader( getClass().getClassLoader() );

final Logger logger = getLogger();
m_embeddor = new DefaultEmbeddor();
m_embeddor.enableLogging( logger );

final Parameters params = new Parameters();
final File instDir = getInstallDirectory();
params.setParameter( "myrmidon.home", instDir.getAbsolutePath() );
m_embeddor.parameterize( params );
m_embeddor.initialize();
m_embeddor.start();
}

return m_embeddor;
}

/** /**
* Executes a target in a project, and asserts that it fails with the * Executes a target in a project, and asserts that it fails with the
* given error message. * given error message.
@@ -117,22 +73,27 @@ public class AbstractProjectTest
throws Exception throws Exception
{ {
// Create the project and workspace // Create the project and workspace
final Embeddor embeddor = getEmbeddor();
final Project project = embeddor.createProject( projectFile.getAbsolutePath(), null, null );
final Workspace workspace = embeddor.createWorkspace( new Parameters() );
final EmbeddedAnt embeddor = new EmbeddedAnt();
embeddor.setHomeDirectory( getInstallDirectory() );
embeddor.enableLogging( getLogger() );
embeddor.setSharedClassLoader( getClass().getClassLoader() );
embeddor.setProjectFile( projectFile.getAbsolutePath() );
embeddor.setProjectListener( null );


// Add a listener to make sure all is good // Add a listener to make sure all is good
final TrackingProjectListener tracker = new TrackingProjectListener(); final TrackingProjectListener tracker = new TrackingProjectListener();
workspace.addProjectListener( tracker );
embeddor.addProjectListener( tracker );


// Add supplied listener // Add supplied listener
if( listener != null ) if( listener != null )
{ {
workspace.addProjectListener( listener );
embeddor.addProjectListener( listener );
} }


// Now execute the target // Now execute the target
workspace.executeProject( project, targetName );
embeddor.executeTargets( new String[] { targetName } );

embeddor.stop();


// Make sure all expected events were delivered // Make sure all expected events were delivered
tracker.assertComplete(); tracker.assertComplete();


+ 48
- 5
proposal/myrmidon/src/test/org/apache/myrmidon/components/AbstractComponentTest.java View File

@@ -10,12 +10,16 @@ package org.apache.myrmidon.components;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.io.File;
import org.apache.aut.converter.Converter; import org.apache.aut.converter.Converter;
import org.apache.avalon.framework.logger.LogEnabled; import org.apache.avalon.framework.logger.LogEnabled;
import org.apache.avalon.framework.logger.Logger; import org.apache.avalon.framework.logger.Logger;
import org.apache.avalon.framework.service.DefaultServiceManager; import org.apache.avalon.framework.service.DefaultServiceManager;
import org.apache.avalon.framework.service.ServiceManager; import org.apache.avalon.framework.service.ServiceManager;
import org.apache.avalon.framework.service.Serviceable; import org.apache.avalon.framework.service.Serviceable;
import org.apache.avalon.framework.parameters.Parameters;
import org.apache.avalon.framework.parameters.Parameterizable;
import org.apache.avalon.framework.activity.Initializable;
import org.apache.myrmidon.AbstractMyrmidonTest; import org.apache.myrmidon.AbstractMyrmidonTest;
import org.apache.myrmidon.components.classloader.DefaultClassLoaderManager; import org.apache.myrmidon.components.classloader.DefaultClassLoaderManager;
import org.apache.myrmidon.components.configurer.DefaultConfigurer; import org.apache.myrmidon.components.configurer.DefaultConfigurer;
@@ -93,10 +97,9 @@ public abstract class AbstractComponentTest
m_serviceManager.put( Executor.ROLE, component ); m_serviceManager.put( Executor.ROLE, component );
components.add( component ); components.add( component );


final DefaultClassLoaderManager classLoaderMgr = new DefaultClassLoaderManager();
classLoaderMgr.setCommonClassLoader( getClass().getClassLoader() );
m_serviceManager.put( ClassLoaderManager.ROLE, classLoaderMgr );
components.add( classLoaderMgr );
component = createComponent( ClassLoaderManager.ROLE, DefaultClassLoaderManager.class );
m_serviceManager.put( ClassLoaderManager.ROLE, component );
components.add( component );


component = createComponent( ExtensionManager.ROLE, DefaultExtensionManager.class ); component = createComponent( ExtensionManager.ROLE, DefaultExtensionManager.class );
m_serviceManager.put( ExtensionManager.ROLE, component ); m_serviceManager.put( ExtensionManager.ROLE, component );
@@ -132,6 +135,29 @@ public abstract class AbstractComponentTest
} }
} }


// Parameterise the components
final Parameters parameters = getParameters();
for( Iterator iterator = components.iterator(); iterator.hasNext(); )
{
Object obj = iterator.next();
if( obj instanceof Parameterizable )
{
final Parameterizable parameterizable = (Parameterizable)obj;
parameterizable.parameterize( parameters );
}
}

// Initialise the components
for( Iterator iterator = components.iterator(); iterator.hasNext(); )
{
Object obj = iterator.next();
if( obj instanceof Initializable )
{
final Initializable initializable = (Initializable)obj;
initializable.initialize();
}
}

// Register some standard roles // Register some standard roles
// Add some core roles // Add some core roles
final RoleManager roleManager = (RoleManager)getServiceManager().lookup( RoleManager.ROLE ); final RoleManager roleManager = (RoleManager)getServiceManager().lookup( RoleManager.ROLE );
@@ -144,12 +170,29 @@ public abstract class AbstractComponentTest
} }


/** /**
* Creates an instance of a component. Sub-classes can override this
* Creates the parameters for the test. Sub-classes can override this
* method to set-up the parameters.
*/
protected Parameters getParameters()
{
final Parameters parameters = new Parameters();
final String homeDir = getInstallDirectory().getAbsolutePath();
parameters.setParameter( "myrmidon.home", homeDir );
parameters.setParameter( "myrmidon.ext.path", homeDir + File.separatorChar + "ext" );
return parameters;
}

/**
* Creates an instance of a test component. Sub-classes can override this
* method to add a particular implementation to the set of test components. * method to add a particular implementation to the set of test components.
*/ */
protected Object createComponent( final String role, final Class defaultImpl ) protected Object createComponent( final String role, final Class defaultImpl )
throws Exception throws Exception
{ {
if( role.equals( ClassLoaderManager.ROLE ) )
{
return new DefaultClassLoaderManager( getClass().getClassLoader() );
}
return defaultImpl.newInstance(); return defaultImpl.newInstance();
} }




+ 364
- 0
proposal/myrmidon/src/test/org/apache/myrmidon/components/classloader/test/DefaultClassLoaderManagerTestCase.java View File

@@ -0,0 +1,364 @@
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE.txt file.
*/
package org.apache.myrmidon.components.classloader.test;

import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Enumeration;
import org.apache.myrmidon.components.AbstractComponentTest;
import org.apache.myrmidon.components.classloader.DefaultClassLoaderManager;
import org.apache.myrmidon.interfaces.classloader.ClassLoaderManager;
import org.apache.myrmidon.interfaces.classloader.ClassLoaderException;
import org.apache.avalon.framework.parameters.Parameters;
import org.apache.avalon.excalibur.i18n.Resources;

/**
* Test cases for the DefaultClassLoaderManager.
*
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a>
* @version $Revision$ $Date$
*/
public class DefaultClassLoaderManagerTestCase
extends AbstractComponentTest
{
private static final String UNSHARED_PKG_NAME =
getPackageName( DefaultClassLoaderManagerTestCase.class ) + ".libs.unshared";
private static final String UNSHARED_RES_NAME = getResourceName( UNSHARED_PKG_NAME, "unshared.txt" );
private static final String UNSHARED_CLASS_NAME = UNSHARED_PKG_NAME + ".UnsharedClass";

private static final String SHARED_PKG_NAME =
getPackageName( DefaultClassLoaderManagerTestCase.class ) + ".libs.shared";
private static final String SHARED_RES_NAME = getResourceName( SHARED_PKG_NAME, "shared.txt" );
private static final String SHARED_CLASS_NAME = SHARED_PKG_NAME + ".SharedClass";

private static final String EXTN_PKG_NAME =
getPackageName( DefaultClassLoaderManagerTestCase.class ) + ".libs.extn";
private static final String EXTN_RES_NAME = getResourceName( EXTN_PKG_NAME, "extn.txt" );
private static final String EXTN_CLASS_NAME = EXTN_PKG_NAME + ".ExtnClass";

private File m_commonJar;
private ClassLoader m_commonClassLoader;
private ClassLoaderManager m_loaderManager;

public DefaultClassLoaderManagerTestCase( final String name )
{
super( name );
}

/**
* Sets up the test.
*/
protected void setUp() throws Exception
{
m_commonJar = getTestResource( "common.jar" );
final URL commonJarUrl = m_commonJar.toURL();
m_commonClassLoader = new URLClassLoader( new URL[]{commonJarUrl} );

assertClassFound( m_commonClassLoader, SHARED_CLASS_NAME );
assertResourcesFound( m_commonClassLoader, SHARED_RES_NAME, m_commonJar );

// Create the classloader mgr
m_loaderManager = (ClassLoaderManager)getServiceManager().lookup( ClassLoaderManager.ROLE );
}

/**
* Creates an instance of a test component.
*/
protected Object createComponent( final String role, final Class defaultImpl )
throws Exception
{
if( role.equals( ClassLoaderManager.ROLE ) )
{
return new DefaultClassLoaderManager( m_commonClassLoader );
}
else
{
return super.createComponent( role, defaultImpl );
}
}

/**
* Creates the parameters for the test. Sub-classes can override this
* method to set-up the parameters.
*/
protected Parameters getParameters()
{
final Parameters parameters = super.getParameters();
parameters.setParameter( "myrmidon.ext.path", getTestDirectory( "ext" ).getAbsolutePath() );
return parameters;
}

/**
* Returns the name of a resource in a package.
*/
private static String getResourceName( final String pkgName,
final String resname )
{
return pkgName.replace( '.', '/' ) + '/' + resname;
}

/**
* Asserts that a class is not available in a classloader.
*/
private void assertClassNotFound( final ClassLoader classLoader,
final String className )
{
try
{
classLoader.loadClass( className );
fail( "Class " + className + " should not be available." );
}
catch( ClassNotFoundException e )
{
}
}

/**
* Asserts that a class is available in a classloader.
*/
private void assertClassFound( final ClassLoader classLoader,
final String className )
throws Exception
{
assertClassFound( classLoader, className, classLoader );
}

/**
* Asserts that a class is available in a classloader.
*/
private void assertClassFound( final ClassLoader classLoader,
final String className,
final ClassLoader expectedClassLoader )
throws Exception
{
try
{
final Class cls = classLoader.loadClass( className );
assertSame( expectedClassLoader, cls.getClassLoader() );
if( classLoader != expectedClassLoader )
{
final Class expectedCls = expectedClassLoader.loadClass( className );
assertSame( expectedCls, cls );
}
}
catch( ClassNotFoundException e )
{
fail( "Class " + className + " not found." );
}

}

/**
* Asserts that a resouce is not available in a classloader.
*/
private void assertResourceNotFound( final ClassLoader classLoader,
final String resName )
throws Exception
{
assertNull( classLoader.getResource( resName ) );
assertNull( classLoader.getResourceAsStream( resName ) );
final Enumeration enum = classLoader.getResources( resName );
assertTrue( !enum.hasMoreElements() );
}

/**
* Asserts that a resource is available in a classloader.
*/
private void assertResourcesFound( final ClassLoader classLoader,
final String resName,
final File expectedJar )
throws Exception
{
assertResourcesFound( classLoader, resName, new File[]{expectedJar} );
}

/**
* Asserts that a resource is available in a classloader.
*/
private void assertResourcesFound( final ClassLoader classLoader,
final String resName,
final File[] expectedJars )
throws Exception
{
final String[] expectedLocations = new String[ expectedJars.length ];
for( int i = 0; i < expectedJars.length; i++ )
{
final File jar = expectedJars[ i ];
expectedLocations[ i ] = "jar:" + jar.toURL() + "!/" + resName;
}

assertResourcesFound( classLoader, resName, expectedLocations );
}

/**
* Asserts that a resource is available in a classloader.
*/
private void assertResourcesFound( final ClassLoader classLoader,
final String resName,
final String[] expectedLocations )
throws Exception
{
// Use the first in the list of expected locations as the location
// of the resource returned by getResource()
final URL resUrl = classLoader.getResource( resName );
assertNotNull( resUrl );
assertEquals( expectedLocations[ 0 ], resUrl.toString() );

// Now check all of the resources returned by getResources()
final Enumeration resources = classLoader.getResources( resName );
for( int i = 0; i < expectedLocations.length; i++ )
{
final String expectedLocation = expectedLocations[ i ];
assertTrue( resources.hasMoreElements() );
final URL location = (URL)resources.nextElement();
assertEquals( expectedLocation, location.toString() );
}
assertTrue( !resources.hasMoreElements() );
}

/**
* Tests for a Jar with no required extensions.
*/
public void testNoDependencies() throws Exception
{
// Make some assumptions about the common classloader
assertClassNotFound( m_commonClassLoader, UNSHARED_CLASS_NAME );
assertResourceNotFound( m_commonClassLoader, UNSHARED_RES_NAME );

// Build the classloader
final File jarFile = getTestResource( "no-dependencies.jar" );
final ClassLoader classLoader = m_loaderManager.getClassLoader( jarFile );

// Check shared classes/resources
assertClassFound( classLoader, SHARED_CLASS_NAME, m_commonClassLoader );
assertResourcesFound( classLoader, SHARED_RES_NAME, new File[]{m_commonJar, jarFile} );

// Check unshared classes/resources
assertClassFound( classLoader, UNSHARED_CLASS_NAME );
assertResourcesFound( classLoader, UNSHARED_RES_NAME, jarFile );
}

/**
* Tests ClassLoader caching.
*/
public void testClassLoaderReuse() throws Exception
{
final File jarFile = getTestResource( "no-dependencies.jar" );
final ClassLoader classLoader1 = m_loaderManager.getClassLoader( jarFile );
final ClassLoader classLoader2 = m_loaderManager.getClassLoader( jarFile );
assertSame( classLoader1, classLoader2 );
}

/**
* Tests for a Jar with a single required extension.
*/
public void testOneDependency() throws Exception
{
// Make some assumptions about the common classloader
assertClassNotFound( m_commonClassLoader, UNSHARED_CLASS_NAME );
assertResourceNotFound( m_commonClassLoader, UNSHARED_RES_NAME );
assertClassNotFound( m_commonClassLoader, EXTN_CLASS_NAME );
assertResourceNotFound( m_commonClassLoader, EXTN_RES_NAME );

// Build the extension classloader
final File extnJarFile = getTestResource( "ext/simple-extension.jar" );
final ClassLoader extnClassLoader = m_loaderManager.getClassLoader( extnJarFile );

// Build the Jar classloader
final File jarFile = getTestResource( "one-dependency.jar" );
final ClassLoader classLoader = m_loaderManager.getClassLoader( jarFile );

// Check shared classes/resources
assertClassFound( classLoader, SHARED_CLASS_NAME, m_commonClassLoader );
assertResourcesFound( classLoader, SHARED_RES_NAME, new File[]{m_commonJar, extnJarFile, jarFile} );

// Check extension classes/resources
assertClassFound( classLoader, EXTN_CLASS_NAME, extnClassLoader );
assertResourcesFound( classLoader, EXTN_RES_NAME, extnJarFile );

// Check unshared classes/resources
assertClassFound( classLoader, UNSHARED_CLASS_NAME );
assertResourcesFound( classLoader, UNSHARED_RES_NAME, jarFile );
}

/**
* Tests that classes from extensions can be shared across classloaders.
*/
public void testShareClasses() throws Exception
{
// Build the extension classloader
final File extnJarFile = getTestResource( "ext/simple-extension.jar" );
final ClassLoader extnClassLoader = m_loaderManager.getClassLoader( extnJarFile );

// Build the Jar classloaders
final File jarFile1 = getTestResource( "one-dependency.jar" );
final ClassLoader classLoader1 = m_loaderManager.getClassLoader( jarFile1 );
final File jarFile2 = getTestResource( "one-dependency-2.jar" );
final ClassLoader classLoader2 = m_loaderManager.getClassLoader( jarFile2 );

// Check extension classes/resources
assertClassFound( classLoader1, EXTN_CLASS_NAME, extnClassLoader );
assertResourcesFound( classLoader1, EXTN_RES_NAME, extnJarFile );
assertClassFound( classLoader2, EXTN_CLASS_NAME, extnClassLoader );
assertResourcesFound( classLoader2, EXTN_RES_NAME, extnJarFile );
}

/**
* Tests detection of dependency cycles in extensions.
*/
public void testCycle() throws Exception
{
final File jarFile = getTestResource( "ext/cycle-extension-1.jar" );
try
{
m_loaderManager.getClassLoader( jarFile );
fail();
}
catch( final ClassLoaderException e )
{
final Resources rez = getResourcesForTested( DefaultClassLoaderManager.class );
final String[] messages = {
rez.getString( "create-classloader-for-file.error", jarFile ),
rez.getString( "dependency-cycle.error", jarFile )
};
assertSameMessage( messages, e );
}
}

/**
* add some classes to common loader only.
*
* unknown extension
* multiple versions of extensions
* extn with requirement on itself
*
* jar with 1 and 2 extns:
* class/resources in parent
* class/resources in jar
* class/resources in extn
* class/resources in all
*
* jar with transitive extn
* class/resources in 2nd extn
*
* jar with transitive extn + explicit extn on same jar
* class/resources in 2nd extn
*
* Same classes:
* get extn explicitly and implicitly, and check classes are the same
* extn shared by 2 jars, using same extn and different extns
* classes in common classloader, shared by 2 jars
*
* multiple files:
* fetch classloader twice
* different path ordering
*
* tools.jar
*/
}

+ 10
- 0
proposal/myrmidon/src/test/org/apache/myrmidon/components/classloader/test/libs/cycle-extension-1.mf View File

@@ -0,0 +1,10 @@
Extension-Name: test.cycle1
Specification-Title: Test Extension
Specification-Version: 1.0.0
Specification-Vendor: Jakarta Apache
Implementation-Vendor-Id: org.apache.myrmidon
Implementation-Vendor: Apache Myrmidon Project
Implementation-Version: 3.0
Extension-List: cycle2
cycle2-Extension-Name: test.cycle2
cycle2-Specification-Version: 1.0

+ 10
- 0
proposal/myrmidon/src/test/org/apache/myrmidon/components/classloader/test/libs/cycle-extension-2.mf View File

@@ -0,0 +1,10 @@
Extension-Name: test.cycle2
Specification-Title: Test Extension
Specification-Version: 1.0.0
Specification-Vendor: Jakarta Apache
Implementation-Vendor-Id: org.apache.myrmidon
Implementation-Vendor: Apache Myrmidon Project
Implementation-Version: 1.709.2
Extension-List: cycle1
cycle1-Extension-Name: test.cycle1
cycle1-Specification-Version: 1.0

+ 21
- 0
proposal/myrmidon/src/test/org/apache/myrmidon/components/classloader/test/libs/extn/ExtnClass.java View File

@@ -0,0 +1,21 @@
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE.txt file.
*/
package org.apache.myrmidon.components.classloader.test.libs.extn;

import org.apache.myrmidon.components.classloader.test.libs.shared.SharedClass;

/**
* A test class loaded from an extension.
*
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a>
* @version $Revision$ $Date$
*/
public class ExtnClass
{
public SharedClass m_test = new SharedClass();
}

+ 1
- 0
proposal/myrmidon/src/test/org/apache/myrmidon/components/classloader/test/libs/extn/extn.txt View File

@@ -0,0 +1 @@
A test resource loaded from an extension.

+ 3
- 0
proposal/myrmidon/src/test/org/apache/myrmidon/components/classloader/test/libs/one-dependency.mf View File

@@ -0,0 +1,3 @@
Extension-List: extension1
extension1-Extension-Name: test.simple
extension1-Specification-Version: 1.0

+ 18
- 0
proposal/myrmidon/src/test/org/apache/myrmidon/components/classloader/test/libs/shared/SharedClass.java View File

@@ -0,0 +1,18 @@
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE.txt file.
*/
package org.apache.myrmidon.components.classloader.test.libs.shared;

/**
* A test class.
*
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a>
* @version $Revision$ $Date$
*/
public class SharedClass
{
}

+ 1
- 0
proposal/myrmidon/src/test/org/apache/myrmidon/components/classloader/test/libs/shared/shared.txt View File

@@ -0,0 +1 @@
A shared resource.

+ 7
- 0
proposal/myrmidon/src/test/org/apache/myrmidon/components/classloader/test/libs/simple-extension.mf View File

@@ -0,0 +1,7 @@
Extension-Name: test.simple
Specification-Title: Test Simple Extension
Specification-Version: 1.0.0
Specification-Vendor: Jakarta Apache
Implementation-Vendor-Id: org.apache.myrmidon
Implementation-Vendor: Apache Myrmidon Project
Implementation-Version: 1.0.2

+ 21
- 0
proposal/myrmidon/src/test/org/apache/myrmidon/components/classloader/test/libs/unshared/UnsharedClass.java View File

@@ -0,0 +1,21 @@
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE.txt file.
*/
package org.apache.myrmidon.components.classloader.test.libs.unshared;

import org.apache.myrmidon.components.classloader.test.libs.shared.SharedClass;

/**
* A test class.
*
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a>
* @version $Revision$ $Date$
*/
public class UnsharedClass
{
public SharedClass m_test = new SharedClass();
}

+ 1
- 0
proposal/myrmidon/src/test/org/apache/myrmidon/components/classloader/test/libs/unshared/unshared.txt View File

@@ -0,0 +1 @@
An unshared resource.

+ 42
- 0
proposal/myrmidon/src/test/org/apache/myrmidon/components/embeddor/test/DefaultEmbeddorTest.java View File

@@ -9,8 +9,10 @@ package org.apache.myrmidon.components.embeddor.test;


import java.io.File; import java.io.File;
import org.apache.avalon.framework.parameters.Parameters; import org.apache.avalon.framework.parameters.Parameters;
import org.apache.avalon.framework.logger.Logger;
import org.apache.myrmidon.AbstractProjectTest; import org.apache.myrmidon.AbstractProjectTest;
import org.apache.myrmidon.LogMessageTracker; import org.apache.myrmidon.LogMessageTracker;
import org.apache.myrmidon.components.embeddor.DefaultEmbeddor;
import org.apache.myrmidon.interfaces.embeddor.Embeddor; import org.apache.myrmidon.interfaces.embeddor.Embeddor;
import org.apache.myrmidon.interfaces.model.Project; import org.apache.myrmidon.interfaces.model.Project;
import org.apache.myrmidon.interfaces.model.Target; import org.apache.myrmidon.interfaces.model.Target;
@@ -26,11 +28,50 @@ import org.apache.myrmidon.listeners.ProjectListener;
public class DefaultEmbeddorTest public class DefaultEmbeddorTest
extends AbstractProjectTest extends AbstractProjectTest
{ {
private DefaultEmbeddor m_embeddor;

public DefaultEmbeddorTest( String name ) public DefaultEmbeddorTest( String name )
{ {
super( name ); super( name );
} }


/**
* Tear-down the test.
*/
protected void tearDown() throws Exception
{
if( m_embeddor != null )
{
m_embeddor.dispose();
m_embeddor = null;
}
}

/**
* Returns an embeddor which can be used to build and execute projects.
*/
protected Embeddor getEmbeddor() throws Exception
{
if( m_embeddor == null )
{
// Need to set the context classloader - The default embeddor uses it
Thread.currentThread().setContextClassLoader( getClass().getClassLoader() );

final Logger logger = getLogger();
m_embeddor = new DefaultEmbeddor();
m_embeddor.enableLogging( logger );

final Parameters params = new Parameters();
final File instDir = getInstallDirectory();
params.setParameter( "myrmidon.home", instDir.getAbsolutePath() );
m_embeddor.parameterize( params );
m_embeddor.initialize();
m_embeddor.start();
}

return m_embeddor;
}

/** /**
* Tests that a project is successfully built from a file. * Tests that a project is successfully built from a file.
*/ */
@@ -63,6 +104,7 @@ public class DefaultEmbeddorTest
public void testCreateListener() throws Exception public void testCreateListener() throws Exception
{ {
final ProjectListener listener = getEmbeddor().createListener( "default" ); final ProjectListener listener = getEmbeddor().createListener( "default" );
assertNotNull( listener );
} }


/** /**


+ 15
- 11
proposal/myrmidon/src/xdocs/todo.xml View File

@@ -524,23 +524,12 @@ public class MyrmidonSecurityManager
<code>&lt;socket&gt;</code> <code>&lt;socket&gt;</code>
conditions to an antlib. Need to resolve how these will be passed a logger. conditions to an antlib. Need to resolve how these will be passed a logger.
</li> </li>
<li>Make the
<code>&lt;uptodate&gt;</code> task a condition, and move to
an antlib.
</li>
<li>Split up
<code>&lt;is-set&gt;</code> condition into is-set and is-true conditions.
</li>
<li>Allow the <li>Allow the
<code>&lt;if&gt;</code> task to take any condition implementation. <code>&lt;if&gt;</code> task to take any condition implementation.
</li> </li>
<li>Add an else block to the <li>Add an else block to the
<code>&lt;if&gt;</code> task. <code>&lt;if&gt;</code> task.
</li> </li>
<li>Split the
<code>&lt;available&gt;</code> condition into separate conditions
that test for the availability of a class, or a resource.
</li>
<li>Move <li>Move
<code>crimson.jar</code> to <code>crimson.jar</code> to
<code>bin/lib</code> in the distribution, <code>bin/lib</code> in the distribution,
@@ -550,6 +539,21 @@ public class MyrmidonSecurityManager
<li>Add a <code>--type</code> command-line option, to allow <li>Add a <code>--type</code> command-line option, to allow
the project builder to be manually selected. the project builder to be manually selected.
</li> </li>
<li>Change <code>ProjectBuilder</code>
and <code>Embeddor</code> to throw something more
specialised than Exception.
</li>
<li>Change <code>DefaultClassLoaderManager</code> to handle
directories as part of a library classpath.
</li>
<li><code>&lt;condition&gt;</code> should set the property
value to <code>false</code> when the condition is false.</li>
<li>Split the <code>&lt;uptodate&gt;</code> condition into
a condition that checks against a single target file,
and one which checks using a destdir/mapper.</li>
<li>Add a task to unset a property.</li>
<li>Change the various def and import task to allow a classpath
to be provided.</li>
<li>Unit tests.</li> <li>Unit tests.</li>
</ul> </ul>




Loading…
Cancel
Save