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