This document outlines the work that is underway to extend the Eclipse Web Tools Platform so that it can offer support for JBI components. In order to understand a little about the process we'll walk through the steps involved in adding a new module type, creating a server definition and then ultimately adding the new project type and deployables.

It is a work in progress, but hopefully by seeing the process of adding the extension we help feed innovation around WTP and JBI.

OK.. Lets start at the begining, we will basically create three plug-in projects, these will be for the server definition, the UI components of JBI and the JBI core components (which will cover creating deployables).

We will start in the UI project, and do our first big step: we will declare a new type of module. In our case this will be a jst.jbi.component (though obviously we could create a new module type for other things ie a BPEL module).

<extension point="org.eclipse.wst.server.core.moduleTypes">
    <moduleType id="jst.jbi.component" name="%jbi.component.module.type.name" />
</extension>

A fine first move, and certainly one that we can be proud of. Now we have our new module type we will want to build up all that wonderful functionality around it.

Now we have a module type we need to move on to the defining the ServiceMix server, we aren't going to go into all the details here since it is more of a general overview of the functionality and steps required. Server Definitions are basically just plug-in themselves, though they need to implement some extension points to allow themselves to be added. WTP supports the concept of a generic server, this is one that is able to be started and controlled through a simple definition XML file and one that can handle publishing a project using an ANT task. You can override this functionality and implement real Java classes to do it all though for our purposes we should be able to function using the Generic (though we do need to override the behaviour since generic servers look for an http connection to be in place to determine that the server has started).

To start lets look at our plugin.xml :

<extension point="org.eclipse.wst.server.core.runtimeTypes">
	   <runtimeType
			id="org.eclipse.jst.server.generic.runtime.servicemix30"
			name="%servicemix30.runtime.name"
			description="%servicemix30.runtime.description" vendor="%servicemix30.vendor"
			version="3.0"
			class="org.eclipse.jst.server.generic.core.internal.GenericServerRuntime">
			<moduleType types="jst.jbi.component" versions="1.0" />
		</runtimeType>
	</extension>

First up we declare a runtime, in WTP there is a separation between a runtime which contains the libraries and functionality that wish to associate to a project and a server which is a running instance of the runtime. In this extension point we basically declare that we have a new runtime which is based on generic called servicemix30, we also associate our modulue type with this runtime.

<extension point="org.eclipse.wst.server.core.serverTypes">
		<serverType
			class="org.eclipse.jst.server.generic.core.internal.GenericServer"
			id="org.eclipse.jst.server.generic.servicemix30"
			initialState="stopped" supportsRemoteHosts="false"
			runtimeTypeId="org.eclipse.jst.server.generic.runtime.servicemix30"
			description="%servicemix30.server.description"
			launchConfigId="org.eclipse.jst.server.generic.core.launchConfigurationType"
			behaviourClass="org.eclipse.jst.server.servicemix.ServiceMixServerBehaviour"
			name="%servicemix30.server.name" startTimeout="75000" stopTimeout="15000"
			hasConfiguration="false" launchModes="run,debug,profile" runtime="true"
			startBeforePublish="false" />
	</extension>

Next up we need to declare our server type, it sits on top of our runtime and determine some basic information as to how the server is started and stopped as well as whether it needs to be running to publish to. Publish is where a project can be deployed (published) to a server, note that publishing is always to a server not a runtime.

Next up since we are using the GenericServer class in our server type (see above) we need to declare where to get the definition for the server:

<extension
		point="org.eclipse.jst.server.generic.core.serverdefinition">
		<serverdefinition
			definitionfile="/servers/servicemix30.serverdef.xml"
			id="org.eclipse.jst.server.generic.runtime.servicemix30" />
	</extension>

The server definition is an interesting file (there is a good write-up on server definitions here), obviously our server definition will include the jst.jbi.component deploy and undeploy in the place of the EJB/WAR or EAR stuff that normally is used:

<?xml version="1.0" encoding="UTF-8"?>
<tns:ServerRuntime
	xmlns:tns="http://eclipse.org/jst/server/generic/ServerTypeDefinition"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://eclipse.org/jst/server/generic/ServerTypeDefinition"
	name="Apache ServiceMix 3.0" version="3.0">

	<property id="serverRootDirectory"
		label="%servicemix30.installation.path" type="directory"
		context="runtime" default="C:/servicemix-3.0" />

	<module>
		<type>jst.jbi.component</type>
		<publishDir>${serverRootDirectory}/install</publishDir>
		<publisherReference>org.eclipse.jst.server.generic.antpublisher</publisherReference>
	</module>

	<publisher id="org.eclipse.jst.server.generic.antpublisher">
		<publisherdata>
			<dataname>build.file</dataname>
			<datavalue>/buildfiles/servicemix30.xml</datavalue>
		</publisherdata>
		<publisherdata>
			<dataname>target.publish.jst.jbi.component</dataname>
			<datavalue>deploy.jbi.component</datavalue>
		</publisherdata>
		<publisherdata>
			<dataname>target.unpublish.jst.jbi.component</dataname>
			<datavalue>undeploy.jbi.component</datavalue>
		</publisherdata>
	</publisher>

	<project>
		<classpathReference>servicemix.project</classpathReference>
	</project>

	<start>
		<mainClass>org.codehaus.classworlds.Launcher</mainClass>
		<workingDirectory>${serverRootDirectory}</workingDirectory>
		<programArguments></programArguments>
		<vmParameters>
			-Xms128m
			-Dclassworlds.conf=${serverRootDirectory}\conf\servicemix.conf
			-Xmx512m
			-Djava.endorsed.dirs=${serverRootDirectory}\lib\endorsed
			-Dservicemix.home=${serverRootDirectory}
		</vmParameters>
		<classpathReference>servicemix</classpathReference>
	</start>

	<stop>
		<mainClass>org.probably.should.do.this</mainClass>
		<workingDirectory>${serverRootDirectory}/bin</workingDirectory>
		<vmParameters></vmParameters>
		<classpathReference>servicemix</classpathReference>
	</stop>

	<publisher id="org.eclipse.jst.server.generic.antpublisher">
		<publisherdata>
			<dataname>build.file</dataname>
			<datavalue>/buildfiles/servicemix30.xml</datavalue>
		</publisherdata>
		<publisherdata>
			<dataname>target.publish.jbi</dataname>
			<datavalue>deploy.jbi.component</datavalue>
		</publisherdata>
		<publisherdata>
			<dataname>target.unpublish.jbi</dataname>
			<datavalue>undeploy.jbi.component</datavalue>
		</publisherdata>
	</publisher>

	<classpath id="servicemix">
		<archive path="${serverRootDirectory}/conf" />
		<archive
			path="${serverRootDirectory}/lib/classworlds-1.0.1.jar" />
	</classpath>

	<classpath id="servicemix.project">
		<archive path="${serverRootDirectory}/servicemix-3.0-M1.jar" />
	</classpath>
</tns:ServerRuntime>

Note that we use classworlds to get the server started, and yes we really ought to get the shutdown finished

Also note that we have used the ANT publisher in the definition of our servers, this means that the files buildfiles/servicemix30.xml will be used as the ANT project and when you choose to publish a JBI component or unpublish one the targets deploy.jbi.component and undeploy.jbi.component are used respectively. Note that in the server definition we also define classpath's for the server to use to start/stop and also for a project that will use this server definition to act as its runtime. This means we can get the classpath from the server when we are creating a project against it as a runtime, to make the project classpath available we need to add this extension point:

<extension point="org.eclipse.jst.server.core.runtimeClasspathProviders">
    	<runtimeClasspathProvider
	        id="org.eclipse.jst.server.generic.runtime.servicemix30"
	        runtimeTypeIds="org.eclipse.jst.server.generic.runtime.servicemix30"
	        class="org.eclipse.jst.server.generic.core.internal.GenericServerRuntimeTargetHandler"/>
    </extension>

So now thanks to the magic of WTP we have defined a module and a generic server to deal with it. Since we are proud of our creation so far we can use an extension point to give our runtime and server a nice icon.

<extension point="org.eclipse.wst.server.ui.serverImages">
		<image icon="/icons/servicemix.gif"
			id="org.apache.servicemix.server.image"
			typeIds="org.eclipse.jst.server.generic.servicemix30" />
		<image icon="/icons/servicemix.gif"
			id="org.apache.servicemix.server.image"
			typeIds="org.eclipse.jst.server.generic.runtime.servicemix30" />
	</extension>

OK.. so far we have added a new type of module (in this case JBI) and defined a server that is capable of handling it.

We could start up Eclipse with our plugins in place and we should start seeing some results! Note that the runtime settings are being requested by the server definition (ie. the location of our ServiceMix installation).

However, what we now what is to be able to create a project which can interact with both our server and runtime definitions. This means be able to reference the runtime when creating the project and also leverage the WTP publish/deploy architecture so that we can publish our project to a runtime.  In order to do this we need to create a new type of Facet, facet's are basically a way to introduce functionality to projects in Eclipse, therefore a project can have one or more facets (ie. it could be a Java project and a EJB project).

Lets look at the project facet definition from our core JBI plugin.

<extension
		point="org.eclipse.wst.common.project.facet.core.facets">
		<project-facet id="jst.jbi.component">
			<label>%jbi.component.facet.label</label>
			<description>
				%jbi.component.facet.description
			</description>
			<icon>icons/jbi-component.gif</icon>
		</project-facet>
		<project-facet-version facet="jst.jbi.component"
			version="1.0">
			<constraint>
	        <and>
	          <requires facet="jst.java" version="[1.4"/>
	        </and>
	        <conflicts group="modules"/>
	      </constraint>
	      <group-member id="modules"/>
		</project-facet-version>
		<action facet="jst.jbi.component" version="1.0" type="install">
			<delegate
					class="org.eclipse.jst.jbi.ui.project.facet.JbiFacetInstallDelegate" />
			<config-factory
					class="org.eclipse.jst.jbi.internal.project.operations.JbiFacetInstallDataModelProvider" />
		</action>
		<template id="template.jst.jbi.component">
			<label>%jbi.component.facet.label</label>
			<fixed facet="jst.java"/>
	      	<fixed facet="jst.jbi.component"/>
		</template>
	</extension>

As we can see we basically introduce a facet called jst.jbi.component. In the definition we can give a little information about the type of requirements this facet would have, in this case we need a version of the Java facet equal to or greater than 1.4 (since JBI has that requirement). Also important is the concept of building delegates and config factories in order to allow the facet to have information gathered that it needs in a model (represented in the config-factory) and a delegate that is responsible for installing any requirements into the project for this facet.

Also note that we provide a template, this can be used in the wizard to show the default facets, in our case we wanted only that the java facet be there.

Before diving into the Java code for these classes there are two more extension point that we want to add first:

<extension point="org.eclipse.ui.newWizards">
		<category name="%jbi.category"
			id="org.eclipse.jst.jbi.ui">
		</category>
		<wizard name="%jbi.component.project.name" icon="/icons/jbi-component.gif"
			category="org.eclipse.jst.jbi.ui"
			class="org.eclipse.jst.jbi.ui.project.facet.JbiProjectWizard"
			project="true"
			finalPerspective="org.eclipse.jdt.internal.ui.JavaHierarchyPerspectiveFactory"
			id="org.eclipse.jst.jbi.ui.project.facet.JbiProjectWizard">
			<description>%jbi.component.project.description</description>
		</wizard>
	</extension>

	<extension point="org.eclipse.wst.common.project.facet.ui.wizard">
		<wizard-pages facet="jst.jbi.component" version="1.0">
			<install>
				<page
					class="org.eclipse.jst.jbi.ui.project.facet.JbiFacetInstallPage" />
			</install>
		</wizard-pages>
	</extension>

These are used to first define our new JBI project wizard and then to define the install page for our JBI facet, since we are leveraging the WST/JST we will be able to re-use much of their infrastructure to get us a little closer.

OK... lets get started with the new project wizard, we can re-use much of the JEE stuff that those busy people on WTP have done (thanks!!). For our new project wizard we can extend NewProjectDataModelFacetWizard.

public class JbiProjectWizard extends NewProjectDataModelFacetWizard {

	public JbiProjectWizard(IDataModel model) {
		super(model);
		setWindowTitle(JBIUIMessages.KEY_1);
	}

	public JbiProjectWizard() {
		super();
		setWindowTitle(JBIUIMessages.KEY_1);
	}

	protected IDataModel createDataModel() {
		return DataModelFactory
				.createDataModel(new JbiFacetProjectCreationDataModelProvider());
	}

	protected IFacetedProjectTemplate getTemplate() {
		return ProjectFacetsManager.getTemplate("template.jst.jbi.component"); //$NON-NLS-1$
	}

	protected IWizardPage createFirstPage() {
		return new JbiProjectFirstPage(model, "first.page"); //$NON-NLS-1$
	}

	protected ImageDescriptor getDefaultPageImageDescriptor() {
		final Bundle bundle = Platform.getBundle(Activator.PLUGIN_ID); //$NON-NLS-1$
		final URL url = bundle.getEntry("icons/jbi-component.gif"); //$NON-NLS-1$
		return ImageDescriptor.createFromURL(url);
	}

}

In the example new project wixard we see the use of an IDataModel, basically this is a holder for the parameters that we want to gather during the set-up of the project. Our implementation re-uses much of the WTP facet stuff by extending FacetProjectCreationDataModelProvider.

public class JbiFacetProjectCreationDataModelProvider extends
		FacetProjectCreationDataModelProvider {

	public void init() {
		super.init();
		FacetDataModelMap map = (FacetDataModelMap) getProperty(FACET_DM_MAP);
		IDataModel javaFacet = DataModelFactory
				.createDataModel(new JavaFacetInstallDataModelProvider());
		map.add(javaFacet);
		IDataModel jbiFacet = DataModelFactory
				.createDataModel(new JbiFacetInstallDataModelProvider());
		map.add(jbiFacet);
		javaFacet
				.setProperty(
						IJavaFacetInstallDataModelProperties.SOURCE_FOLDER_NAME,
						jbiFacet
								.getStringProperty(IJbiFacetInstallDataModelProperties.JAVA_SOURCE_FOLDER));
	}
}

Note that we use the provide to get the facet data model map which we can use to all add the new Java data model provider and our JbiFacet install data model provider. These act to provide information about the parameters that you need to gather. A quick look at the JbiFacetInstallDataModelProvider gives you the idea.

public class JbiFacetInstallDataModelProvider extends
		J2EEModuleFacetInstallDataModelProvider {

	private static final String JBI_V1_0 = "1.0";

	private static final String JBI_PROJECT_FACET = "jst.jbi.component";

	public static final IProjectFacetVersion JBI_10 = ProjectFacetsManager
			.getProjectFacet(JBI_PROJECT_FACET).getVersion(JBI_V1_0); //$NON-NLS-1$

	public Set getPropertyNames() {
		Set names = super.getPropertyNames();
		names.add(CONFIG_FOLDER);
		names.add(IJbiFacetInstallDataModelProperties.JAVA_SOURCE_FOLDER);
		return names;
	}

	public Object getDefaultProperty(String propertyName) {
		if (propertyName.equals(FACET_ID)) {
			return JBI_PROJECT_FACET;
		} else if (propertyName.equals(CONFIG_FOLDER)) {
			return "src/main/resources";
		} else if (propertyName
				.equals(IJbiFacetInstallDataModelProperties.JAVA_SOURCE_FOLDER)) {
			return "src/main/java";
		}
		return super.getDefaultProperty(propertyName);
	}

	protected int convertFacetVersionToJ2EEVersion(IProjectFacetVersion version) {
		return J2EEVersionConstants.J2EE_1_4_ID;
	}

	public boolean isPropertyEnabled(String propertyName) {
		return super.isPropertyEnabled(propertyName);
	}

	public boolean propertySet(String propertyName, Object propertyValue) {
		boolean status = false;
		status = super.propertySet(propertyName, propertyValue);
		return status;
	}

	public IStatus validate(String propertyName) {
		return OK_STATUS;
	}

}

The wizard code is simply to build a module, however since the facet we are adding has a runtime available you suddenly get the runtimes available in the wizard. Also remember the install delegate we had at the begining, well we have use that to determine whether a runtime was assigned and reference it if it was.

public class JbiFacetInstallDelegate extends J2EEFacetInstallDelegate implements
		IDelegate {

	public void execute(IProject project, IProjectFacetVersion fv,
			Object config, IProgressMonitor monitor) throws CoreException {
		if (monitor != null) {
			monitor.beginTask("Installing JBI facet", 1);
		}

		try {
			IDataModel model = (IDataModel) config;

			final IJavaProject jproj = JavaCore.create(project);

			// Add WTP natures.
			WtpUtils.addNatures(project);

			// Setup the flexible project structure.
			final IVirtualComponent c = ComponentCore.createComponent(project);
			c.create(0, null);
			c.setMetaProperty("java-output-path", "/build/classes/");

			final IVirtualFolder jbiRoot = c.getRootFolder();

			// Create directory structure
			String srcFolder = null;
			srcFolder = model
					.getStringProperty(IJbiFacetInstallDataModelProperties.JAVA_SOURCE_FOLDER);
			jbiRoot.createLink(new Path("/" + srcFolder), 0, null);
			String resourcesFolder = model
					.getStringProperty(IJbiFacetInstallDataModelProperties.CONFIG_FOLDER);
			jbiRoot.createLink(new Path("/" + resourcesFolder), 0, null);

			// Look ma a runtime!!!!
			IRuntime runtime = (IRuntime) model
					.getProperty(IJbiFacetInstallDataModelProperties.FACET_RUNTIME);
			....
			if (monitor != null) {
				monitor.worked(1);
			}
		}

		finally {
			if (monitor != null) {
				monitor.done();
			}
		}

	}
}

As you can imagine this means that information from your runtime can be used to set up special stuff in the project, such as adding the runtime's project classpath to the project. OK... lets see all that in action

So now we have a server/runtime that we have added, a type of module and a new project and facet that allows a project to be associated with that facet and thus the runtime/server. OK, the final bit we need to think about is how do we make it so that we can add a project to the server (not just associate it with the runtime and publish the project to that server). Now this is still in development, but I'll put up what we have right now so you get the flavour of it all

You need to implement three extension points, these basically tell WTP that your project can create a deployable artifact that it is based on a project and that it is associated with the jst.jbi.component module type.

<extension point="org.eclipse.wst.server.core.moduleFactories">
		<moduleFactory projects="true"
			class="org.eclipse.jst.jbi.internal.deployables.JBIComponentDeployableFactory"
			id="org.eclipse.jst.jbi.deployables.component">
			<moduleType versions="1.0" types="jst.jbi.component"/>
		</moduleFactory>
	</extension>

	<extension
		point="org.eclipse.wst.server.core.moduleArtifactAdapters">
		<moduleArtifactAdapter
			id="org.eclipse.jst.jbi.component"
			class="org.eclipse.jst.jbt.internal.deployables.JBIComponentDeployableObjectAdapter">
			<enablement>
				<with variable="selection">
					<adapt type="org.eclipse.core.resources.IProject" />
				</with>
			</enablement>
		</moduleArtifactAdapter>
	</extension>

	<extension point="org.eclipse.core.runtime.adapters">
		<factory
			class="org.eclipse.jst.jbi.internal.deployables.JBIComponentDeployableObjectAdapter"
			adaptableType="org.eclipse.core.resources.IProject">
			<adapter
				type="org.eclipse.jst.jbi.internal.deployables.IJBIComponentArtifact" />
		</factory>
	</extension>

Lets start by looking at the JBIComponentDeployableFactory, its job is to determine if a project can be deployed, and to create a module delegate that will actually perform the preparation, remember it is actually the server that does the publishing, our JBIComponentDeployable is passed to the publishing part of the server definition (in our case the generic ant task) to publish to the server.

public class JBIComponentDeployable extends ComponentDeployable {

	public JBIComponentDeployable(IProject project, IVirtualComponent component) {
		super(project);
	}

	public String getContextRoot() {
		Properties props = component.getMetaProperties();
		if (props.containsKey("context-root")) //$NON-NLS-1$
			return props.getProperty("context-root"); //$NON-NLS-1$
		return component.getName();
	}

	public String getURI(IModule module) {
		IVirtualComponent comp = ComponentCore.createComponent(module
				.getProject());
		String aURI = null;
		if (comp != null) {
			if (!comp.isBinary()
					&& isProjectOfType(module.getProject(), "jst.jbi.component")) {
				IVirtualReference ref = component.getReference(comp.getName());
				aURI = ref.getRuntimePath().append(
						comp.getName() + "-installer.jar").toString(); //$NON-NLS-1$
			}
		}

		if (aURI != null && aURI.length() > 1 && aURI.startsWith("/")) //$NON-NLS-1$
			aURI = aURI.substring(1);
		return aURI;
	}

	public String getVersion() {
		IFacetedProject facetedProject = null;
		try {
			facetedProject = ProjectFacetsManager
					.create(component.getProject());
			if (facetedProject != null
					&& ProjectFacetsManager.isProjectFacetDefined("jst.jbi.component")) {
				IProjectFacet projectFacet = ProjectFacetsManager
						.getProjectFacet("jst.jbi.component");
				return facetedProject.getInstalledVersion(projectFacet)
						.getVersionString();
			}
		} catch (Exception e) {
			// Ignore
		}
		return "1.0"; //$NON-NLS-1$
	}
}

Note that this document will continue to discuss how the IProject is converted through the adaptable pattern into a JbiComponent in the next few days. In the meantime I'll leave you with a little demonstration of how a deployable projects looks.