Package au.com.forge.eclipse.pde.impl

Source Code of au.com.forge.eclipse.pde.impl.ApacheFelixLaunchConfiguration

/**
*  Eclipse PDE Launch Configuration - An Eclipse plugin that can launch
*    Apache Felix in conjunction with the Eclipse Felix PDE Launcher
*  Copyright (C) 2010 Forge Research
*  Copyright (C) 2010-2014 Christopher Armstrong <carmstrong@fastmail.com.au>
*
*  This program is free software: you can redistribute it and/or modify
*  it under the terms of the GNU General Public License as published by
*  the Free Software Foundation, either version 3 of the License, or
*  (at your option) any later version.
*
*  This program is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU General Public License for more details.
*
*  You should have received a copy of the GNU General Public License
*   along with this program.  If not, see <http://www.gnu.org/licenses/>.
*
*/
package au.com.forge.eclipse.pde.impl;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Scanner;

import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
import org.eclipse.pde.core.plugin.IMatchRules;
import org.eclipse.pde.core.plugin.IPluginModelBase;
import org.eclipse.pde.core.plugin.PluginRegistry;
import org.eclipse.pde.launching.AbstractPDELaunchConfiguration;
import org.eclipse.pde.launching.IPDELauncherConstants;
import org.osgi.framework.Bundle;
import org.osgi.framework.Version;

/**
* Apache Felix launch configuration for Eclipse. This plugin depends on the
* custom launcher specified by {@link #LAUNCHER_PLUGIN_ID}.
*
* @author Christopher Armstrong
*
*/
public class ApacheFelixLaunchConfiguration extends
    AbstractPDELaunchConfiguration {

  /**
   * The main class in the Eclipse PDE Felix Launcher bundle.
   */
  private static final String LAUNCHER_PLUGIN_MAIN_CLASS = "au.com.forge.felix.eclipse_pde_launcher.impl.EclipsePDEFelixLauncher";

  /**
   * The identifier of this plugin.
   */
  private static final String PLUGIN_ID = "au.com.forge.felix.eclipse.launch_configuration";
  /**
   * The identifier of the Eclipse PDE Felix Launcher (pre Felix v4.2)
   */
  private static final String LAUNCHER_PLUGIN_ID_LEGACY = "au.com.forge.felix.eclipse_pde_launcher";

  /**
   * The identifier of the Eclipse PDE Felix launcher (Felix v4.2 and up)
   */
  private static final String LAUNCHER_PLUGIN_ID_FELIX42 = "au.com.forge.felix.eclipse_pde_launcher-4.2";

  /**
   * The launcher configuration property that specifies the path
   * to the configuration file to be merged into the Felix launch
   * configuration.
   *
   */
  private static final String LAUNCHER_CONFIG_PROPERTY_KEY = "au.com.forge.felix.config.properties";

  /**
   * The bundle name of the Apache Felix main bundle.
   */
  private static final String APACHE_FELIX_MAIN_BUNDLE = "org.apache.felix.main";

  /**
   * Internal representation of a plugin (aka bundle).
   *
   * @author chris
   *
   */
  private static class PluginSpec {
    public PluginSpec(String bundleName2, String version2,
        Integer startLevel2, boolean autostart2) {
      this.bundleName = bundleName2;
      this.version = version2;
      this.startLevel = startLevel2;
      this.autostart = autostart2;
    }

    public String bundleName;
    public String version;
    public Integer startLevel;
    public boolean autostart;
  }

  /**
   * Create the list of program arguments that are to be passed to the Felix
   * Application launcher. This implementation passes the arguments generated
   * by the parent class, and adds:
   * <dl>
   * <dt><bundle-cache>
   * <dd>The location of the bundle cache. This is calculated to be a
   * subdirectory called <code>bundle-cache</code> of the configuration
   * directory (<code>{@link #getConfigDir(ILaunchConfiguration)}</code>). It
   * is created if it does not exist.
   * </dl>
   * <p>
   * This is different from the VM arguments, which are the parameters passed
   * to the virtual machine, not the application being launched.
   */
  public String[] getProgramArguments(ILaunchConfiguration configuration)
      throws CoreException {
    List<String> arguments = new ArrayList<String>();

    File bundleCacheDir = new File(getConfigDir(configuration),
        "bundle-cache");
    if (!bundleCacheDir.exists())
      bundleCacheDir.mkdir();
    arguments.add("bundle-cache");

    arguments.addAll(Arrays.asList(configuration.getAttribute(
        IJavaLaunchConfigurationConstants.ATTR_PROGRAM_ARGUMENTS, "")
        .split(" ")));
    return (String[]) arguments.toArray(new String[arguments.size()]);
  }

  /**
   * Cleanup working directories and configuration
   */
  protected void clear(ILaunchConfiguration configuration,
      IProgressMonitor monitor) throws CoreException {
   
    File bundleCacheDir = new File(getConfigDir(configuration),
        "bundle-cache");
    {
      IFileStore bundleCacheStore = EFS.getStore(bundleCacheDir.toURI());
      bundleCacheStore.delete(EFS.NONE, monitor);
    }
    if (configuration.getAttribute(IPDELauncherConstants.CONFIG_CLEAR_AREA,
        false)) {
      File confDir = new File(getConfigDir(configuration), "conf");
      IFileStore confDirStore = EFS.getStore(confDir.toURI());
      confDirStore.delete(EFS.NONE, monitor);
    }
  }

  /**
   * Calculate the classpath of the OSGi instance we create (the Felix
   * launcher). This method adds the Apache Felix main bundle
   * (org.apache.felix.main) and the custom Eclipse-Felix launcher bundle that
   * is packaged with this plugin.
   */
  public String[] getClasspath(ILaunchConfiguration configuration)
      throws CoreException {
    ArrayList<String> classpath = new ArrayList<String>();

    // Find all the Apache Felix "main" bundles in the target platform
    IPluginModelBase[] apacheFelixBundles = PluginRegistry.findModels(
        APACHE_FELIX_MAIN_BUNDLE, null, null);

    if (apacheFelixBundles.length == 0)
      throw new CoreException(
          new Status(
              Status.ERROR,
              PLUGIN_ID,
              "Unable to find any Apache Felix main bundle (org.apache.felix.main) in the workspace or the Target Platform."));
    // Find the selected apache felix bundle version
    Version targetVersion = findTargetApacheFelixVersion(configuration);
    IPluginModelBase apacheFelixBundle = null;
    if (targetVersion != null) {
      for (int i = 0; i < apacheFelixBundles.length; i++) {
        if (apacheFelixBundles[i].getBundleDescription().getVersion()
            .equals(targetVersion)) {
          apacheFelixBundle = apacheFelixBundles[i];
          break;
        }
      }
      if (apacheFelixBundle == null) {
        throw new CoreException(
            new Status(
                Status.ERROR,
                PLUGIN_ID,
                "Could not find bundle location for selected Apache Felix Main version "
                    + targetVersion
                    + " among Apache Felix Main bundles found in target platform "
                    + Arrays.toString(apacheFelixBundles)));
      }
    } else {
      apacheFelixBundle = apacheFelixBundles[0];
    }
    if (apacheFelixBundle.getInstallLocation() == null)
      throw new CoreException(
          new Status(
              Status.ERROR,
              PLUGIN_ID,
              "Found Felix plugin in workspace, but unable to find its corresponding JAR file."));
    classpath.add(apacheFelixBundle.getInstallLocation());

    Version felixBundleVersion = apacheFelixBundle.getBundleDescription()
        .getVersion();
    int major = felixBundleVersion.getMajor();
    int minor = felixBundleVersion.getMinor();
    Collection<String> felix42LauncherClasspath = calculateNeededClassPath(LAUNCHER_PLUGIN_ID_FELIX42);
    Collection<String> legacyLauncherClasspath = calculateNeededClassPath(LAUNCHER_PLUGIN_ID_LEGACY);

    if ((major == 4 && minor >= 2) || (major > 4)) {
      if (felix42LauncherClasspath != null)
        classpath.addAll(felix42LauncherClasspath);
      else
        throw new CoreException(
            new Status(
                Status.ERROR,
                PLUGIN_ID,
                "Unable to find required Eclipse-Felix launcher plugin for Apache Felix 4.2 or newer"
                    + " packaged as a JAR in the target "
                    + "platform, Eclipse installation or workspace."));
    } else {
      if (legacyLauncherClasspath != null)
        classpath.addAll(legacyLauncherClasspath);
      else
        throw new CoreException(
            new Status(
                Status.ERROR,
                PLUGIN_ID,
                "Unable to find required Eclipse-Felix launcher plugin for older Apache Felix (older than 4.2)"
                    + " packaged as a JAR in the target "
                    + "platform, Eclipse installation or workspace."));

    }

    return (String[]) classpath.toArray(new String[classpath.size()]);
  }

  /**
   * Find the Apache Felix version in the launch configuration.
   *
   * @param configuration launch configuration
   * @return the Felix version, or null if there is no Felix plugin in the launch config
   * @throws CoreException
   */
  private Version findTargetApacheFelixVersion(
      ILaunchConfiguration configuration) throws CoreException {
    String[] targetPlugins = getTargetPluginList(configuration);
    for (int i = 0; i < targetPlugins.length; i++) {
      String targetPlugin = targetPlugins[i];
      PluginSpec pluginSpec = parseTargetPluginString(targetPlugin,
          false, null);
      if (pluginSpec.bundleName.equals(APACHE_FELIX_MAIN_BUNDLE))
        return pluginSpec.version != null ? new Version(
            pluginSpec.version) : null;
    }
    return null;
  }

  /**
   * MOD_0.2.2 ogattaz
   *
   * Calculate the classpath of the specified plugin that lives in the Eclipse
   * installation or in the development workspace.
   *
   * @param pluginId
   *            The OSGi plugin ID to calculate the classpath for.
   * @return A collection of classpath elements
   * @throws CoreException
   *             Thrown when the specified plugin cannot be found in the
   *             workspace or Eclipse installation.
   */
  private Collection<String> calculateNeededClassPath(String pluginId)
      throws CoreException {
    ArrayList<String> classpath = new ArrayList<String>();
    IPluginModelBase model = PluginRegistry.findModel(pluginId);
    if (model != null && model.getUnderlyingResource() == null) {
      classpath.add(model.getInstallLocation());
      return classpath;
    }
    Bundle bundle = Platform.getBundle(pluginId);
    if (bundle != null) {
      try {
        File bundleFile = FileLocator.getBundleFile(bundle);
        classpath.add(bundleFile.getAbsolutePath());
       
        /*
         * MOD_0.2.2 ogattaz
         *
         * This trick to be able to retrieve the
         * "Eclipse DEFelix Launcher" class during the plugins
         * development time
         *
         * This trick allows you to debug the plugins live, by launching
         * an instance of Eclipse from your development Eclipse using
         * the PDE projects rather the built and installed plugins
         *
         * if the bundle AbsolutePath is the file directory of a PDE
         * project on the file system, tries to find a sub folder "bin"
         * (cf. a pde project)
         */
        if (bundleFile.isDirectory() && isPdeProjectDir(bundleFile)) {

          // In a PDE project, the classes are available in the "bin"
          // sub directory of the project (unlike in the jar file of a
          // built bundle).
          File wBundleBinDir = new File(bundleFile, "bin");
          if (wBundleBinDir.exists()) {
            classpath.add(wBundleBinDir.getAbsolutePath());
          }
        }

        return classpath;
      } catch (IOException e) {
      }
    }
    return null;
  }

  /**
   * Determine if the specified project is a PDE project.
   *
   * MOD_0.2.2 ogattaz
   *
   * @return true if the file directory contains valid ".classpath" and
   *         ".project" files
   */
  private boolean isPdeProjectDir(File aDir) {
    return isDotProjectFileExists(aDir) && isDotClasspathFileExists(aDir);
  }

  /**
   * Determine if the directory is a project directory
   * (i.e. contains a valid .classpath file)
   * 
   * <pre>
   * <?xml version="1.0" encoding="UTF-8"?>
   * <classpath>
   * ...
   * </classpath>
   *
   * </pre>
   * @author MOD_0.2.2 ogattaz
   * @param aDir
   *            the file directory to be explored
   * @return true if the file directory contains a valid ".classpath" file
   */
  private boolean isDotClasspathFileExists(File aDir) {
    File wBundleClasspathFile = new File(aDir, ".classpath");
    return wBundleClasspathFile.exists()
        && isFileContainsText(wBundleClasspathFile, "</classpath>");

  }

  /**
   * Determine if the directory contains a valid .project file
   * MOD_0.2.2 ogattaz
   *
   * <pre>
   * <?xml version="1.0" encoding="UTF-8"?>
   * <projectDescription>
   * ...
   * </projectDescription>
   * </pre>
   *
   * @return
   */
  /**
   * @param aDir
   *            the file directory to be explored
   * @return true if the file directory contains a valid ".project" file
   */
  private boolean isDotProjectFileExists(File aDir) {
    File wBundleProjectFile = new File(aDir, ".project");
    return wBundleProjectFile.exists()
        && isFileContainsText(wBundleProjectFile,
            "</projectDescription>");
  }

  /**
   * Determine if the file contains a specific
   * piece of text.
   *
   * MOD_0.2.2 ogattaz
   *
   * @param aFile
   * @param aText
   * @return
   */
  private boolean isFileContainsText(File aFile, String aText) {

    if (aText != null && !aText.isEmpty()) {
      Scanner scanner = null;
      try {
        scanner = new Scanner(aFile);
        // now read the file line by line...
        String wLine;
        while (scanner.hasNextLine()) {
          wLine = scanner.nextLine();
          if (wLine != null && !wLine.isEmpty()
              && wLine.contains(aText)) {
            return true;
          }
        }
      } catch (FileNotFoundException e) {
        // nothing special , return false
      } finally {
        if (scanner != null) {
          scanner.close();
        }
      }
    }
    return false;
  }

  /**
   * Determine the arguments to pass to the sub-process VM. This method
   * extends the super method and adds the location of the felix configuration
   * properties file, which in turn is generated by this
   */
  public String[] getVMArguments(ILaunchConfiguration configuration)
      throws CoreException {
    ArrayList<String> vmArguments = new ArrayList<String>();
    vmArguments.addAll(Arrays.asList(super.getVMArguments(configuration)));
    vmArguments.add(String.format("-D%s=",
        new Object[] { LAUNCHER_CONFIG_PROPERTY_KEY })
        + createConfigurationProperties(configuration));
    return (String[]) vmArguments.toArray(new String[vmArguments.size()]);
  }

  /**
   * Get the Main class of the launcher. This value is specified by
   * {@link #LAUNCHER_PLUGIN_MAIN_CLASS}.
   */
  public String getMainClass() {
    return LAUNCHER_PLUGIN_MAIN_CLASS;
  }

  /**
   * Generate the configuration properties file that is passed to the Felix
   * Launcher. This takes the configuration specified in the Launch
   * Configuration dialog and translates it into Felix properties.
   *
   * @param configuration
   *            The launch configuration, which contains the user-specified
   *            properties
   * @return The URI of the properties file, which is passed to the Felix
   *         launcher
   * @throws CoreException
   *             Thrown when there is a problem writing the configuration
   *             properties to the filesystem.
   */
  private String createConfigurationProperties(
      ILaunchConfiguration configuration) throws CoreException {
    // Felix conf/ directory, inside the Eclipse "configuration area"

    File confDir = new File(getConfigDir(configuration), "conf");
    if (!confDir.exists())
      confDir.mkdir();
    File configPropertiesFile = new File(confDir, "config.properties");
    Properties configProperties = new Properties();

    configProperties.setProperty("felix.cache.rootdir",
        getConfigDir(configuration).getAbsolutePath());
    configProperties.setProperty("org.osgi.framework.storage",
        "bundle-cache");
    configProperties
        .setProperty("org.osgi.framework.storage.clean", "none");

    Integer defaultStartLevel = new Integer(configuration.getAttribute(
        IPDELauncherConstants.DEFAULT_START_LEVEL, 4));
    configProperties.setProperty("org.osgi.framework.startlevel.beginning",
        defaultStartLevel.toString());
    boolean defaultAutoStart = configuration.getAttribute(
        IPDELauncherConstants.DEFAULT_AUTO_START, true);
   
    // Installed+Started bundles
    Map<Integer,List<String>> startLevelBundles = new HashMap<Integer,List<String>>();
    // Installed only bundles
    Map<Integer,List<String>>  installLevelBundles = new HashMap<Integer,List<String>> ();

    // Parse the list of selected Target Platform bundles
    String[] targetPlugins = getTargetPluginList(configuration);
    if (targetPlugins != null) {
      parseBundleList(defaultStartLevel, defaultAutoStart, targetPlugins,
          startLevelBundles, installLevelBundles, "file");
    }

    // Parse the list of selected Workspace bundles
    String workspacePluginList = configuration.getAttribute(
        IPDELauncherConstants.WORKSPACE_BUNDLES, (String) null);
    if (workspacePluginList != null) {
      String[] workspacePlugins = workspacePluginList.split(",");
      parseBundleList(defaultStartLevel, defaultAutoStart,
          workspacePlugins, startLevelBundles, installLevelBundles,
          "eclipse-project");
    }

    writeBundles(startLevelBundles, "felix.auto.start.", configProperties);
    writeBundles(installLevelBundles, "felix.auto.install.",
        configProperties);

    try {
      configProperties.store(new FileOutputStream(configPropertiesFile),
          "");
    } catch (IOException e) {
      throw new CoreException(
          new Status(Status.ERROR, PLUGIN_ID,
              "Unable to write config.properties file: "
                  + e.getMessage(), e));
    }
    return configPropertiesFile.toURI().toString();
  }

  /**
   * Retrieve the target plugin list from the launch configuration
   *
   * @param configuration
   *            launch configuration
   * @return target plugin list
   * @throws CoreException
   */
  private String[] getTargetPluginList(ILaunchConfiguration configuration)
      throws CoreException {
    String targetPlatformPluginList = configuration.getAttribute(
        IPDELauncherConstants.TARGET_BUNDLES, (String) null);
    String[] targetPlugins = null;
    if (targetPlatformPluginList != null) {
      targetPlugins = targetPlatformPluginList.split(",");
    }
    return targetPlugins;
  }

  /**
   * Parse the list of bundles.
   *
   * @param defaultStartLevel
   *            The default start level.
   * @param defaultAutoStart
   *            A boolean indicating if bundles should be auto-started.
   * @param targetPlugins
   *            The list of plugins in the target, specified as plugin
   *            identifiers
   * @param startLevelBundles
   *            The map of start level => bundles (bundles which start
   *            automatically)
   * @param installLevelBundles
   *            The map of install level => bundles (bundles which are to be
   *            installed only)
   * @param protocol
   *            The protocol used to launch the plugin.
   * @throws CoreException
   *             Thrown when an error occurs parsing the target platform
   *             plugin names.
   */
  private void parseBundleList(Integer defaultStartLevel,
      boolean defaultAutoStart, String[] targetPlugins,
      Map<Integer,List<String> >  startLevelBundles, Map<Integer,List<String> >  installLevelBundles, String protocol)
      throws CoreException {
    for (int i = 0; i < targetPlugins.length; i++) {
      String targetPlugin = targetPlugins[i];
      PluginSpec plugin = parseTargetPluginString(targetPlugin,
          defaultAutoStart, defaultStartLevel);

      List<String>  bundles;
      if (plugin.autostart) {
        bundles = (List<String>) startLevelBundles.get(plugin.startLevel);
        if (bundles == null) {
          bundles = new ArrayList<String>();
          startLevelBundles.put(plugin.startLevel, bundles);
        }
      } else {
        bundles = (List<String>) installLevelBundles.get(plugin.startLevel);
        if (bundles == null) {
          bundles = new ArrayList<String>();
          installLevelBundles.put(plugin.startLevel, bundles);
        }
      }

      IPluginModelBase pluginModelBase = findBundleModel(
          plugin.bundleName, plugin.version);
      if (pluginModelBase == null) {
        throw new CoreException(new Status(Status.ERROR, PLUGIN_ID,
            "Unable to load plugin model for bundle '"
                + plugin.bundleName + "' version "
                + plugin.version));
      }
      bundles.add(protocol + ":" + pluginModelBase.getInstallLocation());
    }
  }

  /**
   * Parse a target plugin name and generate a PluginSpec
   * for the framework launch.
   *
   * @param targetPlugin eclipse-generated plugin name
   * @param defaultAutoStart default auto start level
   * @param defaultStartLevel default plugin start level
   * @return a plugin spec for launching the bundle
   * @throws CoreException
   */
  private PluginSpec parseTargetPluginString(String targetPlugin,
      boolean defaultAutoStart, Integer defaultStartLevel)
      throws CoreException {
    String[] pluginSpec = targetPlugin.split("@", 2);

    if (pluginSpec.length != 2) {
      throw new CoreException(new Status(Status.ERROR, PLUGIN_ID,
          "Unexpected target plugin specification: " + targetPlugin));
    }
    final String bundleSpec = pluginSpec[0];
    final String startSpec = pluginSpec[1];
    final String[] nameParts = bundleSpec.split("\\*", 2);
    final String[] componentParts = startSpec.split(":", 2);

    Integer startLevel;
    boolean autostart;
    String version = null;
    String bundleName;
    if (componentParts.length == 2) {
      autostart = componentParts[1].equals("default") ? defaultAutoStart
          : Boolean.valueOf(componentParts[1]).booleanValue();

      startLevel = componentParts[0].equals("default") ? defaultStartLevel
          : Integer.valueOf(componentParts[0]);

      bundleName = nameParts[0];
      if (nameParts.length == 2) {
        version = nameParts[1];
      }
    } else
      // (componentParts.length < 3)
      throw new CoreException(new Status(Status.ERROR, PLUGIN_ID,
          "Problem parsing target/workspace bundle strings."));

    return new PluginSpec(bundleName, version, startLevel, autostart);
  }

  /**
   * Query the plugin registry for a bundle
   *
   * @param bundleName the bundle name
   * @param version the bundle version (optionally null)
   * @return the plugin model base object
   */
  private IPluginModelBase findBundleModel(final String bundleName,
      final String version) {
    if (version == null)
      return PluginRegistry.findModel(bundleName);
    else {
      return PluginRegistry.findModel(bundleName, version,
          IMatchRules.PERFECT, null);
    }
  }

  /**
   * Write the list of bundles and their start levels to the configuration
   * file.
   *
   * @param startLevelBundles
   *            A map of start level => array of strings containing bundle
   *            names
   * @param propertyPrefix
   *            The prefix of the property used to auto-start / auto-install
   *            that start level
   * @param configProperties
   *            The set of configuration properties which will be written to
   *            the configuration properties file for Felix
   */
  private void writeBundles(Map<Integer,List<String>> startLevelBundles, String propertyPrefix,
      Properties configProperties) {

    Iterator<Entry<Integer,List<String>>> startLevelIt = startLevelBundles.entrySet().iterator();
    while (startLevelIt.hasNext()) {
      StringBuffer bundleList = new StringBuffer();
      Entry<Integer,List<String>> entry = startLevelIt.next();
      Integer startLevel = (Integer) entry.getKey();
      List<String> bundles = entry.getValue();
      Iterator<String> bundleIt = bundles.iterator();
      while (bundleIt.hasNext()) {
        String bundleUrl = (String) bundleIt.next();
        bundleList.append(bundleUrl);
        bundleList.append(" ");
      }
      configProperties.setProperty(
          propertyPrefix + startLevel.toString(),
          bundleList.toString());
    }
  }
}
TOP

Related Classes of au.com.forge.eclipse.pde.impl.ApacheFelixLaunchConfiguration

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.