CQ-4081: Initial import
diff --git a/bundles/org.eclipse.e4.core.deeplink/.classpath b/bundles/org.eclipse.e4.core.deeplink/.classpath
new file mode 100644
index 0000000..8a8f166
--- /dev/null
+++ b/bundles/org.eclipse.e4.core.deeplink/.classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>

+<classpath>

+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>

+	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>

+	<classpathentry kind="src" path="src"/>

+	<classpathentry kind="output" path="bin"/>

+</classpath>

diff --git a/bundles/org.eclipse.e4.core.deeplink/.project b/bundles/org.eclipse.e4.core.deeplink/.project
new file mode 100644
index 0000000..68b82f5
--- /dev/null
+++ b/bundles/org.eclipse.e4.core.deeplink/.project
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>

+<projectDescription>

+	<name>org.eclipse.e4.core.deeplink</name>

+	<comment></comment>

+	<projects>

+	</projects>

+	<buildSpec>

+		<buildCommand>

+			<name>org.eclipse.jdt.core.javabuilder</name>

+			<arguments>

+			</arguments>

+		</buildCommand>

+		<buildCommand>

+			<name>org.eclipse.pde.ManifestBuilder</name>

+			<arguments>

+			</arguments>

+		</buildCommand>

+		<buildCommand>

+			<name>org.eclipse.pde.SchemaBuilder</name>

+			<arguments>

+			</arguments>

+		</buildCommand>

+	</buildSpec>

+	<natures>

+		<nature>org.eclipse.pde.PluginNature</nature>

+		<nature>org.eclipse.jdt.core.javanature</nature>

+	</natures>

+</projectDescription>

diff --git a/bundles/org.eclipse.e4.core.deeplink/.settings/org.eclipse.jdt.core.prefs b/bundles/org.eclipse.e4.core.deeplink/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..86c81bb
--- /dev/null
+++ b/bundles/org.eclipse.e4.core.deeplink/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,7 @@
+#Wed Jan 13 23:46:14 CST 2010

+eclipse.preferences.version=1

+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5

+org.eclipse.jdt.core.compiler.compliance=1.5

+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error

+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error

+org.eclipse.jdt.core.compiler.source=1.5

diff --git a/bundles/org.eclipse.e4.core.deeplink/META-INF/MANIFEST.MF b/bundles/org.eclipse.e4.core.deeplink/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..6704d01
--- /dev/null
+++ b/bundles/org.eclipse.e4.core.deeplink/META-INF/MANIFEST.MF
@@ -0,0 +1,13 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: Client Plug-in
+Bundle-SymbolicName: org.eclipse.e4.core.deeplink
+Bundle-Version: 4.6.0.qualifier
+Bundle-Activator: org.eclipse.e4.core.deeplink.Activator
+Bundle-Vendor: Eclipse
+Require-Bundle: org.eclipse.core.runtime,
+ org.eclipse.e4.core.functionalprog;bundle-version="1.0.0",
+ org.apache.commons.lang;bundle-version="2.3.0"
+Bundle-RequiredExecutionEnvironment: JavaSE-1.6
+Bundle-ActivationPolicy: lazy
+Export-Package: org.eclipse.e4.core.deeplink
diff --git a/bundles/org.eclipse.e4.core.deeplink/about.html b/bundles/org.eclipse.e4.core.deeplink/about.html
new file mode 100644
index 0000000..f77f378
--- /dev/null
+++ b/bundles/org.eclipse.e4.core.deeplink/about.html
@@ -0,0 +1,22 @@
+<h1>About This Content</h1>
+
+23 June, 2010
+
+<h2>License</h2>
+
+<p>The Eclipse Foundation makes available all content in this plug-in
+("Content"). Unless otherwise indicated below, the Content is provided
+to you under the terms and conditions of the Eclipse Public License
+Version 1.0 ("EPL"). A copy of the EPL is available at
+http://www.eclipse.org/legal/epl-v10.html. For purposes of the EPL,
+"Program" will mean the Content.</p>
+
+<p>If you did not receive this Content directly from the Eclipse
+Foundation, the Content is being redistributed by another party
+("Redistributor") and different terms and conditions may apply to your
+use of any object code in the Content. Check the Redistributor’s
+license that was provided with the Content. If no such license exists,
+contact the Redistributor. Unless otherwise indicated below, the terms
+and conditions of the EPL still apply to any source code in the
+Content and such source code may be obtained at
+http://www.eclipse.org.</p>
diff --git a/bundles/org.eclipse.e4.core.deeplink/build.properties b/bundles/org.eclipse.e4.core.deeplink/build.properties
new file mode 100644
index 0000000..41eb6ad
--- /dev/null
+++ b/bundles/org.eclipse.e4.core.deeplink/build.properties
@@ -0,0 +1,4 @@
+source.. = src/

+output.. = bin/

+bin.includes = META-INF/,\

+               .

diff --git a/bundles/org.eclipse.e4.core.deeplink/src/org/eclipse/e4/core/deeplink/Activator.java b/bundles/org.eclipse.e4.core.deeplink/src/org/eclipse/e4/core/deeplink/Activator.java
new file mode 100644
index 0000000..3260ed4
--- /dev/null
+++ b/bundles/org.eclipse.e4.core.deeplink/src/org/eclipse/e4/core/deeplink/Activator.java
@@ -0,0 +1,60 @@
+/******************************************************************************
+ * Copyright (c) David Orme and others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    David Orme - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.e4.core.deeplink;

+

+import org.eclipse.core.runtime.Plugin;

+import org.osgi.framework.BundleContext;

+

+/**

+ * The activator class controls the plug-in life cycle

+ */

+public class Activator extends Plugin {

+

+	// The plug-in ID

+	public static final String PLUGIN_ID = "org.eclipse.e4.enterprise.deeplink.core";

+

+	// The shared instance

+	private static Activator plugin;

+	

+	/**

+	 * The constructor

+	 */

+	public Activator() {

+	}

+

+	/*

+	 * (non-Javadoc)

+	 * @see org.eclipse.core.runtime.Plugins#start(org.osgi.framework.BundleContext)

+	 */

+	public void start(BundleContext context) throws Exception {

+		super.start(context);

+		plugin = this;

+	}

+

+	/*

+	 * (non-Javadoc)

+	 * @see org.eclipse.core.runtime.Plugin#stop(org.osgi.framework.BundleContext)

+	 */

+	public void stop(BundleContext context) throws Exception {

+		plugin = null;

+		super.stop(context);

+	}

+

+	/**

+	 * Returns the shared instance

+	 *

+	 * @return the shared instance

+	 */

+	public static Activator getDefault() {

+		return plugin;

+	}

+

+}

diff --git a/bundles/org.eclipse.e4.core.deeplink/src/org/eclipse/e4/core/deeplink/DeepLinkBundleList.java b/bundles/org.eclipse.e4.core.deeplink/src/org/eclipse/e4/core/deeplink/DeepLinkBundleList.java
new file mode 100644
index 0000000..6f5b7bb
--- /dev/null
+++ b/bundles/org.eclipse.e4.core.deeplink/src/org/eclipse/e4/core/deeplink/DeepLinkBundleList.java
@@ -0,0 +1,84 @@
+/******************************************************************************
+ * Copyright (c) David Orme and others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    David Orme - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.e4.core.deeplink;

+

+import org.eclipse.core.runtime.Status;

+import org.osgi.framework.Bundle;

+import org.osgi.framework.BundleContext;

+import org.osgi.framework.BundleException;

+

+/**

+ * This class is responsible for starting the appropriate bundles required by 

+ * DeepLinking's HttpServlets.

+ */

+public class DeepLinkBundleList {

+

+	private static final String EQUINOX_HTTP_REGISTRY = "org.eclipse.equinox.http.registry";

+	private static final String EQUINOX_HTTP_SERVLET = "org.eclipse.equinox.http.servlet";

+	private static final String EQUINOX_HTTP_JETTY = "org.eclipse.equinox.http.jetty";

+	private static final String DEEPLINK_HANDLER = "org.eclipse.e4.core.deeplink.handler";

+	private BundleContext context;

+

+	String[] loadTheseBundles = new String[] {

+			DEEPLINK_HANDLER,

+			EQUINOX_HTTP_JETTY,

+			EQUINOX_HTTP_SERVLET,

+			EQUINOX_HTTP_REGISTRY

+	};

+	

+	public DeepLinkBundleList(BundleContext context) {

+		this.context = context;

+	}

+	

+	public void startupBundlesForHttpServlets() throws BundleException {		

+		int startedBundles = 0;

+		boolean deepLinkPresent = false;

+		

+		Bundle[] bundles = context.getBundles();

+		for (Bundle bundle : bundles) {

+			for (String bundleName : loadTheseBundles) {

+				if (bundleName.equals(bundle.getSymbolicName())) {

+					bundle.stop();

+				}

+			}

+		}

+		for (Bundle bundle : bundles) {

+			for (String bundleName : loadTheseBundles) {

+				if (bundleName.equals(bundle.getSymbolicName())) {

+					logStarting(bundle);

+					bundle.start();

+					

+					if (bundleName.contains("deeplink")) {

+						deepLinkPresent = true;

+					}

+					++startedBundles;

+				}

+			}

+		}

+		if (deepLinkPresent && startedBundles > 0 && startedBundles < loadTheseBundles.length) {

+			String bundleNames = "";

+			for (String bundleName : loadTheseBundles) {

+				bundleNames += bundleName + " ";

+			}

+			throw new BundleException("Deep Linking present: Expected to load: [" + bundleNames + "]");

+		}

+	}

+

+	private void logStarting(Bundle bundle) {

+		Activator activator = Activator.getDefault();

+		if (activator != null) {

+			activator.getLog().log(

+				new Status(Status.INFO, Activator.PLUGIN_ID,

+						"Starting: " + bundle.getSymbolicName()));

+		}

+	}

+

+}

diff --git a/bundles/org.eclipse.e4.core.deeplink/src/org/eclipse/e4/core/deeplink/DeepLinkLaunchException.java b/bundles/org.eclipse.e4.core.deeplink/src/org/eclipse/e4/core/deeplink/DeepLinkLaunchException.java
new file mode 100644
index 0000000..2272d67
--- /dev/null
+++ b/bundles/org.eclipse.e4.core.deeplink/src/org/eclipse/e4/core/deeplink/DeepLinkLaunchException.java
@@ -0,0 +1,33 @@
+/******************************************************************************
+ * Copyright (c) David Orme and others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    David Orme - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.e4.core.deeplink;

+

+public class DeepLinkLaunchException extends RuntimeException {

+

+	private static final long serialVersionUID = 1L;

+

+	public DeepLinkLaunchException() {

+		super();

+	}

+

+	public DeepLinkLaunchException(String message, Throwable cause) {

+		super(message, cause);

+	}

+

+	public DeepLinkLaunchException(String message) {

+		super(message);

+	}

+

+	public DeepLinkLaunchException(Throwable cause) {

+		super(cause);

+	}

+

+}

diff --git a/bundles/org.eclipse.e4.core.deeplink/src/org/eclipse/e4/core/deeplink/DeepLinkManager.java b/bundles/org.eclipse.e4.core.deeplink/src/org/eclipse/e4/core/deeplink/DeepLinkManager.java
new file mode 100644
index 0000000..b2f2796
--- /dev/null
+++ b/bundles/org.eclipse.e4.core.deeplink/src/org/eclipse/e4/core/deeplink/DeepLinkManager.java
@@ -0,0 +1,205 @@
+/******************************************************************************
+ * Copyright (c) David Orme and others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    David Orme - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.e4.core.deeplink;

+

+import java.io.File;

+import java.io.FileInputStream;

+import java.io.FileNotFoundException;

+import java.io.FileOutputStream;

+import java.io.IOException;

+import java.io.InputStream;

+import java.io.OutputStream;

+import java.net.MalformedURLException;

+import java.net.URL;

+

+import org.eclipse.core.runtime.ILog;

+import org.eclipse.core.runtime.IProgressMonitor;

+import org.eclipse.core.runtime.IStatus;

+import org.eclipse.core.runtime.Platform;

+import org.eclipse.core.runtime.Status;

+import org.eclipse.core.runtime.jobs.Job;

+import org.eclipse.e4.core.deeplink.internal.DeepLinkProperties;
+import org.eclipse.e4.core.deeplink.internal.DeepLinkProxy;
+import org.eclipse.e4.core.deeplink.internal.DeeplinkPortAssigner;
+import org.eclipse.e4.core.deeplink.internal.InstallationLauncher;
+import org.eclipse.e4.core.deeplink.internal.ParsedDeepLinkURL;
+import org.eclipse.equinox.app.IApplicationContext;

+import org.osgi.framework.BundleContext;

+import org.osgi.framework.BundleException;

+

+/**

+ * The DeepLinkManager is the entry-point for the DeepLinking API. This class

+ * can be used as a singleton, but you do not have to. Using it as a singleton

+ * has some minor performance benefits.

+ */

+public class DeepLinkManager {

+	

+	private static final String PROP_FILE_HEADER = "Deep Link 4.0 Configuration";

+	private static DeepLinkManager deepLinkManager = null;

+

+	// FIXME: Need to make this a configuration option!!!!

+	private static final String SERVER_ROOT_PATH = "/tmp/deeplink";
+//	private static final String SERVER_ROOT_PATH = "C:\\Program Files\\DeepLink";
+	private static final String APPLICATION_PROPS = SERVER_ROOT_PATH + "/deeplink4.properties";

+

+	private ILog logger;

+	private DeepLinkProperties properties;

+	private DeeplinkPortAssigner portAssigner;

+	private DeepLinkProxy deepLinkProxy;

+	

+	

+	public static DeepLinkManager getDefault() {

+		return deepLinkManager;

+	}

+	

+	public DeepLinkManager(boolean regeneratePortNumbersOnClash, ILog logger) {

+		deepLinkManager = this;

+		this.logger = logger;

+		properties = getDeepLinkProperties();

+		this.portAssigner = new DeeplinkPortAssigner(properties, regeneratePortNumbersOnClash);

+		InstallationLauncher installationLauncher = new InstallationLauncher(SERVER_ROOT_PATH, properties);

+		this.deepLinkProxy = new DeepLinkProxy(installationLauncher);

+	}

+	

+	// TODO: Test drive this?

+	public String getInstallationIDFromPath(URL url) {

+		String fileURL = url.toString();

+		String path = fileURL.replace("file:/", "");

+		if (path.lastIndexOf('/') == path.length()-1) {

+			path = path.substring(0, path.length()-1);

+		}

+		int endOfPrefix = path.lastIndexOf('/');

+		if (endOfPrefix > 0) {

+			path = path.substring(endOfPrefix+1);

+		}

+		return path;

+	}

+	

+	public int getPortNumberForInstallation(String installation) {

+		int result = portAssigner.getPortNumberForInstallation(installation);

+		try {

+			properties.store(propsOutputStream(), PROP_FILE_HEADER);

+		} catch (IOException e) {

+			throw new RuntimeException(e.getMessage(), e);

+		}

+		return result;

+	}

+	

+	/**

+	 * Takes a deeplink URL as a String and composes the appropriate URL object, looking

+	 * up the correct port number for the application, and returns the result.

+	 */

+	public DeepLinkResult processDeepLink(String deepLinkURL) {

+		try {

+			ParsedDeepLinkURL deepLink = new ParsedDeepLinkURL(deepLinkURL);

+			int portNumber = portAssigner.getPortNumberForInstallation(deepLink.installation);

+			URL httpLink = new URL("http", "localhost", portNumber, "/deeplink" + deepLink.restOfURL);

+			DeepLinkResult result = deepLinkProxy.execute(deepLink.installation, httpLink, deepLinkURL);

+			logInfo("INVOKED: " + deepLink.installation + deepLink.restOfURL + 

+					"\" loaded=\"" + result.loaded + "\" callbackRan=\"" + result.callbackRan + "\" exception=\"" + result.exception + "\"");

+			return result;

+		} catch (DeepLinkURLException e) {

+			logFailure("Unable to open URL: " + deepLinkURL, e);

+			throw e;

+		} catch (MalformedURLException e) {

+			logFailure("Malformed deep link URL (" + e.getMessage() + "): " + deepLinkURL, e);

+			throw new RuntimeException(e.getMessage(), e);

+		} catch (IOException e) {

+			logFailure("Unable to contact deep link handler for: " + deepLinkURL, e);

+			throw new RuntimeException(e.getMessage(), e);

+		} catch (DeepLinkResultException e) {

+			logFailure("Unable to parse result XML: " + e.getMessage(), e);

+			throw e;

+		}

+	}

+

+	private DeepLinkProperties getDeepLinkProperties() {

+		DeepLinkProperties result = new DeepLinkProperties();

+		try {

+			result.load(propsInputStream());

+		} catch (IOException e) {

+			throw new RuntimeException(e.getMessage(), e);

+		}

+		return result;

+	}

+

+	private OutputStream propsOutputStream() {

+		try {

+			return new FileOutputStream(new File(APPLICATION_PROPS));

+		} catch (FileNotFoundException e) {

+			throw new RuntimeException(e.getMessage(), e);

+		}

+	}

+

+	private InputStream propsInputStream() {

+		try {

+			return new FileInputStream(new File(APPLICATION_PROPS));

+		} catch (FileNotFoundException e) {

+			throw new RuntimeException(e.getMessage(), e);

+		}

+	}

+

+	private void logInfo(String message) {

+		IStatus status = new Status(Status.INFO, Activator.PLUGIN_ID, message);

+		logger.log(status);

+	}

+	

+	private void logFailure(String message, Throwable t) {

+		IStatus status = new Status(Status.ERROR, Activator.PLUGIN_ID, message, t);

+		logger.log(status);

+	}

+

+	/**

+	 * Start the deep linking HTTP server if it isn't already started.  If it 

+	 * is started, restart it using the port specified by the deep linking

+	 * configuration.

+	 * 

+	 * @throws BundleException if a bundle cannot be resolved or loaded.

+	 */

+	public void startServer() throws BundleException {

+		String installation = getInstallationIDFromPath(Platform.getInstallLocation().getURL());

+		int serverPort = getPortNumberForInstallation(installation);

+		

+		System.getProperties().setProperty("org.osgi.service.http.port", Integer.toString(serverPort));

+		BundleContext bundleContext = Activator.getDefault().getBundle().getBundleContext();

+		new DeepLinkBundleList(bundleContext).startupBundlesForHttpServlets();

+	}

+

+	private static final int ONE_SECOND = 1000;

+	private final String COMMANDLINE_KEY = "application.args";

+	

+	/**

+	 * Any command line arguments that begin with "deeplink://" will be interpreted

+	 * as a deeplink URL, and those deep links will be scheduled to be invoked.

+	 * <p>

+	 * A Job is used to actually run the deep link because this method is 

+	 * typically called before the Workbench has been loaded, so we wait for 

+	 * the Workbench before running deep links that might reference 

+	 * workbench resources like perspectives, commands, etc.

+	 * 

+	 * @param context The IApplicationContext containing the command line arguments to examine.

+	 */

+	public void processCommandLineArguments(IApplicationContext context) {

+		final String[] args = (String[]) context.getArguments().get(COMMANDLINE_KEY);

+		Job deepLinkJob = new Job("Deep Link Launcher") {

+			@Override

+			protected IStatus run(IProgressMonitor monitor) {

+				for (String arg : args) {

+					if (arg.startsWith("deeplink://")) {

+						deepLinkManager.processDeepLink(arg);

+					}

+				}

+				return Status.OK_STATUS;

+			}};

+		deepLinkJob.schedule(ONE_SECOND);

+	}

+

+}

diff --git a/bundles/org.eclipse.e4.core.deeplink/src/org/eclipse/e4/core/deeplink/DeepLinkResult.java b/bundles/org.eclipse.e4.core.deeplink/src/org/eclipse/e4/core/deeplink/DeepLinkResult.java
new file mode 100644
index 0000000..eaa79f0
--- /dev/null
+++ b/bundles/org.eclipse.e4.core.deeplink/src/org/eclipse/e4/core/deeplink/DeepLinkResult.java
@@ -0,0 +1,155 @@
+/******************************************************************************
+ * Copyright (c) David Orme and others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    David Orme - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.e4.core.deeplink;

+

+import static org.eclipse.e4.core.functionalprog.optionmonad.Nulls.valueOrSubstitute;
+import static org.eclipse.e4.core.functionalprog.optionmonad.Nulls.valueOrThrow;
+

+import static org.apache.commons.lang.StringEscapeUtils.unescapeXml;

+

+import java.io.InputStream;

+import java.util.HashMap;

+import java.util.Map;

+

+import javax.xml.parsers.DocumentBuilder;

+import javax.xml.parsers.DocumentBuilderFactory;

+

+import org.eclipse.e4.core.deeplink.internal.NullNode;
+import org.w3c.dom.Document;

+import org.w3c.dom.NamedNodeMap;

+import org.w3c.dom.Node;

+import org.w3c.dom.NodeList;

+

+/**

+ * A DeepLinkResult (a) parses a result XML stream (that comes from invoking a

+ * deep link) into its components and provides public fields for accessing these

+ * results.

+ */

+public class DeepLinkResult {

+	private static final String RESULT_ELEMENT_NAME = "result";

+	private static final String CALLBACK_RAN_ELEMENT_NAME = "callbackRan";

+	private static final String LOADED_ELEMENT_NAME = "loaded";

+	private static final String EXCEPTION_ELEMENT_NAME = "exception";

+	

+	private static final String OUTPUTDATA_ELEMENT_NAME = "outputData";

+	private static final String OUTPUTDATA_RESULT_KEY = "key";

+	

+	/**

+	 * Some deep links may activate some an of the RCP framework and may or

+	 * may not also map to any explicit callback method.

+	 * <p>

+	 * If the specified RCP element (such as a perspective, Command) could be 

+	 * loaded/focused/executed, etc., did this happen?  This value is True if

+	 * the deep link refers to an RCP element and that RCP element was loaded/

+	 * focused/executed, etc.  This value is False if no RCP element was loaded/

+	 * focused/executed, etc.  This can happen either because the element

+	 * could not be found or because this particular type of deep link 

+	 * does not map to any RCP element. 

+	 * <p>

+	 * If the specified deep link TYPE could not be found, this value will

+	 * be null.  For example, if someone links to 

+	 * <ul>

+	 * <li>deeplink://app/undefinedType/anyID/anyAction

+	 * </ul>

+	 * even though the syntax of the deep link is valid, this value will be 

+	 * null because undefinedType does not refer to any registered deep link 

+	 * type handler.

+	 */

+	public final Boolean loaded;

+	

+	/**

+	 * Did a callback run?  True if a callback associated with the deep link

+	 * ran; false otherwise.	 

+	 * <p>

+	 * If the specified deep link TYPE could not be found, this value will

+	 * be null.  For example, if someone links to 

+	 * <ul>

+	 * <li>deeplink://app/undefinedType/anyID/anyAction

+	 * </ul>

+	 * even though the syntax of the deep link is valid, this value will be 

+	 * null because undefinedType does not refer to any registered deep link 

+	 * type handler.

+	 */

+	public final Boolean callbackRan;

+	

+	/**

+	 * If an exception was thrown during callback execution, this value will

+	 * be the exception's error message string.  It will be null otherwise.

+	 */

+	public final String exception;

+	

+	/**

+	 * If a callback is defined, it can return a Map<String, String> as a

+	 * result.  If this done, that Map is automatically unmarshalled and put here.

+	 * If no callback exists or the callback returned no results, this Map

+	 * will simply be empty.

+	 */

+	public final Map<String, String> outputData = new HashMap<String, String>();

+

+	/**

+	 * (non-api) Construct a DeepLinkResult.  This constructor is only called

+	 * internally to the framework.

+	 * 

+	 * @param resultStream

+	 * @throws DeepLinkResultException

+	 */

+	public DeepLinkResult(InputStream resultStream) throws DeepLinkResultException {

+		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

+		

+		Document doc = null;

+		try {

+			DocumentBuilder db = dbf.newDocumentBuilder();

+			doc = db.parse(resultStream);

+		} catch (Exception e) {

+			throw new DeepLinkResultException("Failure reading deep link result XML", e);

+		}

+		

+		doc.getDocumentElement().normalize();

+		NodeList results = valueOrThrow(doc.getElementsByTagName(RESULT_ELEMENT_NAME), resultFailure("No deep link result XML element"));

+

+		if (results.getLength() != 1) {

+			throw resultFailure("Incorrect number of result values: " + results.getLength());

+		}

+		

+		Node resultNode = valueOrThrow(results.item(0), resultFailure("index out of bounds"));

+		NamedNodeMap attributes = valueOrThrow(resultNode.getAttributes(), resultFailure("No attributes on <result> tag?"));

+		String loaded = valueOrSubstitute(attributes.getNamedItem(LOADED_ELEMENT_NAME), new NullNode()).getNodeValue();

+		String callbackRan = valueOrSubstitute(attributes.getNamedItem(CALLBACK_RAN_ELEMENT_NAME), new NullNode()).getNodeValue();

+		String exception = valueOrSubstitute(attributes.getNamedItem(EXCEPTION_ELEMENT_NAME), new NullNode()).getNodeValue();

+

+		this.loaded = NullNode.UNDEFINED_VALUE.equals(loaded) ? null : Boolean.parseBoolean(loaded);

+		this.callbackRan = NullNode.UNDEFINED_VALUE.equals(callbackRan) ? null : Boolean.parseBoolean(callbackRan);

+		this.exception = NullNode.UNDEFINED_VALUE.equals(exception) ? null : exception;

+		

+		NodeList outputDataXML = doc.getElementsByTagName(OUTPUTDATA_ELEMENT_NAME);

+		Node outputNode = outputDataXML.item(0);

+		if (outputNode == null) {

+			return;

+		}

+		NodeList childNodes = outputNode.getChildNodes();

+		for (int i = 0; i < childNodes.getLength(); i++) {

+			Node outputItem = childNodes.item(i);

+			if ("#text".equals(outputItem.getNodeName())) {

+				continue;

+			}

+			attributes = valueOrThrow(outputItem.getAttributes(), resultFailure("No attributes on <outputData> tag?"));

+			String key = valueOrThrow(attributes.getNamedItem(OUTPUTDATA_RESULT_KEY), resultFailure("No key in output <element...> tag?")).getNodeValue();

+			key = unescapeXml(key);

+			String value = outputItem.getTextContent();

+			outputData.put(key, value);

+		}

+	}

+

+	private DeepLinkResultException resultFailure(String message) {

+		return new DeepLinkResultException(message);

+	}

+	

+}

diff --git a/bundles/org.eclipse.e4.core.deeplink/src/org/eclipse/e4/core/deeplink/DeepLinkResultException.java b/bundles/org.eclipse.e4.core.deeplink/src/org/eclipse/e4/core/deeplink/DeepLinkResultException.java
new file mode 100644
index 0000000..9999a6f
--- /dev/null
+++ b/bundles/org.eclipse.e4.core.deeplink/src/org/eclipse/e4/core/deeplink/DeepLinkResultException.java
@@ -0,0 +1,33 @@
+/******************************************************************************
+ * Copyright (c) David Orme and others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    David Orme - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.e4.core.deeplink;

+

+/**

+ * If the result XML stream cannot be parsed in any way, a DeepLinkResultException

+ * is thrown.

+ */

+public class DeepLinkResultException extends RuntimeException {

+

+	private static final long serialVersionUID = 1L;

+

+	public DeepLinkResultException(String message) {

+		super(message);

+	}

+

+	public DeepLinkResultException(Throwable cause) {

+		super(cause);

+	}

+

+	public DeepLinkResultException(String message, Throwable cause) {

+		super(message, cause);

+	}

+

+}

diff --git a/bundles/org.eclipse.e4.core.deeplink/src/org/eclipse/e4/core/deeplink/DeepLinkURLException.java b/bundles/org.eclipse.e4.core.deeplink/src/org/eclipse/e4/core/deeplink/DeepLinkURLException.java
new file mode 100644
index 0000000..3422ab2
--- /dev/null
+++ b/bundles/org.eclipse.e4.core.deeplink/src/org/eclipse/e4/core/deeplink/DeepLinkURLException.java
@@ -0,0 +1,32 @@
+/******************************************************************************
+ * Copyright (c) David Orme and others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    David Orme - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.e4.core.deeplink;

+

+/**

+ * If a deeplink:// URL cannot be parsed, the framework throws this exception.

+ */

+public class DeepLinkURLException extends RuntimeException {

+

+	private static final long serialVersionUID = 1L;

+

+	public DeepLinkURLException(String message) {

+		super(message);

+	}

+

+	public DeepLinkURLException(Throwable cause) {

+		super(cause);

+	}

+

+	public DeepLinkURLException(String message, Throwable cause) {

+		super(message, cause);

+	}

+

+}

diff --git a/bundles/org.eclipse.e4.core.deeplink/src/org/eclipse/e4/core/deeplink/internal/DeepLinkProperties.java b/bundles/org.eclipse.e4.core.deeplink/src/org/eclipse/e4/core/deeplink/internal/DeepLinkProperties.java
new file mode 100644
index 0000000..dd2956a
--- /dev/null
+++ b/bundles/org.eclipse.e4.core.deeplink/src/org/eclipse/e4/core/deeplink/internal/DeepLinkProperties.java
@@ -0,0 +1,100 @@
+/******************************************************************************
+ * Copyright (c) David Orme and others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    David Orme - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.e4.core.deeplink.internal;

+

+import java.util.Collection;

+import java.util.Properties;

+

+/**

+ * A Java Properties object defining default values for all of deeplinking's

+ * property values.

+ * <p>

+ * Concerns handled here include: Assigning port numbers to RCP application

+ * instances, specifying a base port number for computing new port numbers, and

+ * specifying the command used to launch a particular RCP application instance

+ * if it isn't already running.

+ */

+public class DeepLinkProperties extends Properties {

+	private static final long serialVersionUID = 1L;

+

+	static final int DEFAULT_BASE_PORT_NUMBER = 9000;

+	

+	private static final String BASE_PORT_NUMBER_KEY = "base.port.number";

+	private static final String INSTALLATION_PREFIX = "instance.";

+	private static final String INSTALLATION_PORT_SUFFIX = ".port";

+	private static final String INSTALLATION_COMMAND_SUFFIX = ".command";

+

+	private int nextPortNumber;

+

+	public DeepLinkProperties() {

+		super();

+		nextPortNumber = getBasePortNumber();

+	}

+

+	public DeepLinkProperties(Properties defaults) {

+		super(defaults);

+		nextPortNumber = getBasePortNumber();

+	}

+

+	private String installationPortKey(String myInstallation) {

+		return INSTALLATION_PREFIX + myInstallation + INSTALLATION_PORT_SUFFIX;

+	}

+

+	public int getBasePortNumber() {

+		int basePortNumber;

+		String basePortString = getProperty(BASE_PORT_NUMBER_KEY);

+		if (basePortString == null) {

+			basePortNumber = DEFAULT_BASE_PORT_NUMBER;

+		} else {

+			basePortNumber = Integer.parseInt(basePortString);

+		}

+		return basePortNumber;

+	}

+

+	public String getInstallationPort(String myInstallation) {

+		return getProperty(installationPortKey(myInstallation));

+	}

+

+	public void setInstallationPort(String myInstallation, int port) {

+		setProperty(installationPortKey(myInstallation), Integer.toString(port));

+	}

+	

+	public void removeInstallationPort(String myInstallation) {

+		remove(installationPortKey(myInstallation));

+	}

+

+	public boolean isPortNumberUnique(String myInstallation, String portNumber) {

+		Properties tempProperties = (Properties) clone();

+		tempProperties.remove(BASE_PORT_NUMBER_KEY);

+		tempProperties.remove(installationPortKey(myInstallation));

+

+		return !tempProperties.values().contains(portNumber);

+	}

+

+	@SuppressWarnings("unchecked")

+	public int calculateNextPortNumber() {

+		Properties tempProperties = (Properties) clone();

+		tempProperties.remove(BASE_PORT_NUMBER_KEY);

+

+		Collection values = tempProperties.values();

+		while (values.contains(Integer.toString(nextPortNumber))) {

+			nextPortNumber++;

+		}

+

+		return nextPortNumber;

+	}

+	

+	public String getInstallationCommand(String installation) {

+		String key = INSTALLATION_PREFIX + installation + INSTALLATION_COMMAND_SUFFIX;

+		return getProperty(key);

+	}

+

+}

diff --git a/bundles/org.eclipse.e4.core.deeplink/src/org/eclipse/e4/core/deeplink/internal/DeepLinkProxy.java b/bundles/org.eclipse.e4.core.deeplink/src/org/eclipse/e4/core/deeplink/internal/DeepLinkProxy.java
new file mode 100644
index 0000000..1df5c71
--- /dev/null
+++ b/bundles/org.eclipse.e4.core.deeplink/src/org/eclipse/e4/core/deeplink/internal/DeepLinkProxy.java
@@ -0,0 +1,97 @@
+/******************************************************************************
+ * Copyright (c) David Orme and others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    David Orme - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.e4.core.deeplink.internal;

+

+import java.io.IOException;

+import java.io.InputStream;

+import java.net.URL;

+

+import org.eclipse.e4.core.deeplink.DeepLinkResult;
+import org.eclipse.e4.core.deeplink.DeepLinkResultException;
+

+/**

+ * A simple HTTP proxy that executes deep links on a remote deep link aware

+ * application.

+ * <p>

+ * FIXME: This code runs in the UI thread. A limitation of the current

+ * implementation is that it does not detect if the deep link request is back to

+ * the same application as is initiating the request. If this type of request

+ * occurs, it is possible for the background servlet thread receiving the

+ * request to deadlock the UI thread. This is because the UI thread (running

+ * around line 68 below) will be waiting for a response from the "remote"

+ * servlet, but the "remote" servlet may be waiting to get on the UI thread in

+ * order to process the request and produce a response.

+ */

+public class DeepLinkProxy {

+	private InstallationLauncher installationLauncher;

+	

+	private static final int INITIAL_WAIT = 1500;

+	private static final int SUBSEQUENT_WAIT = 500;

+	private final long TIMEOUT_DURATION = 60 * 1000;

+

+	public DeepLinkProxy(InstallationLauncher installationLauncher) {

+		this.installationLauncher = installationLauncher;

+	}

+

+	/**

+	 * @param installation The installation that should handle this request

+	 * @param httpDeepLink The http:// form of the URL

+	 * @param originalDeepLink The deeplink:// form of the URL

+	 * 

+	 * @return DeepLinkResult the results of executing the deep link on success

+	 * 

+	 * @throws IOException If we couldn't communicate with the deep link server

+	 * @throws DeepLinkResultException If the results the deep link server couldn't be parsed

+	 */

+	public DeepLinkResult execute(String installation, URL httpDeepLink, String originalDeepLink) throws IOException, DeepLinkResultException {

+		try {

+			return proxyDeepLinkURL(httpDeepLink);

+		} catch (IOException e) {

+			// Assume that the reason for the IOException is that the 

+			// installation isn't running.

+			installationLauncher.startInstallation(installation, originalDeepLink);

+			return invokeDeepLinkWithTimeout(installation, httpDeepLink);

+		}

+	}

+

+	private DeepLinkResult invokeDeepLinkWithTimeout(String installation, URL httpDeepLink) {

+		pause(INITIAL_WAIT);

+		long timeout = System.currentTimeMillis() + TIMEOUT_DURATION;

+		while (true) {

+			try {

+				return proxyDeepLinkURL(httpDeepLink);

+			} catch (IOException e2) {

+				if (System.currentTimeMillis() > timeout) {

+					throw new DeepLinkResultException("Timeout invoking deep link: [" + installation + "]: " + httpDeepLink);

+				}

+				pause(SUBSEQUENT_WAIT);

+			}

+		}

+	}

+

+	private void pause(long duration) {

+		try {

+			Thread.sleep(duration);

+		} catch (InterruptedException e1) {

+		}

+	}

+

+	private DeepLinkResult proxyDeepLinkURL(URL httpDeepLink) throws IOException, DeepLinkResultException {

+		InputStream resultStream = httpDeepLink.openStream();

+		try {

+			return new DeepLinkResult(resultStream);

+		} finally {

+			if (resultStream != null) {

+				resultStream.close();

+			}

+		}

+	}

+}

diff --git a/bundles/org.eclipse.e4.core.deeplink/src/org/eclipse/e4/core/deeplink/internal/DeeplinkPortAssigner.java b/bundles/org.eclipse.e4.core.deeplink/src/org/eclipse/e4/core/deeplink/internal/DeeplinkPortAssigner.java
new file mode 100644
index 0000000..97a0f4b
--- /dev/null
+++ b/bundles/org.eclipse.e4.core.deeplink/src/org/eclipse/e4/core/deeplink/internal/DeeplinkPortAssigner.java
@@ -0,0 +1,59 @@
+/******************************************************************************
+ * Copyright (c) David Orme and others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    David Orme - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.e4.core.deeplink.internal;

+

+/**

+ * This class manages the assignment of socket port numbers to each application,

+ * and the persistence of that information in a properties file. You can choose

+ * to regenerate port numbers if they are already assigned, or throw a

+ * RuntimeException.

+ */

+public class DeeplinkPortAssigner {

+	private final DeepLinkProperties properties;

+	private boolean regeneratePortNumbersOnClash;

+

+	public DeeplinkPortAssigner(DeepLinkProperties props, boolean regeneratePortNumbersOnClash) {

+		this.regeneratePortNumbersOnClash = regeneratePortNumbersOnClash;

+		

+		if (props == null) {

+			throw new IllegalArgumentException(

+					"Properties object passed to DeeplinkPortAssigner was null.");

+		}

+

+		this.properties = props;

+	}

+

+	public int getPortNumberForInstallation(String myInstallation) {

+		String installationPortNumberString = properties.getInstallationPort(myInstallation);

+		if (installationPortNumberString == null) {

+			int portNumber = properties.calculateNextPortNumber();

+			properties.setInstallationPort(myInstallation, portNumber);

+			return portNumber;

+		} else {

+			if (!properties.isPortNumberUnique(myInstallation, installationPortNumberString)) {

+				return regeneratePortNumber(myInstallation);

+			} else {

+				return Integer.parseInt(installationPortNumberString);

+			}

+		}

+	}

+

+	private int regeneratePortNumber(String myInstallation) {

+		if(!regeneratePortNumbersOnClash) {

+			throw new RuntimeException("Port number already assigned to another installation.");

+		}

+		properties.removeInstallationPort(myInstallation);

+		int portNumber = properties.calculateNextPortNumber();

+		properties.setInstallationPort(myInstallation, portNumber);

+		return portNumber;

+	}

+

+}

diff --git a/bundles/org.eclipse.e4.core.deeplink/src/org/eclipse/e4/core/deeplink/internal/InstallationLauncher.java b/bundles/org.eclipse.e4.core.deeplink/src/org/eclipse/e4/core/deeplink/internal/InstallationLauncher.java
new file mode 100644
index 0000000..f6b72f6
--- /dev/null
+++ b/bundles/org.eclipse.e4.core.deeplink/src/org/eclipse/e4/core/deeplink/internal/InstallationLauncher.java
@@ -0,0 +1,124 @@
+/******************************************************************************
+ * Copyright (c) David Orme and others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    David Orme - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.e4.core.deeplink.internal;

+

+import static org.eclipse.e4.core.functionalprog.optionmonad.None.none;
+import static org.eclipse.e4.core.functionalprog.optionmonad.Some.some;
+

+import java.io.BufferedReader;

+import java.io.File;

+import java.io.IOException;

+import java.io.InputStream;

+import java.io.InputStreamReader;

+

+import org.eclipse.e4.core.functionalprog.optionmonad.Option;
+

+/**

+ * Launch a particular deeplink aware application so that it can handle a

+ * pending deeplink request.

+ */

+public class InstallationLauncher {

+

+	private String rootFolder;

+	private DeepLinkProperties properties;

+

+	/**

+	 * Construct an InstallationLauncher.

+	 * 

+	 * @param rootFolder The root folder/directory to search for installations

+	 * @param properties The current system deep link configuration 

+	 */

+	public InstallationLauncher(String rootFolder, DeepLinkProperties properties) {

+		this.rootFolder = rootFolder;

+		this.properties = properties;

+	}

+	

+	/**

+	 * Start the named deeplink aware application, passing the specified

+	 * deeplink URL on the command line.

+	 * 

+	 * @param installation

+	 *            The installation name to launch

+	 * @param deepLinkURL

+	 *            The deeplink:// URL to put on the command line (this

+	 *            capability is not used by the current implementation)

+	 */

+	public void startInstallation(String installation, String deepLinkURL) {

+		String command = properties.getInstallationCommand(installation);

+		Option<String> commandLine = computeStartupCommandLine(installation, command);

+		if (!commandLine.hasValue()) {

+			throw new IllegalArgumentException("Unable to launch installation: " + installation);

+		}

+		String commandDir = rootFolder + "\\" + installation;

+		startApplication(commandLine.get(), commandDir);

+	}

+

+	class StreamGobbler extends Thread

+	{

+	    InputStream is;

+	    String type;

+	    

+	    StreamGobbler(InputStream is, String type)

+	    {

+	        this.is = is;

+	        this.type = type;

+	        setDaemon(true);

+	    }

+	    

+	    public void run()

+	    {

+	        try

+	        {

+	            InputStreamReader isr = new InputStreamReader(is);

+	            BufferedReader br = new BufferedReader(isr);

+	            String line=null;

+	            while ( (line = br.readLine()) != null)

+	                System.out.println(type + ">" + line);    

+	            } catch (IOException ioe)

+	              {

+	                ioe.printStackTrace();  

+	              }

+	    }

+	}

+

+	private void startApplication(String command, String commandDir) {

+		try {

+			Process p = Runtime.getRuntime().exec(command, null, new File(commandDir));

+			// For now, just throw away the data from the subprocess.  Possibly log it in the future?

+			// See: http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html?page=4

+			new StreamGobbler(p.getInputStream(), "STDOUT").start();

+			new StreamGobbler(p.getErrorStream(), "STDERR").start();

+		} catch (IOException e) {

+			throw new RuntimeException("Unable to launch: " + command, e);

+		}

+	}

+

+	private Option<String> computeStartupCommandLine(String installation,

+			String command) {

+		Option<String> result = validateCommand(installation, command);

+		if (result.hasValue()) {

+			return result;

+		}

+		return validateCommand(installation, "eclipse.exe");

+	}

+

+	private Option<String> validateCommand(String installation, String command) {

+		if (command != null) {

+			String commandLine = rootFolder + "\\" + installation + "\\" + command;

+			if (new File(commandLine).exists()) {

+				return some(commandLine);

+			} else {

+				return none();

+			}

+		}

+		return none();

+	}

+}

diff --git a/bundles/org.eclipse.e4.core.deeplink/src/org/eclipse/e4/core/deeplink/internal/NullNode.java b/bundles/org.eclipse.e4.core.deeplink/src/org/eclipse/e4/core/deeplink/internal/NullNode.java
new file mode 100644
index 0000000..eb9ef39
--- /dev/null
+++ b/bundles/org.eclipse.e4.core.deeplink/src/org/eclipse/e4/core/deeplink/internal/NullNode.java
@@ -0,0 +1,181 @@
+/******************************************************************************
+ * Copyright (c) David Orme and others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    David Orme - initial API and implementation
+ ******************************************************************************/
+/**

+ * 

+ */

+package org.eclipse.e4.core.deeplink.internal;

+

+import org.w3c.dom.DOMException;

+import org.w3c.dom.Document;

+import org.w3c.dom.NamedNodeMap;

+import org.w3c.dom.Node;

+import org.w3c.dom.NodeList;

+import org.w3c.dom.UserDataHandler;

+

+/**

+ * A null object pattern implementation of a W3C DOM Node.  Used when parsing

+ * the deep link result XML's DOM. 

+ */

+public class NullNode implements Node {

+

+	public static final String UNDEFINED_VALUE = "undefined";

+

+	public Node appendChild(Node newChild) throws DOMException {

+		return null;

+	}

+

+	public Node cloneNode(boolean deep) {

+		return null;

+	}

+

+	public short compareDocumentPosition(Node other) throws DOMException {

+		return 0;

+	}

+

+	public NamedNodeMap getAttributes() {

+		return null;

+	}

+

+	public String getBaseURI() {

+		return null;

+	}

+

+	public NodeList getChildNodes() {

+		return null;

+	}

+

+	public Object getFeature(String feature, String version) {

+		return null;

+	}

+

+	public Node getFirstChild() {

+		return null;

+	}

+

+	public Node getLastChild() {

+		return null;

+	}

+

+	public String getLocalName() {

+		return UNDEFINED_VALUE;

+	}

+

+	public String getNamespaceURI() {

+		return UNDEFINED_VALUE;

+	}

+

+	public Node getNextSibling() {

+		return null;

+	}

+

+	public String getNodeName() {

+		return UNDEFINED_VALUE;

+	}

+

+	public short getNodeType() {

+		return 0;

+	}

+

+	public String getNodeValue() throws DOMException {

+		return UNDEFINED_VALUE;

+	}

+

+	public Document getOwnerDocument() {

+		return null;

+	}

+

+	public Node getParentNode() {

+		return null;

+	}

+

+	public String getPrefix() {

+		return UNDEFINED_VALUE;

+	}

+

+	public Node getPreviousSibling() {

+		return null;

+	}

+

+	public String getTextContent() throws DOMException {

+		return UNDEFINED_VALUE;

+	}

+

+	public Object getUserData(String key) {

+		return null;

+	}

+

+	public boolean hasAttributes() {

+		return false;

+	}

+

+	public boolean hasChildNodes() {

+		return false;

+	}

+

+	public Node insertBefore(Node newChild, Node refChild)

+			throws DOMException {

+		return null;

+	}

+

+	public boolean isDefaultNamespace(String namespaceURI) {

+		return false;

+	}

+

+	public boolean isEqualNode(Node arg) {

+		return false;

+	}

+

+	public boolean isSameNode(Node other) {

+		return false;

+	}

+

+	public boolean isSupported(String feature, String version) {

+		return false;

+	}

+

+	public String lookupNamespaceURI(String prefix) {

+		return UNDEFINED_VALUE;

+	}

+

+	public String lookupPrefix(String namespaceURI) {

+		return UNDEFINED_VALUE;

+	}

+

+	public void normalize() {

+	}

+

+	public Node removeChild(Node oldChild) throws DOMException {

+		return null;

+	}

+

+	public Node replaceChild(Node newChild, Node oldChild)

+			throws DOMException {

+		return null;

+	}

+

+	public void setNodeValue(String nodeValue) throws DOMException {

+		

+	}

+

+	public void setPrefix(String prefix) throws DOMException {

+		

+	}

+

+	public void setTextContent(String textContent) throws DOMException {

+		

+	}

+

+	public Object setUserData(String key, Object data,

+			UserDataHandler handler) {

+		return null;

+	}

+	

+}
\ No newline at end of file
diff --git a/bundles/org.eclipse.e4.core.deeplink/src/org/eclipse/e4/core/deeplink/internal/ParsedDeepLinkURL.java b/bundles/org.eclipse.e4.core.deeplink/src/org/eclipse/e4/core/deeplink/internal/ParsedDeepLinkURL.java
new file mode 100644
index 0000000..b0885f9
--- /dev/null
+++ b/bundles/org.eclipse.e4.core.deeplink/src/org/eclipse/e4/core/deeplink/internal/ParsedDeepLinkURL.java
@@ -0,0 +1,38 @@
+/******************************************************************************
+ * Copyright (c) David Orme and others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    David Orme - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.e4.core.deeplink.internal;

+

+import org.eclipse.e4.core.deeplink.DeepLinkURLException;
+

+

+/**

+ * Parse a deeplink:// URL so that the installation and the rest of the URL

+ * can be processed separately.

+ */

+public class ParsedDeepLinkURL {

+

+	private static final String PROTOCOL_STRING = "deeplink://";

+	

+	public final String installation;

+	public final String restOfURL;

+

+	public ParsedDeepLinkURL(String urlString) throws DeepLinkURLException {

+		if (!urlString.startsWith(PROTOCOL_STRING)) {

+			throw new DeepLinkURLException("Invalid deep link URL: " + urlString);

+		}

+		String urlWithoutProtocol = urlString.replace(PROTOCOL_STRING, "").trim();

+		if (urlWithoutProtocol.length() < 1 || urlWithoutProtocol.startsWith("/")) {

+			throw new DeepLinkURLException("Invalid deep link URL: " + urlString);

+		}

+		installation = urlWithoutProtocol.split("/+")[0];

+		restOfURL = urlWithoutProtocol.replace(installation, "").trim();

+	}

+}