package org.argeo.slc.ide.ui.launch.osgi; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.StringTokenizer; import java.util.TreeMap; import java.util.TreeSet; import org.argeo.slc.ide.ui.SlcIdeUiPlugin; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.variables.IStringVariableManager; import org.eclipse.core.variables.VariablesPlugin; import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants; import org.eclipse.jdt.launching.IVMInstall; import org.eclipse.jdt.launching.IVMInstall2; import org.eclipse.jdt.launching.IVMInstallType; import org.eclipse.jdt.launching.JavaRuntime; import org.eclipse.jface.dialogs.ErrorDialog; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.osgi.service.resolver.BundleDescription; import org.eclipse.pde.core.plugin.IPluginModelBase; import org.eclipse.pde.core.plugin.PluginRegistry; import org.eclipse.pde.launching.IPDELauncherConstants; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; /** * Most of the actual logic is concentrated in this class which manipulates * {@link ILaunchConfigurationWorkingCopy}. Static method are used since the * shortcut and launch configuration classes are already extending PDE classes. */ @SuppressWarnings("restriction") public class OsgiLaunchHelper implements OsgiLauncherConstants { private static Boolean debug = true; private final static String DEFAULT_DATA_DIR = "data"; private final static String DEFAULT_EXEC_DIR = "exec"; private final static String DEFAULT_VMARGS = "-Xmx256m"; private final static String DEFAULT_PROGRAM_ARGS = "-console"; /** Sets default values on this configuration. */ public static void setDefaults(ILaunchConfigurationWorkingCopy wc, Boolean isEclipse) { try { if (isEclipse) { wc.setAttribute(IPDELauncherConstants.USE_DEFAULT, false); wc.setAttribute(IPDELauncherConstants.USE_PRODUCT, false); } wc.setAttribute(ATTR_ADD_JVM_PATHS, false); wc.setAttribute(ATTR_ADDITIONAL_VM_ARGS, DEFAULT_VMARGS); wc.setAttribute(ATTR_ADDITIONAL_PROGRAM_ARGS, DEFAULT_PROGRAM_ARGS); // Defaults String originalVmArgs = wc.getAttribute( IJavaLaunchConfigurationConstants.ATTR_VM_ARGUMENTS, ""); wc.setAttribute(ATTR_DEFAULT_VM_ARGS, originalVmArgs); // clear config area by default wc.setAttribute(IPDELauncherConstants.CONFIG_CLEAR_AREA, true); } catch (CoreException e) { Shell shell = Display.getCurrent().getActiveShell(); ErrorDialog.openError(shell, "Error", "Cannot execute initalize configuration", e.getStatus()); } } /** Find the working directory based on this properties file. */ public static String findWorkingDirectory(IFile propertiesFile) { try { IProject project = propertiesFile.getProject(); IPath parent = propertiesFile.getProjectRelativePath() .removeLastSegments(1); IFolder execFolder = project.getFolder(parent .append(DEFAULT_EXEC_DIR)); if (!execFolder.exists()) execFolder.create(true, true, null); IFolder launchFolder = project.getFolder(execFolder .getProjectRelativePath().append( extractName(propertiesFile))); if (!launchFolder.exists()) launchFolder.create(true, true, null); return "${workspace_loc:" + launchFolder.getFullPath().toString().substring(1) + "}"; } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("Cannot create working directory", e); } } /** Extract the launch configuration name from the properties file. */ public static String extractName(IFile propertiesFile) { IPath path = propertiesFile.getFullPath(); IPath pathNoExt = path.removeFileExtension(); return pathNoExt.segment(pathNoExt.segmentCount() - 1); } /** Expects properties file to be set as mapped resources */ @SuppressWarnings("unchecked") public static void updateLaunchConfiguration( ILaunchConfigurationWorkingCopy wc, Boolean isEclipse) { try { if (debug) debug("##\n## Launch " + wc.getName() + " - " + new Date() + "\n##"); // Finds the properties file and load it IFile propertiesFile = (IFile) wc.getMappedResources()[0]; propertiesFile.refreshLocal(IResource.DEPTH_ONE, null); Properties properties = readProperties(propertiesFile); // Extract information from the properties file Map bundlesToStart = new TreeMap(); Map systemPropertiesToAppend = new HashMap(); String applicationId = interpretProperties(properties, bundlesToStart, systemPropertiesToAppend); if (applicationId != null) wc.setAttribute(IPDELauncherConstants.APPLICATION, applicationId); else { if (isEclipse) throw new Exception("No application defined," + " please set the 'eclipse.application' property" + " in the properties file"); } // Define directories File workingDir = getWorkingDirectory(wc); File dataDir = new File(workingDir, DEFAULT_DATA_DIR); // Update the launch configuration accordingly updateLaunchConfiguration(wc, bundlesToStart, systemPropertiesToAppend, dataDir.getAbsolutePath(), isEclipse); if (debug) { Map attrs = new TreeMap( (Map) wc.getAttributes()); for (String key : attrs.keySet()) OsgiLaunchHelper.debug(key + "=" + attrs.get(key)); } } catch (Exception e) { e.printStackTrace(); Shell shell = SlcIdeUiPlugin.getDefault().getWorkbench() .getActiveWorkbenchWindow().getShell(); // Shell shell= Display.getCurrent().getActiveShell(); ErrorDialog.openError(shell, "Error", "Cannot prepare launch configuration", new Status(IStatus.ERROR, SlcIdeUiPlugin.ID, e.getMessage(), e)); return; } } /** * Actually modifies the launch configuration in order to reflect the * current state read from the properties file and the launch configuration * UI. */ protected static void updateLaunchConfiguration( ILaunchConfigurationWorkingCopy wc, Map bundlesToStart, Map systemPropertiesToAppend, String dataDir, Boolean isEclipse) throws CoreException { // Convert bundle lists final String targetBundles; final String wkSpaceBundles; if (wc.getAttribute(ATTR_SYNC_BUNDLES, true)) { StringBuffer tBuf = new StringBuffer(); for (IPluginModelBase model : PluginRegistry.getExternalModels()) { tBuf.append(model.getBundleDescription().getSymbolicName()); tBuf.append(','); } targetBundles = tBuf.toString(); StringBuffer wBuf = new StringBuffer(); models: for (IPluginModelBase model : PluginRegistry .getWorkspaceModels()) { if (model.getBundleDescription() == null) { System.err.println("No bundle description for " + model); continue models; } wBuf.append(model.getBundleDescription().getSymbolicName()); wBuf.append(','); } wkSpaceBundles = wBuf.toString(); } else { targetBundles = wc.getAttribute(targetBundlesAttr(isEclipse), ""); wkSpaceBundles = wc.getAttribute(workspaceBundlesAttr(isEclipse), ""); } wc.setAttribute(targetBundlesAttr(isEclipse), convertBundleList(bundlesToStart, targetBundles)); wc.setAttribute(workspaceBundlesAttr(isEclipse), convertBundleList(bundlesToStart, wkSpaceBundles)); // Update other default information wc.setAttribute(IPDELauncherConstants.DEFAULT_AUTO_START, false); wc.setAttribute(IPDELauncherConstants.USE_CUSTOM_FEATURES, false); if (!isEclipse) wc.setAttribute(IPDELauncherConstants.USE_DEFAULT, true); // VM arguments (system properties) String defaultVmArgs = wc.getAttribute( OsgiLauncherConstants.ATTR_DEFAULT_VM_ARGS, ""); StringBuffer vmArgs = new StringBuffer(defaultVmArgs); // Data dir system property if (dataDir != null) { addSysProperty(vmArgs, OsgiLauncherConstants.ARGEO_OSGI_DATA_DIR, dataDir); if (isEclipse) { wc.setAttribute(IPDELauncherConstants.LOCATION, dataDir); } } // Add locations of JVMs if (wc.getAttribute(ATTR_ADD_JVM_PATHS, false)) addVms(vmArgs); // Add other system properties for (String key : systemPropertiesToAppend.keySet()) addSysProperty(vmArgs, key, systemPropertiesToAppend.get(key)); vmArgs.append(" ").append(wc.getAttribute(ATTR_ADDITIONAL_VM_ARGS, "")); wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_VM_ARGUMENTS, vmArgs.toString()); // Program arguments StringBuffer progArgs = new StringBuffer(""); if (dataDir != null) { progArgs.append("-data "); progArgs.append(surroundSpaces(dataDir)); if (wc.getAttribute(ATTR_CLEAR_DATA_DIRECTORY, false)) { File dataDirFile = new File(dataDir); deleteDir(dataDirFile); dataDirFile.mkdirs(); } } String additionalProgramArgs = wc.getAttribute( OsgiLauncherConstants.ATTR_ADDITIONAL_PROGRAM_ARGS, ""); progArgs.append(' ').append(additionalProgramArgs); wc.setAttribute( IJavaLaunchConfigurationConstants.ATTR_PROGRAM_ARGUMENTS, progArgs.toString()); } /** The launch configuration attribute to use for target bundles */ protected static String targetBundlesAttr(Boolean isEclipse) { return isEclipse ? IPDELauncherConstants.SELECTED_TARGET_PLUGINS : IPDELauncherConstants.TARGET_BUNDLES; } /** The launch configuration attribute to use for workspace bundles */ protected static String workspaceBundlesAttr(Boolean isEclipse) { return isEclipse ? IPDELauncherConstants.SELECTED_WORKSPACE_PLUGINS : IPDELauncherConstants.WORKSPACE_BUNDLES; } /** * Interprets special properties and register the others as system * properties to append. * * @return the application id defined by * {@link OsgiLauncherConstants#ECLIPSE_APPLICATION}, or null if not * found */ protected static String interpretProperties(Properties properties, Map bundlesToStart, Map systemPropertiesToAppend) { computeBundlesToStart(bundlesToStart, properties, null); String applicationId = null; propKeys: for (Object keyObj : properties.keySet()) { String key = keyObj.toString(); if (OsgiLauncherConstants.ARGEO_OSGI_START.equals(key)) continue propKeys; if (key.startsWith(OsgiLauncherConstants.ARGEO_OSGI_START + ".")) continue propKeys; else if (OsgiLauncherConstants.ARGEO_OSGI_BUNDLES.equals(key)) continue propKeys; else if (OsgiLauncherConstants.ARGEO_OSGI_LOCATIONS.equals(key)) continue propKeys; else if (OsgiLauncherConstants.OSGI_BUNDLES.equals(key)) continue propKeys; else if (OsgiLauncherConstants.ECLIPSE_APPLICATION.equals(key)) applicationId = properties.getProperty(key); else systemPropertiesToAppend.put(key, properties.getProperty(key)); } return applicationId; } /** Adds a regular system property. */ protected static void addSysProperty(StringBuffer vmArgs, String key, String value) { surroundSpaces(value); String str = "-D" + key + "=" + value; vmArgs.append(' ').append(str); } /** Adds JVMS registered in the workspace as special system properties. */ protected static void addVms(StringBuffer vmArgs) { addVmSysProperty(vmArgs, "default", JavaRuntime.getDefaultVMInstall()); IVMInstallType[] vmTypes = JavaRuntime.getVMInstallTypes(); for (IVMInstallType vmType : vmTypes) { for (IVMInstall vmInstall : vmType.getVMInstalls()) { // properties based on name addVmSysProperty(vmArgs, vmInstall.getName(), vmInstall); if (vmInstall instanceof IVMInstall2) { // properties based on version IVMInstall2 vmInstall2 = (IVMInstall2) vmInstall; String version = vmInstall2.getJavaVersion(); addVmSysProperty(vmArgs, version, vmInstall); List tokens = new ArrayList(); StringTokenizer st = new StringTokenizer(version, "."); while (st.hasMoreTokens()) tokens.add(st.nextToken()); if (tokens.size() >= 2) addVmSysProperty(vmArgs, tokens.get(0) + "." + tokens.get(1), vmInstall); } } } } /** Adds a special system property pointing to one of the registered JVMs. */ protected static void addVmSysProperty(StringBuffer vmArgs, String suffix, IVMInstall vmInstall) { addSysProperty(vmArgs, OsgiLauncherConstants.VMS_PROPERTY_PREFIX + "." + suffix, vmInstall.getInstallLocation().getPath()); } /** Surround the string with quotes if it contains spaces. */ protected static String surroundSpaces(String str) { if (str.indexOf(' ') >= 0) return '\"' + str + '\"'; else return str; } /** * Reformat the bundle list in order to reflect which bundles have to be * started. */ protected static String convertBundleList( Map bundlesToStart, String original) { StringTokenizer stComa = new StringTokenizer(original, ","); // sort by bundle symbolic name Set bundleIds = new TreeSet(); bundles: while (stComa.hasMoreTokens()) { String bundleId = stComa.nextToken(); if (bundleId.indexOf('*') >= 0) throw new RuntimeException( "Bundle id " + bundleId + " not properly formatted, clean your workspace projects"); int indexAt = bundleId.indexOf('@'); if (indexAt >= 0) { bundleId = bundleId.substring(0, indexAt); } // We can now rely on bundleId value if (bundleId.endsWith(".source")) { // debug("Skip source bundle " + bundleId); continue bundles; } else if (bundleId .equals("org.eclipse.equinox.simpleconfigurator")) { // IPDEBuildConstants.BUNDLE_SIMPLE_CONFIGURATOR // skip simple configurator in order to avoid side-effects continue bundles; } bundleIds.add(bundleId); } StringBuffer bufBundles = new StringBuffer(1024); boolean first = true; for (String bundleId : bundleIds) { if (first) first = false; else bufBundles.append(','); boolean modified = false; if (bundlesToStart.containsKey(bundleId)) { Integer startLevel = bundlesToStart.get(bundleId); String startLevelStr = startLevel != null ? startLevel .toString() : "default"; bufBundles.append(bundleId).append('@').append(startLevelStr) .append(":true"); modified = true; debug("Will start " + bundleId + " at level " + startLevelStr); } if (!modified) bufBundles.append(bundleId); } String output = bufBundles.toString(); return output; } // UTILITIES /** Recursively deletes a directory tree. */ private static void deleteDir(File dir) { File[] files = dir.listFiles(); for (File file : files) { if (file.isDirectory()) deleteDir(file); else file.delete(); } dir.delete(); } /** Loads a properties file. */ private static Properties readProperties(IFile file) throws CoreException { Properties props = new Properties(); InputStream in = null; try { in = file.getContents(); props.load(in); } catch (Exception e) { throw new CoreException(new Status(IStatus.ERROR, SlcIdeUiPlugin.ID, "Cannot read properties file", e)); } finally { if (in != null) try { in.close(); } catch (IOException e) { // silent } } return props; } /** Determines the start levels for the bundles */ private static void computeBundlesToStart( Map bundlesToStart, Properties properties, Integer defaultStartLevel) { // default (and previously, only behaviour) appendBundlesToStart(bundlesToStart, defaultStartLevel, properties.getProperty(OsgiLauncherConstants.ARGEO_OSGI_START, "")); // list argeo.osgi.start.* system properties Iterator keys = properties.keySet().iterator(); final String prefix = OsgiLauncherConstants.ARGEO_OSGI_START + "."; while (keys.hasNext()) { String key = (String) keys.next(); if (key.startsWith(prefix)) { Integer startLevel; String suffix = key.substring(prefix.length()); String[] tokens = suffix.split("\\."); if (tokens.length > 0 && !tokens[0].trim().equals("")) try { // first token is start level startLevel = new Integer(tokens[0]); } catch (NumberFormatException e) { startLevel = defaultStartLevel; } else startLevel = defaultStartLevel; // append bundle names String bundleNames = properties.getProperty(key); appendBundlesToStart(bundlesToStart, startLevel, bundleNames); } } } /** Append a comma-separated list of bundles to the start levels. */ private static void appendBundlesToStart( Map bundlesToStart, Integer startLevel, String str) { if (str == null || str.trim().equals("")) return; String[] bundleNames = str.split(","); for (int i = 0; i < bundleNames.length; i++) { if (bundleNames[i] != null && !bundleNames[i].trim().equals("")) bundlesToStart.put(bundleNames[i], startLevel); } } /* * HACKED UTILITIES */ // Hacked from // org.eclipse.pde.internal.ui.launcher.LaunchArgumentsHelper.getWorkingDirectory(ILaunchConfiguration) private static File getWorkingDirectory(ILaunchConfiguration configuration) throws CoreException { String working; try { working = configuration.getAttribute( IJavaLaunchConfigurationConstants.ATTR_WORKING_DIRECTORY, new File(".").getCanonicalPath()); //$NON-NLS-1$ } catch (IOException e) { working = "${workspace_loc}/../"; //$NON-NLS-1$ } File dir; try { dir = new File(getSubstitutedString(working)); } catch (Exception e) { // the directory was most probably deleted IFile propertiesFile = (IFile) configuration.getMappedResources()[0]; working = findWorkingDirectory(propertiesFile); dir = new File(getSubstitutedString(working)); } if (!dir.exists()) dir.mkdirs(); return dir; } // Hacked from // org.eclipse.pde.internal.ui.launcher.LaunchArgumentsHelper.getSubstitutedString(String) private static String getSubstitutedString(String text) throws CoreException { if (text == null) return ""; //$NON-NLS-1$ IStringVariableManager mgr = VariablesPlugin.getDefault() .getStringVariableManager(); return mgr.performStringSubstitution(text); } /** * Not used anymore, but kept because this routine may be useful in the * future. */ protected void addSelectedProjects(StringBuffer name, ISelection selection, List bundlesToStart) { Assert.isNotNull(selection); Map bundleProjects = new HashMap(); for (IPluginModelBase modelBase : PluginRegistry.getWorkspaceModels()) { IProject bundleProject = modelBase.getUnderlyingResource() .getProject(); bundleProjects.put(bundleProject.getName(), modelBase); } IStructuredSelection sSelection = (IStructuredSelection) selection; for (Iterator it = sSelection.iterator(); it.hasNext();) { Object obj = it.next(); if (obj instanceof IProject) { IProject project = (IProject) obj; if (bundleProjects.containsKey(project.getName())) { IPluginModelBase modelBase = bundleProjects.get(project .getName()); BundleDescription bundleDescription = null; if (modelBase.isFragmentModel()) { BundleDescription[] hosts = modelBase .getBundleDescription().getHost().getHosts(); for (BundleDescription bd : hosts) { if (debug) System.out.println("Host for " + modelBase.getBundleDescription() .getSymbolicName() + ": " + bd.getSymbolicName()); bundleDescription = bd; } } else { bundleDescription = modelBase.getBundleDescription(); } if (bundleDescription != null) { String symbolicName = bundleDescription .getSymbolicName(); String bundleName = bundleDescription.getName(); bundlesToStart.add(symbolicName); if (name.length() > 0) name.append(" "); if (bundleName != null) name.append(bundleName); else name.append(symbolicName); } } } } } static void debug(Object obj) { if (debug) System.out.println(obj); } }