1 package org.simantics.team.internal;
\r
4 import java.io.FileWriter;
\r
5 import java.io.IOException;
\r
6 import java.io.InputStream;
\r
7 import java.io.PrintStream;
\r
8 import java.net.URLDecoder;
\r
9 import java.util.ArrayList;
\r
10 import java.util.Arrays;
\r
11 import java.util.HashSet;
\r
12 import java.util.LinkedList;
\r
13 import java.util.List;
\r
14 import java.util.Properties;
\r
15 import java.util.Set;
\r
17 import org.eclipse.core.runtime.Platform;
\r
18 import org.eclipse.equinox.frameworkadmin.BundleInfo;
\r
19 import org.eclipse.equinox.internal.frameworkadmin.equinox.EquinoxConstants;
\r
20 import org.eclipse.equinox.internal.frameworkadmin.utils.Utils;
\r
21 import org.eclipse.equinox.internal.provisional.frameworkadmin.ConfigData;
\r
22 import org.eclipse.equinox.internal.provisional.frameworkadmin.FrameworkAdmin;
\r
23 import org.eclipse.equinox.internal.provisional.frameworkadmin.FrameworkAdminRuntimeException;
\r
24 import org.eclipse.equinox.internal.provisional.frameworkadmin.LauncherData;
\r
25 import org.eclipse.equinox.internal.provisional.frameworkadmin.Manipulator;
\r
26 import org.osgi.framework.Bundle;
\r
27 import org.osgi.framework.BundleContext;
\r
28 import org.osgi.framework.BundleException;
\r
29 import org.osgi.framework.FrameworkUtil;
\r
30 import org.osgi.framework.InvalidSyntaxException;
\r
31 import org.osgi.framework.ServiceReference;
\r
32 import org.simantics.application.db.SocketUtils;
\r
33 import org.simantics.databoard.binding.error.BindingConstructionException;
\r
34 import org.simantics.db.Resource;
\r
35 import org.simantics.db.Session;
\r
36 import org.simantics.db.exception.DatabaseException;
\r
37 import org.simantics.utils.FileUtils;
\r
38 import org.simantics.utils.strings.EString;
\r
40 @SuppressWarnings("restriction")
\r
41 public final class StagingLauncher {
\r
42 private static final boolean DEBUG = true;
\r
43 private static final boolean DEBUG_EXEC = true;
\r
44 private static final boolean REMOTE_DEBUG_DISABLED = true;
\r
45 public static StagingResult launch(
\r
46 final Config stagingConfig,
\r
47 String serverAddress,
\r
48 String targetResourceId)
\r
49 throws InvalidSyntaxException, IllegalArgumentException,
\r
50 FrameworkAdminRuntimeException, IOException, BindingConstructionException, DatabaseException {
\r
51 Bundle dsBundle = Platform.getBundle("org.eclipse.equinox.ds");
\r
53 dsBundle.start(/*Bundle.START_TRANSIENT*/);
\r
55 System.out.println("state="+dsBundle.getState());
\r
56 } catch (BundleException ex) {
\r
57 throw new StagingException("Could not start org.eclipse.equinox.ds.", ex);
\r
59 Bundle faBundle = FrameworkUtil.getBundle(FrameworkAdmin.class);
\r
60 if (null == faBundle)
\r
61 throw new StagingException("Bundle for FrameworkAdmin not available.");
\r
62 BundleContext faContext = faBundle.getBundleContext();
\r
63 if (null == faContext)
\r
64 throw new StagingException("Context for FrameworkAdmin not available.");
\r
65 ServiceReference<FrameworkAdmin> ref = faContext.getServiceReference(FrameworkAdmin.class);
\r
67 throw new StagingException("Reference for FrameworkAdmin not available.");
\r
68 FrameworkAdmin admin = (FrameworkAdmin)faContext.getService(ref);
\r
70 throw new StagingException("FrameworkAdmin not available.");
\r
72 Manipulator rmanip = admin.getRunningManipulator();
\r
74 throw new StagingException("No FrameworkAdmin Manipulator available for the currently running environment.");
\r
76 System.out.println("FrameworkAdmin Manipulator of the running environment:\n" + rmanip);
\r
77 Properties system = System.getProperties();
\r
78 ConfigData rcd = rmanip.getConfigData();
\r
79 LauncherData rld = rmanip.getLauncherData();
\r
80 Manipulator manip = admin.getManipulator();
\r
81 manip.setConfigData(rcd);
\r
82 manip.setLauncherData(rld);
\r
83 ConfigData cd = manip.getConfigData();
\r
84 LauncherData ld = manip.getLauncherData();
\r
85 Properties config = new Properties();
\r
86 StringBuilder osgiBundles = new StringBuilder(1024);
\r
87 StringBuilder bundlesInfo = new StringBuilder(1024);
\r
88 bundlesInfo.append("#version=1\n");
\r
89 for (BundleInfo bi : cd.getBundles()) {
\r
90 boolean started = isMarkedAsStarted(bi);
\r
92 .append(bi.getSymbolicName())
\r
94 .append(bi.getVersion())
\r
96 //.append(bi.getLocation().toString())
\r
97 .append(URLDecoder.decode(bi.getLocation().toString(), "UTF-8"))
\r
99 .append(bi.getStartLevel())
\r
104 System.out.println("bundle: " + bi.getSymbolicName() + "\n\t" + bi.getLocation().toString() + "\n\t" + started);
\r
106 System.out.println("IS STARTED: bundle: " + bi.getSymbolicName() + "\n\t" + bi.getLocation().toString());
\r
107 if (!started && bi.isMarkedAsStarted())
\r
108 System.out.println("NOT STARTED, BUT WAS MARKED AS STARTED: bundle: " + bi.getSymbolicName() + "\n\t" + bi.getLocation().toString());
\r
110 if (isStartUpBundle(bi)) {
\r
111 if (osgiBundles.length() > 0) {
\r
112 osgiBundles.append(",");
\r
115 .append("reference:")
\r
116 .append(URLDecoder.decode(bi.getLocation().toString(), "UTF-8"))
\r
118 .append(bi.getStartLevel())
\r
122 //File cwd = new File(system.getProperty("user.dir"));
\r
123 File javaHome = new File(system.getProperty("java.home"));
\r
124 String installArea = system.getProperty("osgi.install.area");
\r
125 config.setProperty("eclipse.application", "org.simantics.workbench.application");
\r
126 config.setProperty("eclipse.product", "org.simantics.devs3.ui.product");
\r
127 //config.setProperty("eclipse.consoleLog", "");
\r
128 config.setProperty("org.eclipse.update.reconcile", "false");
\r
129 config.setProperty("osgi.bundles", osgiBundles.toString());
\r
130 config.setProperty("osgi.bundles.defaultStartLevel", "4");
\r
131 //config.setProperty("osgi.clean", "true");
\r
132 //config.setProperty("osgi.configuration.area", "@default");
\r
133 config.setProperty("osgi.configuration.cascaded", "false");
\r
134 //config.setProperty("osgi.console", "");
\r
135 //config.setProperty("osgi.debug", "");
\r
136 config.setProperty("osgi.framework", URLDecoder.decode(ld.getFwJar().toURI().toString(), "UTF-8"));
\r
137 //config.setProperty("osgi.noShutdown", "");
\r
138 config.setProperty("osgi.install.area", installArea);
\r
139 config.setProperty("osgi.instance.area", stagingConfig.workspaceRoot.getAbsolutePath());
\r
140 // cd.setProperty("osgi.instance.area", "@none");
\r
141 config.setProperty("osgi.user.area", "@none");
\r
142 // Eclipse 3.6, not sure what this is for but modern apps have it.
\r
143 config.setProperty("equinox.use.ds", "true");
\r
144 // Ignore INFO level log messages
\r
145 config.setProperty("eclipse.log.level", "WARNING");
\r
146 File configDir = new File(stagingConfig.workspaceRoot, "configuration");
\r
147 File simpleConfiguratorDir = new File(configDir, "org.eclipse.equinox.simpleconfigurator");
\r
148 simpleConfiguratorDir.mkdirs();
\r
149 File bundlesInfoFile = new File(simpleConfiguratorDir, "bundles.info");
\r
150 config.setProperty("org.eclipse.equinox.simpleconfigurator.configUrl", URLDecoder.decode(bundlesInfoFile.toURI().toString(), "UTF-8"));
\r
151 File logFile = new File(stagingConfig.workspaceRoot, "db-client.log");
\r
152 writeProperties(config, new File(configDir, "config.ini"), "This configuration file was written by: " + StagingLauncher.class.getCanonicalName());
\r
153 writeFile(bundlesInfo.toString(), bundlesInfoFile);
\r
155 ld.setJvm(new File(new File(javaHome, "bin"), "java"));
\r
156 ld.setFwConfigLocation(configDir);
\r
157 ld.setFwPersistentDataLocation(configDir, false);
\r
158 ld.setLauncher(null);
\r
159 int maxHeap = 768; /*prefs.getInt(Activator.PLUGIN_ID, ImportPreferences.PREF_IMPORT_PROCESS_MAX_HEAP,
\r
160 ImportPreferences.getDefaultImportProcessMaxHeap(), preferenceScopes);
\r
161 // Just a safety if preferences have not been properly initialized for some reason.
\r
163 maxHeap = ImportPreferences.getDefaultImportProcessMaxHeap();*/
\r
164 ld.addJvmArg("-Xmx" + maxHeap + "m");
\r
165 ld.addJvmArg("-Xms" + maxHeap + "m");
\r
166 // Enable assertions to have faster failure in db client routines with improper input.
\r
167 ld.addJvmArg("-ea");
\r
168 // For supporting OSGi dev mode (launched from IDE)
\r
169 if (Platform.inDevelopmentMode()) {
\r
170 // TODO: %osgi.dev is not escaped, it can contain whitespace
\r
171 // but this doesn't seem to matter ?
\r
172 ld.addJvmArg("-Dosgi.dev=" + system.getProperty("osgi.dev"));
\r
174 ld.addJvmArg("-Dosgi.arch=" + rcd.getProperty("osgi.arch"));
\r
175 ld.addJvmArg("-Dosgi.os=" + rcd.getProperty("osgi.os"));
\r
176 ld.addJvmArg("-Dosgi.ws=" + rcd.getProperty("osgi.ws"));
\r
177 ld.addJvmArg("-Dosgi.nl=" + rcd.getProperty("osgi.nl"));
\r
179 // WORKAROUND for a problem in org.eclipse.ecf fragment org.eclipse.ecf.ssl.
\r
180 // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=316500
\r
181 // Either one of these works, THESE:
\r
182 //ld.addJvmArg("-Dosgi.java.profile.bootdelegation=override");
\r
183 //ld.addJvmArg("-Dorg.osgi.framework.bootdelegation=sun.*, com.sun.*, javax.*");
\r
185 ld.addJvmArg("-Dosgi.compatibility.bootdelegation=true");
\r
187 // Enable remote debugging when in osgi dev mode.
\r
188 if (Platform.inDevelopmentMode())
\r
189 if (!REMOTE_DEBUG_DISABLED) {
\r
190 ld.addJvmArg("-Xdebug");
\r
191 ld.addJvmArg("-Xnoagent");
\r
192 int port = SocketUtils.getFreeEphemeralPort();
\r
193 ld.addJvmArg("-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=" + port);
\r
196 ld.addJvmArg("-D" + Constants.PROP_DUMP_PROPERTIES);
\r
197 ld.addJvmArg("-D" + Constants.PROP_LOGFILE + "=" + logFile.toURI().toString());
\r
198 String titleArgument = System.getProperty(Constants.PROP_WINDOW_TITLE);
\r
199 if (null != titleArgument && !titleArgument.equals(""))
\r
200 titleArgument = stagingConfig.titlePrefix + " " + titleArgument;
\r
202 titleArgument = stagingConfig.titlePrefix;
\r
203 ld.addJvmArg("-D" + Constants.PROP_WINDOW_TITLE + "=" + titleArgument);
\r
204 if (null != stagingConfig.teamFolder)
\r
205 ld.addJvmArg("-D" + Constants.PROP_TEAM_FOLDER + "=" + stagingConfig.teamFolder.getAbsolutePath());
\r
206 ld.addJvmArg("-D" + Constants.PROP_WORKSPACE_ROOT + "=" + stagingConfig.workspaceRoot.toURI().toString());
\r
208 System.out.println("JVM ARGS: " + Arrays.toString(ld.getJvmArgs()));
\r
210 // Using this means that
\r
211 // * config.ini must not be written self
\r
212 // * parent program bundles.info can be reused (this could be done in any case)
\r
213 // * running platform configuration must not be copied completely, only selected parts of
\r
214 //manip.save(false);
\r
216 // #5849 workaround
\r
217 // XSupport xs = stagingConfig.session.getService(XSupport.class);
\r
218 // xs.initClusterIdMap(stagingConfig.workspaceRoot.getAbsolutePath());
\r
219 if (null != stagingConfig.clusterMapFolder) {
\r
220 String file = "clusterIdMap.dat";
\r
221 File f = new File(stagingConfig.clusterMapFolder, file);
\r
222 File tFolder = new File(stagingConfig.workspaceRoot, ".metadata/.plugins/org.simantics.db.procore");
\r
224 FileUtils.copyFile(f, new File(tFolder, file));
\r
225 String file2 = "nextId.dat";
\r
226 File f2 = new File(stagingConfig.clusterMapFolder, file2);
\r
227 File tFolder2 = new File(stagingConfig.workspaceRoot, "db");
\r
228 FileUtils.copyFile(f2, new File(tFolder2, file2));
\r
231 System.out.println("LAUNCHING\n" + manip);
\r
234 LauncherData launcherData = ld;
\r
236 System.out.println("Framework JAR: " + ld.getFwJar().toURI().toString());
\r
237 Utils.checkAbsoluteFile(launcherData.getFwJar(), "fwJar"); //$NON-NLS-1$
\r
238 File cwd = stagingConfig.workspaceRoot;
\r
239 Utils.checkAbsoluteDir(cwd, "cwd"); //$NON-NLS-1$
\r
241 List<String> cmdList = new LinkedList<String>();
\r
242 if (launcherData.getJvm() != null)
\r
243 cmdList.add(launcherData.getJvm().getAbsolutePath());
\r
245 cmdList.add("java"); //$NON-NLS-1$
\r
247 if (launcherData.getJvmArgs() != null)
\r
248 for (int i = 0; i < launcherData.getJvmArgs().length; i++)
\r
249 cmdList.add(launcherData.getJvmArgs()[i]);
\r
251 cmdList.add("-jar"); //$NON-NLS-1$
\r
252 cmdList.add("\"" + launcherData.getFwJar().getAbsolutePath() + "\"");
\r
254 //EquinoxManipulatorImpl.checkConsistencyOfFwConfigLocAndFwPersistentDataLoc(launcherData);
\r
255 cmdList.add(EquinoxConstants.OPTION_CONFIGURATION);
\r
256 cmdList.add("\"" + launcherData.getFwPersistentDataLocation().getAbsolutePath() + "\"");
\r
258 cmdList.add("-data");
\r
259 cmdList.add(stagingConfig.workspaceRoot.getAbsolutePath());
\r
261 if (launcherData.isClean())
\r
262 cmdList.add(EquinoxConstants.OPTION_CLEAN);
\r
264 String[] cmdarray = cmdList.toArray(new String[cmdList.size()]);
\r
267 System.out.println("Launching import, CWD=" + cwd + "\n\t" + EString.implode(cmdarray, "\n\t"));
\r
269 process = Runtime.getRuntime().exec(cmdarray, null, cwd);
\r
272 long startTime = System.nanoTime();
\r
273 int exitValue = Integer.MIN_VALUE;
\r
274 InputStream is = process.getInputStream();
\r
275 InputStream es = process.getErrorStream();
\r
278 long endTime = System.nanoTime();
\r
279 //System.out.println("Checking for process exit value");
\r
280 exitValue = process.exitValue();
\r
281 System.out.println("finished in " + (endTime-startTime)*1e-6 + "ms");
\r
282 System.out.println("exit value: " + exitValue);
\r
283 } catch (IllegalThreadStateException e) {
\r
285 int n = is.available();
\r
287 byte[] bytes = new byte[n];
\r
288 int nr = is.read(bytes);
\r
290 System.out.println("DEBUG: STDOUT:" + org.simantics.team.Utils.bytesToStringASCII(bytes, 0, nr));
\r
292 n = es.available();
\r
294 byte[] bytes = new byte[n];
\r
295 int nr = es.read(bytes);
\r
297 System.out.println("DEBUG: STDERR:" + org.simantics.team.Utils.bytesToStringASCII(bytes, 0, nr));
\r
300 } catch (InterruptedException e1) {
\r
301 e1.printStackTrace();
\r
305 String ins = FileUtils.getContents(is);
\r
306 if (!ins.isEmpty())
\r
307 System.out.println("--- STDOUT ---\n" + ins);
\r
308 String errs = FileUtils.getContents(es);
\r
309 if (!errs.isEmpty())
\r
310 System.out.println("--- STDERR ---\n" + errs);
\r
314 // #5849 workaround
\r
315 // xs.mergeClusterIdMap(stagingConfig.workspaceRoot.getAbsolutePath());
\r
317 return new StagingResult(exitValue, logFile, null);
\r
319 faContext.ungetService(ref);
\r
323 private static Set<String> startUpBundles = new HashSet<String>();
\r
325 //startUpBundles.add("org.eclipse.equinox.ds");
\r
326 startUpBundles.add("org.eclipse.equinox.simpleconfigurator");
\r
329 private static Set<String> startedBundles = new HashSet<String>();
\r
331 startedBundles.add("org.eclipse.core.runtime");
\r
334 private static boolean isStartUpBundle(BundleInfo bi) {
\r
335 return startUpBundles.contains(bi.getSymbolicName());
\r
338 private static boolean isMarkedAsStarted(BundleInfo bi) {
\r
339 // Prevent unnecessary bundles from being
\r
340 // activated by only marking as started the plug-ins that
\r
341 // are vital to the environment, which always have lower
\r
342 // start level than the default 4.
\r
343 return bi.isMarkedAsStarted() && (bi.getStartLevel() < 4 || startedBundles.contains(bi.getSymbolicName()));
\r
346 private static void writeProperties(Properties properties, File target, String comment) throws IOException {
\r
347 FileWriter writer = new FileWriter(target);
\r
349 properties.store(writer, comment);
\r
355 private static void writeFile(String string, File target) throws IOException {
\r
356 FileWriter writer = new FileWriter(target);
\r
358 writer.write(string);
\r
363 static public final class StagingResult {
\r
365 private final int exitValue;
\r
367 private final File logFile;
\r
369 private final String messageLog;
\r
371 public StagingResult(String message) {
\r
372 this.exitValue = Integer.MIN_VALUE;
\r
373 this.logFile = null;
\r
374 this.messageLog = message;
\r
376 public StagingResult(int exitValue, File logFile, String messageLog) {
\r
377 this.exitValue = exitValue;
\r
378 this.logFile = logFile;
\r
379 this.messageLog = messageLog;
\r
382 public int getExitValue() {
\r
386 public File getLogFile() {
\r
390 public String getMessageLog() {
\r
395 public static final class Config {
\r
396 public final Session session;
\r
397 public final Resource targetLibrary;
\r
398 public final File workspaceRoot;
\r
399 public final File clusterMapFolder;
\r
400 public final File teamFolder;
\r
401 public final PrintStream out;
\r
403 public Resource createdModel;
\r
404 public Resource createdState;
\r
405 public List<String> messages = new ArrayList<String>();
\r
406 public String titlePrefix = "Staging";
\r
408 public Config(Session session, Resource library, File workspaceRoot, File clusterMapFolder)
\r
409 throws DatabaseException {
\r
410 this(session, library, workspaceRoot, clusterMapFolder, null);
\r
413 public Config( Session session, Resource library, File workspaceRoot, File clusterMapFolder, PrintStream out)
\r
414 throws DatabaseException {
\r
415 this.session = session;
\r
416 this.targetLibrary = library;
\r
417 this.workspaceRoot = workspaceRoot;
\r
418 this.clusterMapFolder = clusterMapFolder;
\r
419 this.teamFolder = org.simantics.team.Utils.getTeamFolder();
\r
423 static final class Constants {
\r
426 * Tells the launched application to dump all its system properties into the
\r
427 * standard output / log.
\r
429 public static final String PROP_DUMP_PROPERTIES = "dump.properties";
\r
432 * Controls where the launched application log is written. Specified as an URI.
\r
434 public static final String PROP_LOGFILE = "staging.logfile";
\r
437 * Prefix for the window title.
\r
439 public static final String PROP_WINDOW_TITLE = "staging.window.title";
\r
442 * Tells the launched application where its team data comes from.
\r
444 public static final String PROP_TEAM_FOLDER = "staging.team.folder";
\r
447 * Workspace root directory of the workbench process that launched the
\r
450 public static final String PROP_WORKSPACE_ROOT = "import.workspace.root";
\r