diff --git a/proposal/myrmidon/src/java/org/apache/myrmidon/components/workspace/DefaultWorkspace.java b/proposal/myrmidon/src/java/org/apache/myrmidon/components/workspace/DefaultWorkspace.java
new file mode 100644
index 000000000..0acc9dc08
--- /dev/null
+++ b/proposal/myrmidon/src/java/org/apache/myrmidon/components/workspace/DefaultWorkspace.java
@@ -0,0 +1,439 @@
+/*
+ * 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 file.
+ */
+package org.apache.myrmidon.components.workspace;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import org.apache.avalon.framework.activity.Disposable;
+import org.apache.avalon.framework.activity.Initializable;
+import org.apache.avalon.framework.component.ComponentException;
+import org.apache.avalon.framework.component.ComponentManager;
+import org.apache.avalon.framework.component.Composable;
+import org.apache.avalon.framework.component.DefaultComponentManager;
+import org.apache.avalon.framework.configuration.Configuration;
+import org.apache.avalon.framework.logger.AbstractLoggable;
+import org.apache.avalon.framework.parameters.ParameterException;
+import org.apache.avalon.framework.parameters.Parameterizable;
+import org.apache.avalon.framework.parameters.Parameters;
+import org.apache.log.Logger;
+import org.apache.myrmidon.api.DefaultTaskContext;
+import org.apache.myrmidon.api.TaskContext;
+import org.apache.myrmidon.api.TaskException;
+import org.apache.myrmidon.components.deployer.DefaultDeployer;
+import org.apache.myrmidon.components.deployer.Deployer;
+import org.apache.myrmidon.components.deployer.DeploymentException;
+import org.apache.myrmidon.components.executor.DefaultExecutionFrame;
+import org.apache.myrmidon.components.executor.ExecutionFrame;
+import org.apache.myrmidon.components.executor.Executor;
+import org.apache.myrmidon.components.model.Project;
+import org.apache.myrmidon.components.model.Target;
+import org.apache.myrmidon.components.model.TypeLib;
+import org.apache.myrmidon.components.type.TypeManager;
+import org.apache.myrmidon.framework.Condition;
+import org.apache.myrmidon.listeners.ProjectListener;
+
+/**
+ * This is the default implementation of Workspace.
+ *
+ * @author Peter Donald
+ */
+public class DefaultWorkspace
+ extends AbstractLoggable
+ implements Workspace, Composable, Parameterizable, Initializable
+{
+ private Executor m_executor;
+ private ProjectListenerSupport m_listenerSupport = new ProjectListenerSupport();
+ private ComponentManager m_componentManager;
+ private Parameters m_parameters;
+ private Project m_project;
+ private TaskContext m_baseContext;
+ private HashMap m_entrys = new HashMap();
+ private TypeManager m_typeManager;
+
+ /**
+ * Add a listener to project events.
+ *
+ * @param listener the listener
+ */
+ public void addProjectListener( final ProjectListener listener )
+ {
+ m_listenerSupport.addProjectListener( listener );
+ }
+
+ /**
+ * Remove a listener from project events.
+ *
+ * @param listener the listener
+ */
+ public void removeProjectListener( final ProjectListener listener )
+ {
+ m_listenerSupport.removeProjectListener( listener );
+ }
+
+ /**
+ * Retrieve relevent services needed for engine.
+ *
+ * @param componentManager the ComponentManager
+ * @exception ComponentException if an error occurs
+ */
+ public void compose( final ComponentManager componentManager )
+ throws ComponentException
+ {
+ m_componentManager = componentManager;
+
+ m_typeManager = (TypeManager)componentManager.lookup( TypeManager.ROLE );
+ m_executor = (Executor)componentManager.lookup( Executor.ROLE );
+ m_project = (Project)componentManager.lookup( Project.ROLE );
+ }
+
+ public void parameterize( final Parameters parameters )
+ throws ParameterException
+ {
+ m_parameters = parameters;
+ }
+
+ public void initialize()
+ throws Exception
+ {
+ m_baseContext = createBaseContext();
+ }
+
+ /**
+ * Execute a target in a particular project.
+ * Execute in the project context.
+ *
+ * @param project the Project
+ * @param target the name of the target
+ * @exception TaskException if an error occurs
+ */
+ public void executeProject( final Project project, final String target )
+ throws TaskException
+ {
+ final ProjectEntry entry = getProjectEntry( project );
+
+ m_listenerSupport.projectStarted();
+
+ executeTarget( "", project.getImplicitTarget(), entry.getFrame() );
+
+ execute( project, target, entry );
+
+ m_listenerSupport.projectFinished();
+ }
+
+
+ private TaskContext createBaseContext()
+ throws TaskException
+ {
+ final TaskContext context = new DefaultTaskContext();
+
+ final String[] names = m_parameters.getNames();
+ for( int i = 0; i < names.length; i++ )
+ {
+ final String value = m_parameters.getParameter( names[ i ], null );
+ context.setProperty( names[ i ], value );
+ }
+
+ //Add system properties so that they overide user-defined properties
+ addToContext( context, System.getProperties() );
+
+ return context;
+ }
+
+ private File findTypeLib( final String libraryName )
+ throws TaskException
+ {
+ //TODO: In future this will be expanded to allow
+ //users to specify search path or automagically
+ //add entries to lib path (like user specific or
+ //workspace specific)
+ final String name = libraryName.replace( '/', File.separatorChar ) + ".atl";
+
+ final String home = System.getProperty( "myrmidon.home" );
+ final File homeDir = new File( home + File.separatorChar + "ext" );
+
+ final File library = new File( homeDir, name );
+
+ if( library.exists() )
+ {
+ if( !library.canRead() )
+ {
+ throw new TaskException( "Unable to read library at " + library );
+ }
+ else
+ {
+ return library;
+ }
+ }
+
+ throw new TaskException( "Unable to locate Type Library " + libraryName );
+ }
+
+ private void deployTypeLib( final Deployer deployer, final Project project )
+ throws TaskException
+ {
+ final TypeLib[] typeLibs = project.getTypeLibs();
+
+ for( int i = 0; i < typeLibs.length; i++ )
+ {
+ final TypeLib typeLib = typeLibs[ i ];
+ final File file = findTypeLib( typeLib.getLibrary() );
+
+ try
+ {
+ if( null == typeLib.getRole() )
+ {
+ deployer.deploy( file );
+ }
+ else
+ {
+ deployer.deployType( typeLib.getRole(), typeLib.getName(), file );
+ }
+ }
+ catch( final DeploymentException de )
+ {
+ throw new TaskException( "Error deploying type library " +
+ typeLib + " at " + file, de );
+ }
+ }
+ }
+
+ private ExecutionFrame createExecutionFrame( final Project project )
+ throws TaskException
+ {
+ final TaskContext context = new DefaultTaskContext( m_baseContext );
+ context.setProperty( TaskContext.BASE_DIRECTORY, project.getBaseDirectory() );
+
+ //Create per frame ComponentManager
+ final DefaultComponentManager componentManager =
+ new DefaultComponentManager( m_componentManager );
+
+ //Add in child type manager so each frame can register different
+ //sets of tasks etc
+ final TypeManager typeManager = m_typeManager.createChildTypeManager();
+ componentManager.put( TypeManager.ROLE, typeManager );
+
+ //We need to create a new deployer so that it deploys
+ //to project specific TypeManager
+ final DefaultDeployer deployer = new DefaultDeployer();
+ deployer.setLogger( getLogger() );
+
+ try { deployer.compose( componentManager ); }
+ catch( final ComponentException ce )
+ {
+ throw new TaskException( "Error configuring deployer", ce );
+ }
+
+ //HACK: Didn't call initialize because Deployer contained in Embeddor
+ // Already initialized and this would be reduendent
+ //deployer.initialize();
+
+ componentManager.put( Deployer.ROLE, deployer );
+
+ deployTypeLib( deployer, project );
+
+ //We need to place projects and ProjectManager
+ //in ComponentManager so as to support project-local call()
+ componentManager.put( Workspace.ROLE, this );
+ componentManager.put( Project.ROLE, project );
+
+ final String[] names = project.getProjectNames();
+ for( int i = 0; i < names.length; i++ )
+ {
+ final String name = names[ i ];
+ final Project other = project.getProject( name );
+ componentManager.put( Project.ROLE + "/" + name, other );
+ }
+
+ final DefaultExecutionFrame frame = new DefaultExecutionFrame();
+
+ try
+ {
+
+ frame.setLogger( getLogger() );
+ frame.contextualize( context );
+ frame.compose( componentManager );
+ }
+ catch( final Exception e )
+ {
+ throw new TaskException( "Error setting up ExecutionFrame", e );
+ }
+
+ return frame;
+ }
+
+ private ProjectEntry getProjectEntry( final Project project )
+ throws TaskException
+ {
+ ProjectEntry entry = (ProjectEntry)m_entrys.get( project );
+
+ if( null == entry )
+ {
+ final ExecutionFrame frame = createExecutionFrame( project );
+ entry = new ProjectEntry( project, frame );
+ m_entrys.put( project, entry );
+ }
+
+ return entry;
+ }
+
+ private Project getProject( final String name, final Project project )
+ throws TaskException
+ {
+ final Project other = project.getProject( name );
+
+ if( null == other )
+ {
+ //TODO: Fix this so location information included in description
+ throw new TaskException( "Project '" + name + "' not found." );
+ }
+
+ return other;
+ }
+
+ /**
+ * Helper method to execute a target.
+ *
+ * @param project the Project
+ * @param target the name of the target
+ * @param context the context
+ * @param done the list of targets already executed in current run
+ * @exception TaskException if an error occurs
+ */
+ private void execute( final Project project,
+ final String targetName,
+ final ProjectEntry entry )
+ throws TaskException
+ {
+ final int index = targetName.indexOf( "->" );
+ if( -1 != index )
+ {
+ final String name = targetName.substring( 0, index );
+ final String otherTargetName = targetName.substring( index + 2 );
+
+ final Project otherProject = getProject( name, project );
+ final ProjectEntry otherEntry = getProjectEntry( otherProject );
+
+ //Execute target in referenced project
+ execute( otherProject, otherTargetName, otherEntry );
+ return;
+ }
+
+ final Target target = project.getTarget( targetName );
+ if( null == target )
+ {
+ throw new TaskException( "Unable to find target " + targetName );
+ }
+
+ //add target to list of targets executed
+ entry.completeTarget( targetName );
+
+ //execute all dependencies
+ final String[] dependencies = target.getDependencies();
+ for( int i = 0; i < dependencies.length; i++ )
+ {
+ if( !entry.isTargetCompleted( dependencies[ i ] ) )
+ {
+ execute( project, dependencies[ i ], entry );
+ }
+ }
+
+ //notify listeners
+ m_listenerSupport.targetStarted( targetName );
+
+ executeTarget( targetName, target, entry.getFrame() );
+
+ //notify listeners
+ m_listenerSupport.targetFinished();
+ }
+
+ /**
+ * Method to execute a particular target instance.
+ *
+ * @param targetName the name of target
+ * @param target the target
+ * @param context the context in which to execute
+ * @exception TaskException if an error occurs
+ */
+ private void executeTarget( final String name,
+ final Target target,
+ final ExecutionFrame frame )
+ throws TaskException
+ {
+ //check the condition associated with target.
+ //if it is not satisfied then skip target
+ final Condition condition = target.getCondition();
+ if( null != condition )
+ {
+ if( false == condition.evaluate( frame.getContext() ) )
+ {
+ getLogger().debug( "Skipping target " + name +
+ " as it does not satisfy condition" );
+ return;
+ }
+ }
+
+ getLogger().debug( "Executing target " + name );
+
+ //frame.getContext().setProperty( Project.TARGET, target );
+
+ //execute all tasks assciated with target
+ final Configuration[] tasks = target.getTasks();
+ for( int i = 0; i < tasks.length; i++ )
+ {
+ executeTask( tasks[ i ], frame );
+ }
+ }
+
+ /**
+ * Execute a task.
+ *
+ * @param task the task definition
+ * @param context the context
+ * @exception TaskException if an error occurs
+ */
+ private void executeTask( final Configuration task, final ExecutionFrame frame )
+ throws TaskException
+ {
+ final String name = task.getName();
+ getLogger().debug( "Executing task " + name );
+
+ //is setting name even necessary ???
+ frame.getContext().setProperty( TaskContext.NAME, name );
+
+ //notify listeners
+ m_listenerSupport.taskStarted( name );
+
+ //run task
+ m_executor.execute( task, frame );
+
+ //notify listeners task has ended
+ m_listenerSupport.taskFinished();
+ }
+
+ /**
+ * Helper method to add values to a context
+ *
+ * @param context the context
+ * @param map the map of names->values
+ */
+ private void addToContext( final TaskContext context, final Map map )
+ throws TaskException
+ {
+ final Iterator keys = map.keySet().iterator();
+
+ while( keys.hasNext() )
+ {
+ final String key = (String)keys.next();
+ final Object value = map.get( key );
+ context.setProperty( key, value );
+ }
+ }
+}
diff --git a/proposal/myrmidon/src/java/org/apache/myrmidon/components/workspace/LogTargetToListenerAdapter.java b/proposal/myrmidon/src/java/org/apache/myrmidon/components/workspace/LogTargetToListenerAdapter.java
new file mode 100644
index 000000000..19eaf1ff0
--- /dev/null
+++ b/proposal/myrmidon/src/java/org/apache/myrmidon/components/workspace/LogTargetToListenerAdapter.java
@@ -0,0 +1,50 @@
+/*
+ * 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 file.
+ */
+package org.apache.myrmidon.components.workspace;
+
+import org.apache.log.LogEvent;
+import org.apache.log.LogTarget;
+import org.apache.myrmidon.listeners.ProjectListener;
+
+/**
+ * Adapter between Avalon LogKit and Project listener interfaces.
+ *
+ * @author Peter Donald
+ */
+public class LogTargetToListenerAdapter
+ implements LogTarget
+{
+ private final ProjectListener m_listener;
+
+ /**
+ * Constructor taking listener to convert to.
+ *
+ * @param listener the ProjectListener
+ */
+ public LogTargetToListenerAdapter( final ProjectListener listener )
+ {
+ m_listener = listener;
+ }
+
+ /**
+ * Process a log event.
+ *
+ * @param event the event
+ */
+ public void processEvent( final LogEvent event )
+ {
+ if( null == event.getThrowable() )
+ {
+ m_listener.log( event.getMessage() );
+ }
+ else
+ {
+ m_listener.log( event.getMessage(), event.getThrowable() );
+ }
+ }
+}
diff --git a/proposal/myrmidon/src/java/org/apache/myrmidon/components/workspace/ProjectEntry.java b/proposal/myrmidon/src/java/org/apache/myrmidon/components/workspace/ProjectEntry.java
new file mode 100644
index 000000000..245c71fd3
--- /dev/null
+++ b/proposal/myrmidon/src/java/org/apache/myrmidon/components/workspace/ProjectEntry.java
@@ -0,0 +1,51 @@
+/*
+ * 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 file.
+ */
+package org.apache.myrmidon.components.workspace;
+
+import java.util.ArrayList;
+import org.apache.myrmidon.components.executor.ExecutionFrame;
+import org.apache.myrmidon.components.model.Project;
+
+/**
+ * This contains detaisl for each project that is managed by ProjectManager.
+ *
+ * @author Peter Donald
+ */
+public final class ProjectEntry
+{
+ private final Project m_project;
+ private final ExecutionFrame m_frame;
+ private final ArrayList m_targetsCompleted = new ArrayList();
+
+ public ProjectEntry( final Project project,
+ final ExecutionFrame frame )
+ {
+ m_project = project;
+ m_frame = frame;
+ }
+
+ public Project getProject()
+ {
+ return m_project;
+ }
+
+ public ExecutionFrame getFrame()
+ {
+ return m_frame;
+ }
+
+ public boolean isTargetCompleted( final String target )
+ {
+ return m_targetsCompleted.contains( target );
+ }
+
+ public void completeTarget( final String target )
+ {
+ m_targetsCompleted.add( target );
+ }
+}
diff --git a/proposal/myrmidon/src/java/org/apache/myrmidon/components/workspace/ProjectListenerSupport.java b/proposal/myrmidon/src/java/org/apache/myrmidon/components/workspace/ProjectListenerSupport.java
new file mode 100644
index 000000000..68841647b
--- /dev/null
+++ b/proposal/myrmidon/src/java/org/apache/myrmidon/components/workspace/ProjectListenerSupport.java
@@ -0,0 +1,160 @@
+/*
+ * 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 file.
+ */
+package org.apache.myrmidon.components.workspace;
+
+import org.apache.myrmidon.listeners.ProjectListener;
+
+/**
+ * Support for the project listener event dispatching.
+ *
+ * @author Peter Donald
+ */
+public class ProjectListenerSupport
+ implements ProjectListener
+{
+ private ProjectListener[] m_listeners = new ProjectListener[ 0 ];
+
+ /**
+ * Add an extra project listener that wants to receive notification of listener events.
+ *
+ * @param listener the listener
+ */
+ public void addProjectListener( final ProjectListener listener )
+ {
+ final ProjectListener[] listeners = new ProjectListener[ m_listeners.length + 1 ];
+ System.arraycopy( m_listeners, 0, listeners, 0, m_listeners.length );
+ listeners[ m_listeners.length ] = listener;
+ m_listeners = listeners;
+ }
+
+ /**
+ * Remove a project listener that wants to receive notification of listener events.
+ *
+ * @param listener the listener
+ */
+ public void removeProjectListener( final ProjectListener listener )
+ {
+ int found = -1;
+
+ for( int i = 0; i < m_listeners.length; i++ )
+ {
+ if( listener == m_listeners[ i ] )
+ {
+ found = i;
+ break;
+ }
+ }
+
+ if( -1 == found ) return;
+
+ final ProjectListener[] listeners = new ProjectListener[ m_listeners.length - 1 ];
+ System.arraycopy( m_listeners, 0, listeners, 0, found );
+
+ final int count = m_listeners.length - found - 1;
+ System.arraycopy( m_listeners, found, listeners, found + 1, count );
+
+ m_listeners = listeners;
+ }
+
+ /**
+ * Fire a projectStarted event.
+ */
+ public void projectStarted()
+ {
+ for( int i = 0; i < m_listeners.length; i++ )
+ {
+ m_listeners[ i ].projectStarted();
+ }
+ }
+
+ /**
+ * Fire a projectFinished event.
+ */
+ public void projectFinished()
+ {
+ for( int i = 0; i < m_listeners.length; i++ )
+ {
+ m_listeners[ i ].projectFinished();
+ }
+ }
+
+ /**
+ * Fire a targetStarted event.
+ *
+ * @param targetName the name of target
+ */
+ public void targetStarted( String targetName )
+ {
+ for( int i = 0; i < m_listeners.length; i++ )
+ {
+ m_listeners[ i ].targetStarted( targetName );
+ }
+ }
+
+ /**
+ * Fire a targetFinished event.
+ */
+ public void targetFinished()
+ {
+ for( int i = 0; i < m_listeners.length; i++ )
+ {
+ m_listeners[ i ].targetFinished();
+ }
+ }
+
+ /**
+ * Fire a targetStarted event.
+ *
+ * @param targetName the name of target
+ */
+ public void taskStarted( String taskName )
+ {
+ for( int i = 0; i < m_listeners.length; i++ )
+ {
+ m_listeners[ i ].taskStarted( taskName );
+ }
+ }
+
+ /**
+ * Fire a taskFinished event.
+ */
+ public void taskFinished()
+ {
+ for( int i = 0; i < m_listeners.length; i++ )
+ {
+ m_listeners[ i ].taskFinished();
+ }
+ }
+
+ /**
+ * Fire a log event.
+ *
+ * @param message the log message
+ */
+ public void log( String message )
+ {
+ for( int i = 0; i < m_listeners.length; i++ )
+ {
+ m_listeners[ i ].log( message );
+ }
+ }
+
+ /**
+ * Fire a log event.
+ *
+ * @param message the log message
+ * @param throwable the throwable to be logged
+ */
+ public void log( String message, Throwable throwable )
+ {
+ for( int i = 0; i < m_listeners.length; i++ )
+ {
+ m_listeners[ i ].log( message, throwable );
+ }
+ }
+}
diff --git a/proposal/myrmidon/src/java/org/apache/myrmidon/components/workspace/Workspace.java b/proposal/myrmidon/src/java/org/apache/myrmidon/components/workspace/Workspace.java
new file mode 100644
index 000000000..57ae60470
--- /dev/null
+++ b/proposal/myrmidon/src/java/org/apache/myrmidon/components/workspace/Workspace.java
@@ -0,0 +1,50 @@
+/*
+ * 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 file.
+ */
+package org.apache.myrmidon.components.workspace;
+
+import java.util.Map;
+import org.apache.avalon.framework.component.Component;
+import org.apache.myrmidon.api.TaskException;
+import org.apache.myrmidon.components.model.Project;
+import org.apache.myrmidon.listeners.ProjectListener;
+
+/**
+ * This is the abstraction through which Projects are managed and executed.
+ *
+ * @author Peter Donald
+ */
+public interface Workspace
+ extends Component
+{
+ String ROLE = "org.apache.myrmidon.components.workspace.Workspace";
+
+ /**
+ * Add a listener to project events.
+ *
+ * @param listener the listener
+ */
+ void addProjectListener( ProjectListener listener );
+
+ /**
+ * Remove a listener from project events.
+ *
+ * @param listener the listener
+ */
+ void removeProjectListener( ProjectListener listener );
+
+ /**
+ * Execute a target in a particular project.
+ *
+ * @param project the Project
+ * @param target the name of the target
+ * @param defines the defines
+ * @exception TaskException if an error occurs
+ */
+ void executeProject( Project project, String target )
+ throws TaskException;
+}