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; +}