]> git.argeo.org Git - gpl/argeo-slc.git/blob - ide/plugins/org.argeo.slc.ide.ui/src/main/java/org/argeo/slc/ide/ui/launch/osgi/OsgiLaunchHelper.java
f99e1b373cfef05ef31ea8e97325aa756a02f96a
[gpl/argeo-slc.git] / ide / plugins / org.argeo.slc.ide.ui / src / main / java / org / argeo / slc / ide / ui / launch / osgi / OsgiLaunchHelper.java
1 package org.argeo.slc.ide.ui.launch.osgi;
2
3 import java.io.File;
4 import java.io.IOException;
5 import java.io.InputStream;
6 import java.util.ArrayList;
7 import java.util.Date;
8 import java.util.HashMap;
9 import java.util.Iterator;
10 import java.util.List;
11 import java.util.Map;
12 import java.util.Properties;
13 import java.util.Set;
14 import java.util.StringTokenizer;
15 import java.util.TreeMap;
16 import java.util.TreeSet;
17
18 import org.argeo.slc.ide.ui.SlcIdeUiPlugin;
19 import org.eclipse.core.resources.IFile;
20 import org.eclipse.core.resources.IFolder;
21 import org.eclipse.core.resources.IProject;
22 import org.eclipse.core.resources.IResource;
23 import org.eclipse.core.runtime.Assert;
24 import org.eclipse.core.runtime.CoreException;
25 import org.eclipse.core.runtime.IPath;
26 import org.eclipse.core.runtime.IStatus;
27 import org.eclipse.core.runtime.Status;
28 import org.eclipse.core.variables.IStringVariableManager;
29 import org.eclipse.core.variables.VariablesPlugin;
30 import org.eclipse.debug.core.ILaunchConfiguration;
31 import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
32 import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
33 import org.eclipse.jdt.launching.IVMInstall;
34 import org.eclipse.jdt.launching.IVMInstall2;
35 import org.eclipse.jdt.launching.IVMInstallType;
36 import org.eclipse.jdt.launching.JavaRuntime;
37 import org.eclipse.jface.dialogs.ErrorDialog;
38 import org.eclipse.jface.viewers.ISelection;
39 import org.eclipse.jface.viewers.IStructuredSelection;
40 import org.eclipse.osgi.service.resolver.BundleDescription;
41 import org.eclipse.pde.core.plugin.IPluginModelBase;
42 import org.eclipse.pde.core.plugin.PluginRegistry;
43 import org.eclipse.pde.launching.IPDELauncherConstants;
44 import org.eclipse.swt.widgets.Display;
45 import org.eclipse.swt.widgets.Shell;
46
47 /**
48 * Most of the actual logic is concentrated in this class which manipulates
49 * {@link ILaunchConfigurationWorkingCopy}. Static method are used since the
50 * shortcut and launch configuration classes are already extending PDE classes.
51 */
52 @SuppressWarnings("restriction")
53 public class OsgiLaunchHelper implements OsgiLauncherConstants {
54 private static Boolean debug = true;
55
56 private final static String DEFAULT_DATA_DIR = "data";
57 private final static String DEFAULT_EXEC_DIR = "exec";
58 private final static String DEFAULT_VMARGS = "-Xmx256m";
59 private final static String DEFAULT_PROGRAM_ARGS = "-console";
60
61 /** Sets default values on this configuration. */
62 public static void setDefaults(ILaunchConfigurationWorkingCopy wc,
63 Boolean isEclipse) {
64 try {
65 if (isEclipse) {
66 wc.setAttribute(IPDELauncherConstants.USE_DEFAULT, false);
67 wc.setAttribute(IPDELauncherConstants.USE_PRODUCT, false);
68 }
69
70 wc.setAttribute(ATTR_ADD_JVM_PATHS, false);
71 wc.setAttribute(ATTR_ADDITIONAL_VM_ARGS, DEFAULT_VMARGS);
72 wc.setAttribute(ATTR_ADDITIONAL_PROGRAM_ARGS, DEFAULT_PROGRAM_ARGS);
73
74 // Defaults
75 String originalVmArgs = wc.getAttribute(
76 IJavaLaunchConfigurationConstants.ATTR_VM_ARGUMENTS, "");
77 wc.setAttribute(ATTR_DEFAULT_VM_ARGS, originalVmArgs);
78
79 // clear config area by default
80 wc.setAttribute(IPDELauncherConstants.CONFIG_CLEAR_AREA, true);
81 } catch (CoreException e) {
82 Shell shell = Display.getCurrent().getActiveShell();
83 ErrorDialog.openError(shell, "Error",
84 "Cannot execute initalize configuration", e.getStatus());
85 }
86 }
87
88 /** Find the working directory based on this properties file. */
89 public static String findWorkingDirectory(IFile propertiesFile) {
90 try {
91 IProject project = propertiesFile.getProject();
92 IPath parent = propertiesFile.getProjectRelativePath()
93 .removeLastSegments(1);
94 IFolder execFolder = project.getFolder(parent
95 .append(DEFAULT_EXEC_DIR));
96 if (!execFolder.exists())
97 execFolder.create(true, true, null);
98 IFolder launchFolder = project.getFolder(execFolder
99 .getProjectRelativePath().append(
100 extractName(propertiesFile)));
101 if (!launchFolder.exists())
102 launchFolder.create(true, true, null);
103 return "${workspace_loc:"
104 + launchFolder.getFullPath().toString().substring(1) + "}";
105 } catch (Exception e) {
106 e.printStackTrace();
107 throw new RuntimeException("Cannot create working directory", e);
108 }
109 }
110
111 /** Extract the launch configuration name from the properties file. */
112 public static String extractName(IFile propertiesFile) {
113 IPath path = propertiesFile.getFullPath();
114 IPath pathNoExt = path.removeFileExtension();
115 return pathNoExt.segment(pathNoExt.segmentCount() - 1);
116
117 }
118
119 /** Expects properties file to be set as mapped resources */
120 @SuppressWarnings("unchecked")
121 public static void updateLaunchConfiguration(
122 ILaunchConfigurationWorkingCopy wc, Boolean isEclipse) {
123 try {
124 if (debug)
125 debug("##\n## Launch " + wc.getName() + " - " + new Date()
126 + "\n##");
127
128 // Finds the properties file and load it
129 IFile propertiesFile = (IFile) wc.getMappedResources()[0];
130 propertiesFile.refreshLocal(IResource.DEPTH_ONE, null);
131 Properties properties = readProperties(propertiesFile);
132
133 // Extract information from the properties file
134 Map<String, Integer> bundlesToStart = new TreeMap<String, Integer>();
135 Map<String, String> systemPropertiesToAppend = new HashMap<String, String>();
136 String applicationId = interpretProperties(properties,
137 bundlesToStart, systemPropertiesToAppend);
138
139 if (applicationId != null)
140 wc.setAttribute(IPDELauncherConstants.APPLICATION,
141 applicationId);
142 else {
143 if (isEclipse)
144 throw new Exception("No application defined,"
145 + " please set the 'eclipse.application' property"
146 + " in the properties file");
147 }
148
149 // Define directories
150 File workingDir = getWorkingDirectory(wc);
151 File dataDir = new File(workingDir, DEFAULT_DATA_DIR);
152
153 // Update the launch configuration accordingly
154 updateLaunchConfiguration(wc, bundlesToStart,
155 systemPropertiesToAppend, dataDir.getAbsolutePath(),
156 isEclipse);
157
158 if (debug) {
159 Map<String, ?> attrs = new TreeMap<String, Object>(
160 wc.getAttributes());
161 for (String key : attrs.keySet())
162 OsgiLaunchHelper.debug(key + "=" + attrs.get(key));
163 }
164 } catch (Exception e) {
165 e.printStackTrace();
166 Shell shell = SlcIdeUiPlugin.getDefault().getWorkbench()
167 .getActiveWorkbenchWindow().getShell();
168 // Shell shell= Display.getCurrent().getActiveShell();
169 ErrorDialog.openError(shell, "Error",
170 "Cannot prepare launch configuration",
171 new Status(IStatus.ERROR, SlcIdeUiPlugin.ID,
172 e.getMessage(), e));
173 return;
174 }
175 }
176
177 /**
178 * Actually modifies the launch configuration in order to reflect the
179 * current state read from the properties file and the launch configuration
180 * UI.
181 */
182 protected static void updateLaunchConfiguration(
183 ILaunchConfigurationWorkingCopy wc,
184 Map<String, Integer> bundlesToStart,
185 Map<String, String> systemPropertiesToAppend, String dataDir,
186 Boolean isEclipse) throws CoreException {
187 // Convert bundle lists
188 final String targetBundles;
189 final String wkSpaceBundles;
190 if (wc.getAttribute(ATTR_SYNC_BUNDLES, true)) {
191 StringBuffer tBuf = new StringBuffer();
192 for (IPluginModelBase model : PluginRegistry.getExternalModels()) {
193 tBuf.append(model.getBundleDescription().getSymbolicName());
194 tBuf.append(',');
195 }
196 targetBundles = tBuf.toString();
197 StringBuffer wBuf = new StringBuffer();
198 models: for (IPluginModelBase model : PluginRegistry
199 .getWorkspaceModels()) {
200 if (model.getBundleDescription() == null) {
201 System.err.println("No bundle description for " + model);
202 continue models;
203 }
204 wBuf.append(model.getBundleDescription().getSymbolicName());
205 wBuf.append(',');
206 }
207 wkSpaceBundles = wBuf.toString();
208 } else {
209 targetBundles = wc.getAttribute(targetBundlesAttr(isEclipse), "");
210 wkSpaceBundles = wc.getAttribute(workspaceBundlesAttr(isEclipse),
211 "");
212 }
213 wc.setAttribute(targetBundlesAttr(isEclipse),
214 convertBundleList(bundlesToStart, targetBundles));
215
216 wc.setAttribute(workspaceBundlesAttr(isEclipse),
217 convertBundleList(bundlesToStart, wkSpaceBundles));
218
219 // Update other default information
220 wc.setAttribute(IPDELauncherConstants.DEFAULT_AUTO_START, false);
221
222 wc.setAttribute(IPDELauncherConstants.USE_CUSTOM_FEATURES, false);
223 if (!isEclipse)
224 wc.setAttribute(IPDELauncherConstants.USE_DEFAULT, true);
225
226 // VM arguments (system properties)
227 String defaultVmArgs = wc.getAttribute(
228 OsgiLauncherConstants.ATTR_DEFAULT_VM_ARGS, "");
229 StringBuffer vmArgs = new StringBuffer(defaultVmArgs);
230
231 // Data dir system property
232 if (dataDir != null) {
233 addSysProperty(vmArgs, OsgiLauncherConstants.ARGEO_OSGI_DATA_DIR,
234 dataDir);
235 if (isEclipse) {
236 wc.setAttribute(IPDELauncherConstants.LOCATION, dataDir);
237 }
238 }
239
240 // Add locations of JVMs
241 if (wc.getAttribute(ATTR_ADD_JVM_PATHS, false))
242 addVms(vmArgs);
243
244 // Add other system properties
245 for (String key : systemPropertiesToAppend.keySet())
246 addSysProperty(vmArgs, key, systemPropertiesToAppend.get(key));
247
248 vmArgs.append(" ").append(wc.getAttribute(ATTR_ADDITIONAL_VM_ARGS, ""));
249
250 wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_VM_ARGUMENTS,
251 vmArgs.toString());
252
253 // Program arguments
254 StringBuffer progArgs = new StringBuffer("");
255 if (dataDir != null) {
256 progArgs.append("-data ");
257 progArgs.append(surroundSpaces(dataDir));
258
259 if (wc.getAttribute(ATTR_CLEAR_DATA_DIRECTORY, false)) {
260 File dataDirFile = new File(dataDir);
261 deleteDir(dataDirFile);
262 dataDirFile.mkdirs();
263 }
264 }
265 String additionalProgramArgs = wc.getAttribute(
266 OsgiLauncherConstants.ATTR_ADDITIONAL_PROGRAM_ARGS, "");
267 progArgs.append(' ').append(additionalProgramArgs);
268 wc.setAttribute(
269 IJavaLaunchConfigurationConstants.ATTR_PROGRAM_ARGUMENTS,
270 progArgs.toString());
271 }
272
273 /** The launch configuration attribute to use for target bundles */
274 protected static String targetBundlesAttr(Boolean isEclipse) {
275 return isEclipse ? IPDELauncherConstants.SELECTED_TARGET_PLUGINS
276 : IPDELauncherConstants.TARGET_BUNDLES;
277 }
278
279 /** The launch configuration attribute to use for workspace bundles */
280 protected static String workspaceBundlesAttr(Boolean isEclipse) {
281 return isEclipse ? IPDELauncherConstants.SELECTED_WORKSPACE_PLUGINS
282 : IPDELauncherConstants.WORKSPACE_BUNDLES;
283 }
284
285 /**
286 * Interprets special properties and register the others as system
287 * properties to append.
288 *
289 * @return the application id defined by
290 * {@link OsgiLauncherConstants#ECLIPSE_APPLICATION}, or null if not
291 * found
292 */
293 protected static String interpretProperties(Properties properties,
294 Map<String, Integer> bundlesToStart,
295 Map<String, String> systemPropertiesToAppend) {
296 computeBundlesToStart(bundlesToStart, properties, null);
297
298 String applicationId = null;
299 propKeys: for (Object keyObj : properties.keySet()) {
300 String key = keyObj.toString();
301 if (OsgiLauncherConstants.ARGEO_OSGI_START.equals(key))
302 continue propKeys;
303 if (key.startsWith(OsgiLauncherConstants.ARGEO_OSGI_START + "."))
304 continue propKeys;
305 else if (OsgiLauncherConstants.ARGEO_OSGI_BUNDLES.equals(key))
306 continue propKeys;
307 else if (OsgiLauncherConstants.ARGEO_OSGI_LOCATIONS.equals(key))
308 continue propKeys;
309 else if (OsgiLauncherConstants.OSGI_BUNDLES.equals(key))
310 continue propKeys;
311 else if (OsgiLauncherConstants.ECLIPSE_APPLICATION.equals(key))
312 applicationId = properties.getProperty(key);
313 else
314 systemPropertiesToAppend.put(key, properties.getProperty(key));
315 }
316 return applicationId;
317 }
318
319 /** Adds a regular system property. */
320 protected static void addSysProperty(StringBuffer vmArgs, String key,
321 String value) {
322 surroundSpaces(value);
323 String str = "-D" + key + "=" + value;
324 vmArgs.append(' ').append(str);
325 }
326
327 /** Adds JVMS registered in the workspace as special system properties. */
328 protected static void addVms(StringBuffer vmArgs) {
329 addVmSysProperty(vmArgs, "default", JavaRuntime.getDefaultVMInstall());
330 IVMInstallType[] vmTypes = JavaRuntime.getVMInstallTypes();
331 for (IVMInstallType vmType : vmTypes) {
332 for (IVMInstall vmInstall : vmType.getVMInstalls()) {
333 // properties based on name
334 addVmSysProperty(vmArgs, vmInstall.getName(), vmInstall);
335 if (vmInstall instanceof IVMInstall2) {
336 // properties based on version
337 IVMInstall2 vmInstall2 = (IVMInstall2) vmInstall;
338 String version = vmInstall2.getJavaVersion();
339 addVmSysProperty(vmArgs, version, vmInstall);
340
341 List<String> tokens = new ArrayList<String>();
342 StringTokenizer st = new StringTokenizer(version, ".");
343 while (st.hasMoreTokens())
344 tokens.add(st.nextToken());
345 if (tokens.size() >= 2)
346 addVmSysProperty(vmArgs,
347 tokens.get(0) + "." + tokens.get(1), vmInstall);
348 }
349 }
350 }
351
352 }
353
354 /** Adds a special system property pointing to one of the registered JVMs. */
355 protected static void addVmSysProperty(StringBuffer vmArgs, String suffix,
356 IVMInstall vmInstall) {
357 addSysProperty(vmArgs, OsgiLauncherConstants.VMS_PROPERTY_PREFIX + "."
358 + suffix, vmInstall.getInstallLocation().getPath());
359 }
360
361 /** Surround the string with quotes if it contains spaces. */
362 protected static String surroundSpaces(String str) {
363 if (str.indexOf(' ') >= 0)
364 return '\"' + str + '\"';
365 else
366 return str;
367 }
368
369 /**
370 * Reformat the bundle list in order to reflect which bundles have to be
371 * started.
372 */
373 protected static String convertBundleList(
374 Map<String, Integer> bundlesToStart, String original) {
375 StringTokenizer stComa = new StringTokenizer(original, ",");
376 // sort by bundle symbolic name
377 Set<String> bundleIds = new TreeSet<String>();
378 bundles: while (stComa.hasMoreTokens()) {
379
380 String bundleId = stComa.nextToken();
381 if (bundleId.indexOf('*') >= 0)
382 throw new RuntimeException(
383 "Bundle id "
384 + bundleId
385 + " not properly formatted, clean your workspace projects");
386
387 int indexAt = bundleId.indexOf('@');
388 if (indexAt >= 0) {
389 bundleId = bundleId.substring(0, indexAt);
390 }
391
392 // We can now rely on bundleId value
393
394 if (bundleId.endsWith(".source")) {
395 // debug("Skip source bundle " + bundleId);
396 continue bundles;
397 } else if (bundleId
398 .equals("org.eclipse.equinox.simpleconfigurator")) {
399 // IPDEBuildConstants.BUNDLE_SIMPLE_CONFIGURATOR
400 // skip simple configurator in order to avoid side-effects
401 continue bundles;
402 }
403 bundleIds.add(bundleId);
404 }
405
406 StringBuffer bufBundles = new StringBuffer(1024);
407 boolean first = true;
408 for (String bundleId : bundleIds) {
409 if (first)
410 first = false;
411 else
412 bufBundles.append(',');
413 boolean modified = false;
414 if (bundlesToStart.containsKey(bundleId)) {
415 Integer startLevel = bundlesToStart.get(bundleId);
416 String startLevelStr = startLevel != null ? startLevel
417 .toString() : "default";
418 bufBundles.append(bundleId).append('@').append(startLevelStr)
419 .append(":true");
420 modified = true;
421 debug("Will start " + bundleId + " at level " + startLevelStr);
422 }
423
424 if (!modified)
425 bufBundles.append(bundleId);
426
427 }
428 String output = bufBundles.toString();
429 return output;
430 }
431
432 // UTILITIES
433 /** Recursively deletes a directory tree. */
434 private static void deleteDir(File dir) {
435 File[] files = dir.listFiles();
436 for (File file : files) {
437 if (file.isDirectory())
438 deleteDir(file);
439 else
440 file.delete();
441 }
442 dir.delete();
443 }
444
445 /** Loads a properties file. */
446 private static Properties readProperties(IFile file) throws CoreException {
447 Properties props = new Properties();
448
449 InputStream in = null;
450 try {
451 in = file.getContents();
452 props.load(in);
453 } catch (Exception e) {
454 throw new CoreException(new Status(IStatus.ERROR,
455 SlcIdeUiPlugin.ID, "Cannot read properties file", e));
456 } finally {
457 if (in != null)
458 try {
459 in.close();
460 } catch (IOException e) {
461 // silent
462 }
463 }
464 return props;
465 }
466
467 /** Determines the start levels for the bundles */
468 private static void computeBundlesToStart(
469 Map<String, Integer> bundlesToStart, Properties properties,
470 Integer defaultStartLevel) {
471
472 // default (and previously, only behaviour)
473 appendBundlesToStart(bundlesToStart, defaultStartLevel,
474 properties.getProperty(OsgiLauncherConstants.ARGEO_OSGI_START,
475 ""));
476
477 // list argeo.osgi.start.* system properties
478 Iterator<Object> keys = properties.keySet().iterator();
479 final String prefix = OsgiLauncherConstants.ARGEO_OSGI_START + ".";
480 while (keys.hasNext()) {
481 String key = (String) keys.next();
482 if (key.startsWith(prefix)) {
483 Integer startLevel;
484 String suffix = key.substring(prefix.length());
485 String[] tokens = suffix.split("\\.");
486 if (tokens.length > 0 && !tokens[0].trim().equals(""))
487 try {
488 // first token is start level
489 startLevel = new Integer(tokens[0]);
490 } catch (NumberFormatException e) {
491 startLevel = defaultStartLevel;
492 }
493 else
494 startLevel = defaultStartLevel;
495
496 // append bundle names
497 String bundleNames = properties.getProperty(key);
498 appendBundlesToStart(bundlesToStart, startLevel, bundleNames);
499 }
500 }
501 }
502
503 /** Append a comma-separated list of bundles to the start levels. */
504 private static void appendBundlesToStart(
505 Map<String, Integer> bundlesToStart, Integer startLevel, String str) {
506 if (str == null || str.trim().equals(""))
507 return;
508
509 String[] bundleNames = str.split(",");
510 for (int i = 0; i < bundleNames.length; i++) {
511 if (bundleNames[i] != null && !bundleNames[i].trim().equals(""))
512 bundlesToStart.put(bundleNames[i], startLevel);
513 }
514 }
515
516 /*
517 * HACKED UTILITIES
518 */
519 // Hacked from
520 // org.eclipse.pde.internal.ui.launcher.LaunchArgumentsHelper.getWorkingDirectory(ILaunchConfiguration)
521 private static File getWorkingDirectory(ILaunchConfiguration configuration)
522 throws CoreException {
523 String working;
524 try {
525 working = configuration.getAttribute(
526 IJavaLaunchConfigurationConstants.ATTR_WORKING_DIRECTORY,
527 new File(".").getCanonicalPath()); //$NON-NLS-1$
528 } catch (IOException e) {
529 working = "${workspace_loc}/../"; //$NON-NLS-1$
530 }
531 File dir;
532 try {
533 dir = new File(getSubstitutedString(working));
534 } catch (Exception e) {
535 // the directory was most probably deleted
536 IFile propertiesFile = (IFile) configuration.getMappedResources()[0];
537 working = findWorkingDirectory(propertiesFile);
538 dir = new File(getSubstitutedString(working));
539 }
540 if (!dir.exists())
541 dir.mkdirs();
542 return dir;
543 }
544
545 // Hacked from
546 // org.eclipse.pde.internal.ui.launcher.LaunchArgumentsHelper.getSubstitutedString(String)
547 private static String getSubstitutedString(String text)
548 throws CoreException {
549 if (text == null)
550 return ""; //$NON-NLS-1$
551 IStringVariableManager mgr = VariablesPlugin.getDefault()
552 .getStringVariableManager();
553 return mgr.performStringSubstitution(text);
554 }
555
556 /**
557 * Not used anymore, but kept because this routine may be useful in the
558 * future.
559 */
560 protected void addSelectedProjects(StringBuffer name, ISelection selection,
561 List<String> bundlesToStart) {
562 Assert.isNotNull(selection);
563
564 Map<String, IPluginModelBase> bundleProjects = new HashMap<String, IPluginModelBase>();
565 for (IPluginModelBase modelBase : PluginRegistry.getWorkspaceModels()) {
566 IProject bundleProject = modelBase.getUnderlyingResource()
567 .getProject();
568 bundleProjects.put(bundleProject.getName(), modelBase);
569 }
570
571 IStructuredSelection sSelection = (IStructuredSelection) selection;
572 for (Iterator<?> it = sSelection.iterator(); it.hasNext();) {
573 Object obj = it.next();
574 if (obj instanceof IProject) {
575 IProject project = (IProject) obj;
576 if (bundleProjects.containsKey(project.getName())) {
577 IPluginModelBase modelBase = bundleProjects.get(project
578 .getName());
579
580 BundleDescription bundleDescription = null;
581 if (modelBase.isFragmentModel()) {
582 BundleDescription[] hosts = modelBase
583 .getBundleDescription().getHost().getHosts();
584 for (BundleDescription bd : hosts) {
585 if (debug)
586 System.out.println("Host for "
587 + modelBase.getBundleDescription()
588 .getSymbolicName() + ": "
589 + bd.getSymbolicName());
590 bundleDescription = bd;
591 }
592 } else {
593 bundleDescription = modelBase.getBundleDescription();
594 }
595
596 if (bundleDescription != null) {
597 String symbolicName = bundleDescription
598 .getSymbolicName();
599 String bundleName = bundleDescription.getName();
600
601 bundlesToStart.add(symbolicName);
602
603 if (name.length() > 0)
604 name.append(" ");
605 if (bundleName != null)
606 name.append(bundleName);
607 else
608 name.append(symbolicName);
609 }
610 }
611 }
612 }
613 }
614
615 static void debug(Object obj) {
616 if (debug)
617 System.out.println(obj);
618 }
619
620 }