openArchitectureWare.org

oAW has moved to Eclipse.

At this site you will find information about the outdated version openArchitectureWare 4, only.
Please read our letter of intent for further information.

 
   

HowTo: Deployment of cartridges as Eclipse plug-in

Suppose you want to provide your generator as an Eclipse plugin. You have a project in your workspace that contains your model(s) and when changing the model call the oAW generator from within the plugin, e.g. by an Action that is explicitly invoked by the user or by an Incremental Project Builder. This approach has some technical aspects you will have to keep in mind. Read this article to learn how you could integrate a workflow invocation from a plugin.

Plugin dependency problem

In Eclipse/OSGi every plugin has its own classloader. When you have a generator plugin you typically build a plugin-dependency to the org.openarchitectureware.core.workflow plugin. This is required because the generator plugin needs access to the exported classes of the workflow plugin.

When invoking a workflow the dependency is vice versa: The Workflow plugin needs access to resources of your specific plugin. But how should it know your generator plugin? oAW does not know about any plugins that it will use. And if so there would be a circular dependency.

Thanks to Achim Demelt on explaining the issue in Thread#2884.

Buddy classloaders

To solve this issue it is possible to configure Buddy loading for plugins that need access to resources outside the plugin that are not known at compile time. In this case the workflow plugin needs access to resources in the generator plugin.

In oAW 4.1.2 (and the current CVS head of course) the workflow plugin declares Eclipse-BuddyPolicy: dependent in its Manifest. By doing this the workflow plugin can load resources of plugins that declare themselves a plugin dependency to it.

Alternatives to Buddy Loading

Tobias Buhr suggested a slight different approach:

In the Activator of the generator-plugin (that one who contains all before mentioned resources), one has to declare the resource loader:


public void start(BundleContext context) throws Exception {
	super.start(context);
	plugin = this;

	/*
	 * see http://www.openarchitectureware.org/forum/viewtopic.php?forum=2&showtopic=2448&highlight=workflowrunner
		 * set oaw's classloader to the current class-loader
		 */
	ResourceLoaderFactory.setCurrentThreadResourceLoader(new ResourceLoaderImpl(getClass().getClassLoader()));
}
Then, one can invoke the WorkflowRunner.run from another plugin and it goes fine :-)

Accessing resources from projects in the workspace

We have seen how the workflow plugin can access the generator resources. But you typically want to place resources like your models not in the generator plugin. More likely you will have a project in your workspace that contains them. When invoking the generator it will have to load your model from the project. But the generator plugin does not know which project it should generate something for nor how the classpath of this project is constructed.

Using a custom ResourceLoader

Responsible for loading any resources in oAW is the ResourceLoader (core.workflow plugin). Its default implementation, org.openarchitectureware.workflow.util.ResourceLoaderDefaultImpl is not capable of loading resources from within a plugin to resources defined in a workspace project. But it is possible to provide a custom implementation of the ResourceLoader interface and register it to be used for the generator execution thread.

Implementation of the ResourceLoader

The ResourceLoader listet below has dependencies to the following plugins:
  • org.eclipse.core.resources
  • org.eclipse.core.runtime
  • org.eclipse.jdt.core
  • org.openarchitectureware.core.workflow


import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.openarchitectureware.workflow.util.ResourceLoaderDefaultImpl;

/**
 * This ResourceLoader is capable of loading resources from a specific Java project within the workspace.
 * Typical use is when invoking a workflow from within a plugin.
 *  
 * @author Axel Terfloth (axel.terfloth@itemis.de)
 * @author Karsten Thoms (karsten.thoms@itemis.de)
 *
 */
public class OawEclipseProjectResourceLoader extends ResourceLoaderDefaultImpl {
    private ClassLoader projectCL;
    

	public OawEclipseProjectResourceLoader(IProject project) throws CoreException {
		super();
		projectCL = createClassLoader(project);
	}

	/**
	 * Builds a classloader for a Java project from the workspace. 
	 * @param project An Eclipse project
	 * @throws CoreException
	 */
	public ClassLoader createClassLoader (IProject project) throws CoreException {
		IJavaProject jp = JavaCore.create(project);
		
		IClasspathEntry[] javacp = jp.getResolvedClasspath(true);		
		URL[] url = new URL[javacp.length];

		for (int i=0; i<javacp.length; i++)
			try {
				url[i] = javacp[i].getPath().toFile().toURL();
			} catch (MalformedURLException e) {
				e.printStackTrace();
			}
		return new URLClassLoader(url);
	}

	@Override
	protected URL internalGetResource(String path) {
		URL resource = projectCL.getResource(path);
		if ( resource == null ) {
			resource = super.internalGetResource(path);
		}
		return resource;
	}

	@Override
	protected InputStream internalGetResourceAsStream(String path) {
		URL url = internalGetResource(path);
		try {
			return url != null ? url.openStream() : null;
		} catch (IOException e) {
			return null;
		}
	}

}

Classpath Extension

The current ResourceLoader lets you load a workflow-file from a project but usually the workflow file itself references other files/classes that are not in the classpath of the calling plugin. To enable this type of resource and class loading we have to add the dependent jars and bin folders defined in the configuration of the project to the classpath of the URLClassloader and make it a child of the current Classloader.

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.openarchitectureware.workflow.util.ResourceLoaderDefaultImpl;

/**
 * This ResourceLoader is capable of loading resources from a specific Java project within the workspace.
 * Typical use is when invoking a workflow from within a plugin.
 *  
 * @author Axel Terfloth (axel.terfloth@itemis.de)
 * @author Karsten Thoms (karsten.thoms@itemis.de)
 *
 */
public class OawEclipseProjectResourceLoader extends ResourceLoaderDefaultImpl {
    private ClassLoader projectCL;
    

	public OawEclipseProjectResourceLoader(IProject project) throws CoreException {
		super();
		projectCL = createClassLoader(project);
	}

	/**
	 * Builds a classloader for a Java project from the workspace. 
	 * @param project An Eclipse project
	 * @throws CoreException
	 */
	public ClassLoader createClassLoader (IProject project) throws CoreException {
		IJavaProject jp = JavaCore.create(project);
		
		IClasspathEntry[] javacp = jp.getResolvedClasspath(true);		
		URL[] url = new URL[javacp.length];

		for (int i=0; i<javacp.length; i++)
			try {
				url[i] = javacp[i].getPath().toFile().toURL();
			} catch (MalformedURLException e) {
				e.printStackTrace();
			}
		return new URLClassLoader(url);
	}

	@Override
	protected URL internalGetResource(String path) {
		URL resource = projectCL.getResource(path);
		if ( resource == null ) {
			resource = super.internalGetResource(path);
		}
		return resource;
	}

	@Override
	protected InputStream internalGetResourceAsStream(String path) {
		URL url = internalGetResource(path);
		try {
			return url != null ? url.openStream() : null;
		} catch (IOException e) {
			return null;
		}
	}

}

Running the generator

Now that you have the special ResourceLoader implementation you can invoke a workflow from within a plugin. You will need
  • Write a component in the generator plugin that invokes the generator, e.g. an Action or Builder.
  • Access to the (workspace) project on which the generator will be invoked. E.g. from within an IncrementalProjectBuilder you can access it by calling getProject().
  • The path to the workflow file that should be invoked. The workflow is placed within the generator plugin.

The following listing is the method that invokes your workflow.


	public void runGenerator(IProject project, String workflowFile) throws CoreException {
		Map<String,String> properties = new HashMap<String,String>();
		Map<String,?> slotMap = new HashMap<String,Object>();

		// configure properties passed to the workflow engine
		properties.put("basedir", project.getLocation().toFile().getAbsolutePath());
		
		try {
			OawEclipseProjectResourceLoader resourceLoader = new OawEclipseProjectResourceLoader(project);
			ResourceLoaderFactory.setCurrentThreadResourceLoader(resourceLoader);

			WorkflowRunner runner = new WorkflowRunner();
			runner.run(workflowFile, new NullProgressMonitor(), properties, slotMap);
		} finally {
			ResourceLoaderFactory.setCurrentThreadResourceLoader(null);
		}
	}