--- /dev/null
+package org.simantics.team.internal;\r
+\r
+import java.io.File;\r
+import java.io.FileWriter;\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.io.PrintStream;\r
+import java.net.URLDecoder;\r
+import java.util.ArrayList;\r
+import java.util.Arrays;\r
+import java.util.HashSet;\r
+import java.util.LinkedList;\r
+import java.util.List;\r
+import java.util.Properties;\r
+import java.util.Set;\r
+\r
+import org.eclipse.core.runtime.Platform;\r
+import org.eclipse.equinox.frameworkadmin.BundleInfo;\r
+import org.eclipse.equinox.internal.frameworkadmin.equinox.EquinoxConstants;\r
+import org.eclipse.equinox.internal.frameworkadmin.utils.Utils;\r
+import org.eclipse.equinox.internal.provisional.frameworkadmin.ConfigData;\r
+import org.eclipse.equinox.internal.provisional.frameworkadmin.FrameworkAdmin;\r
+import org.eclipse.equinox.internal.provisional.frameworkadmin.FrameworkAdminRuntimeException;\r
+import org.eclipse.equinox.internal.provisional.frameworkadmin.LauncherData;\r
+import org.eclipse.equinox.internal.provisional.frameworkadmin.Manipulator;\r
+import org.osgi.framework.Bundle;\r
+import org.osgi.framework.BundleContext;\r
+import org.osgi.framework.BundleException;\r
+import org.osgi.framework.FrameworkUtil;\r
+import org.osgi.framework.InvalidSyntaxException;\r
+import org.osgi.framework.ServiceReference;\r
+import org.simantics.application.db.SocketUtils;\r
+import org.simantics.databoard.binding.error.BindingConstructionException;\r
+import org.simantics.db.Resource;\r
+import org.simantics.db.Session;\r
+import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.utils.FileUtils;\r
+import org.simantics.utils.strings.EString;\r
+\r
+@SuppressWarnings("restriction")\r
+public final class StagingLauncher {\r
+ private static final boolean DEBUG = true;\r
+ private static final boolean DEBUG_EXEC = true;\r
+ private static final boolean REMOTE_DEBUG_DISABLED = true;\r
+ public static StagingResult launch(\r
+ final Config stagingConfig,\r
+ String serverAddress,\r
+ String targetResourceId)\r
+ throws InvalidSyntaxException, IllegalArgumentException,\r
+ FrameworkAdminRuntimeException, IOException, BindingConstructionException, DatabaseException {\r
+ Bundle dsBundle = Platform.getBundle("org.eclipse.equinox.ds");\r
+ try {\r
+ dsBundle.start(/*Bundle.START_TRANSIENT*/);\r
+ if (DEBUG)\r
+ System.out.println("state="+dsBundle.getState());\r
+ } catch (BundleException ex) {\r
+ throw new StagingException("Could not start org.eclipse.equinox.ds.", ex);\r
+ }\r
+ Bundle faBundle = FrameworkUtil.getBundle(FrameworkAdmin.class);\r
+ if (null == faBundle)\r
+ throw new StagingException("Bundle for FrameworkAdmin not available.");\r
+ BundleContext faContext = faBundle.getBundleContext();\r
+ if (null == faContext)\r
+ throw new StagingException("Context for FrameworkAdmin not available.");\r
+ ServiceReference<FrameworkAdmin> ref = faContext.getServiceReference(FrameworkAdmin.class);\r
+ if (ref == null)\r
+ throw new StagingException("Reference for FrameworkAdmin not available.");\r
+ FrameworkAdmin admin = (FrameworkAdmin)faContext.getService(ref);\r
+ if (null == admin)\r
+ throw new StagingException("FrameworkAdmin not available.");\r
+ try {\r
+ Manipulator rmanip = admin.getRunningManipulator();\r
+ if (rmanip == null)\r
+ throw new StagingException("No FrameworkAdmin Manipulator available for the currently running environment.");\r
+ if (DEBUG)\r
+ System.out.println("FrameworkAdmin Manipulator of the running environment:\n" + rmanip);\r
+ Properties system = System.getProperties();\r
+ ConfigData rcd = rmanip.getConfigData();\r
+ LauncherData rld = rmanip.getLauncherData();\r
+ Manipulator manip = admin.getManipulator();\r
+ manip.setConfigData(rcd);\r
+ manip.setLauncherData(rld);\r
+ ConfigData cd = manip.getConfigData();\r
+ LauncherData ld = manip.getLauncherData();\r
+ Properties config = new Properties();\r
+ StringBuilder osgiBundles = new StringBuilder(1024);\r
+ StringBuilder bundlesInfo = new StringBuilder(1024);\r
+ bundlesInfo.append("#version=1\n");\r
+ for (BundleInfo bi : cd.getBundles()) {\r
+ boolean started = isMarkedAsStarted(bi);\r
+ bundlesInfo\r
+ .append(bi.getSymbolicName())\r
+ .append(",")\r
+ .append(bi.getVersion())\r
+ .append(",")\r
+ //.append(bi.getLocation().toString())\r
+ .append(URLDecoder.decode(bi.getLocation().toString(), "UTF-8"))\r
+ .append(",")\r
+ .append(bi.getStartLevel())\r
+ .append(",")\r
+ .append(started)\r
+ .append("\n");\r
+ if (DEBUG) {\r
+ System.out.println("bundle: " + bi.getSymbolicName() + "\n\t" + bi.getLocation().toString() + "\n\t" + started);\r
+ if (started)\r
+ System.out.println("IS STARTED: bundle: " + bi.getSymbolicName() + "\n\t" + bi.getLocation().toString());\r
+ if (!started && bi.isMarkedAsStarted())\r
+ System.out.println("NOT STARTED, BUT WAS MARKED AS STARTED: bundle: " + bi.getSymbolicName() + "\n\t" + bi.getLocation().toString());\r
+ }\r
+ if (isStartUpBundle(bi)) {\r
+ if (osgiBundles.length() > 0) {\r
+ osgiBundles.append(",");\r
+ }\r
+ osgiBundles\r
+ .append("reference:")\r
+ .append(URLDecoder.decode(bi.getLocation().toString(), "UTF-8"))\r
+ .append("@")\r
+ .append(bi.getStartLevel())\r
+ .append(":start");\r
+ }\r
+ }\r
+ //File cwd = new File(system.getProperty("user.dir"));\r
+ File javaHome = new File(system.getProperty("java.home"));\r
+ String installArea = system.getProperty("osgi.install.area");\r
+ config.setProperty("eclipse.application", "org.simantics.workbench.application");\r
+ config.setProperty("eclipse.product", "org.simantics.devs3.ui.product");\r
+ //config.setProperty("eclipse.consoleLog", "");\r
+ config.setProperty("org.eclipse.update.reconcile", "false");\r
+ config.setProperty("osgi.bundles", osgiBundles.toString());\r
+ config.setProperty("osgi.bundles.defaultStartLevel", "4");\r
+ //config.setProperty("osgi.clean", "true");\r
+ //config.setProperty("osgi.configuration.area", "@default");\r
+ config.setProperty("osgi.configuration.cascaded", "false");\r
+ //config.setProperty("osgi.console", "");\r
+ //config.setProperty("osgi.debug", "");\r
+ config.setProperty("osgi.framework", URLDecoder.decode(ld.getFwJar().toURI().toString(), "UTF-8"));\r
+ //config.setProperty("osgi.noShutdown", "");\r
+ config.setProperty("osgi.install.area", installArea);\r
+ config.setProperty("osgi.instance.area", stagingConfig.workspaceRoot.getAbsolutePath());\r
+// cd.setProperty("osgi.instance.area", "@none");\r
+ config.setProperty("osgi.user.area", "@none");\r
+ // Eclipse 3.6, not sure what this is for but modern apps have it.\r
+ config.setProperty("equinox.use.ds", "true");\r
+ // Ignore INFO level log messages\r
+ config.setProperty("eclipse.log.level", "WARNING");\r
+ File configDir = new File(stagingConfig.workspaceRoot, "configuration");\r
+ File simpleConfiguratorDir = new File(configDir, "org.eclipse.equinox.simpleconfigurator");\r
+ simpleConfiguratorDir.mkdirs();\r
+ File bundlesInfoFile = new File(simpleConfiguratorDir, "bundles.info");\r
+ config.setProperty("org.eclipse.equinox.simpleconfigurator.configUrl", URLDecoder.decode(bundlesInfoFile.toURI().toString(), "UTF-8"));\r
+ File logFile = new File(stagingConfig.workspaceRoot, "db-client.log");\r
+ writeProperties(config, new File(configDir, "config.ini"), "This configuration file was written by: " + StagingLauncher.class.getCanonicalName());\r
+ writeFile(bundlesInfo.toString(), bundlesInfoFile);\r
+\r
+ ld.setJvm(new File(new File(javaHome, "bin"), "java"));\r
+ ld.setFwConfigLocation(configDir);\r
+ ld.setFwPersistentDataLocation(configDir, false);\r
+ ld.setLauncher(null);\r
+ int maxHeap = 768; /*prefs.getInt(Activator.PLUGIN_ID, ImportPreferences.PREF_IMPORT_PROCESS_MAX_HEAP,\r
+ ImportPreferences.getDefaultImportProcessMaxHeap(), preferenceScopes);\r
+ // Just a safety if preferences have not been properly initialized for some reason.\r
+ if (maxHeap == 0)\r
+ maxHeap = ImportPreferences.getDefaultImportProcessMaxHeap();*/\r
+ ld.addJvmArg("-Xmx" + maxHeap + "m");\r
+ ld.addJvmArg("-Xms" + maxHeap + "m");\r
+ // Enable assertions to have faster failure in db client routines with improper input.\r
+ ld.addJvmArg("-ea");\r
+ // For supporting OSGi dev mode (launched from IDE)\r
+ if (Platform.inDevelopmentMode()) {\r
+ // TODO: %osgi.dev is not escaped, it can contain whitespace\r
+ // but this doesn't seem to matter ?\r
+ ld.addJvmArg("-Dosgi.dev=" + system.getProperty("osgi.dev"));\r
+ }\r
+ ld.addJvmArg("-Dosgi.arch=" + rcd.getProperty("osgi.arch"));\r
+ ld.addJvmArg("-Dosgi.os=" + rcd.getProperty("osgi.os"));\r
+ ld.addJvmArg("-Dosgi.ws=" + rcd.getProperty("osgi.ws"));\r
+ ld.addJvmArg("-Dosgi.nl=" + rcd.getProperty("osgi.nl"));\r
+\r
+ // WORKAROUND for a problem in org.eclipse.ecf fragment org.eclipse.ecf.ssl.\r
+ // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=316500\r
+ // Either one of these works, THESE:\r
+ //ld.addJvmArg("-Dosgi.java.profile.bootdelegation=override");\r
+ //ld.addJvmArg("-Dorg.osgi.framework.bootdelegation=sun.*, com.sun.*, javax.*");\r
+ // OR:\r
+ ld.addJvmArg("-Dosgi.compatibility.bootdelegation=true");\r
+\r
+ // Enable remote debugging when in osgi dev mode.\r
+ if (Platform.inDevelopmentMode())\r
+ if (!REMOTE_DEBUG_DISABLED) {\r
+ ld.addJvmArg("-Xdebug");\r
+ ld.addJvmArg("-Xnoagent");\r
+ int port = SocketUtils.getFreeEphemeralPort();\r
+ ld.addJvmArg("-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=" + port);\r
+ }\r
+\r
+ ld.addJvmArg("-D" + Constants.PROP_DUMP_PROPERTIES);\r
+ ld.addJvmArg("-D" + Constants.PROP_LOGFILE + "=" + logFile.toURI().toString());\r
+ String titleArgument = System.getProperty(Constants.PROP_WINDOW_TITLE);\r
+ if (null != titleArgument && !titleArgument.equals(""))\r
+ titleArgument = stagingConfig.titlePrefix + " " + titleArgument;\r
+ else\r
+ titleArgument = stagingConfig.titlePrefix;\r
+ ld.addJvmArg("-D" + Constants.PROP_WINDOW_TITLE + "=" + titleArgument);\r
+ if (null != stagingConfig.teamFolder)\r
+ ld.addJvmArg("-D" + Constants.PROP_TEAM_FOLDER + "=" + stagingConfig.teamFolder.getAbsolutePath()); \r
+ ld.addJvmArg("-D" + Constants.PROP_WORKSPACE_ROOT + "=" + stagingConfig.workspaceRoot.toURI().toString());\r
+ if (DEBUG)\r
+ System.out.println("JVM ARGS: " + Arrays.toString(ld.getJvmArgs()));\r
+\r
+ // Using this means that\r
+ // * config.ini must not be written self\r
+ // * parent program bundles.info can be reused (this could be done in any case)\r
+ // * running platform configuration must not be copied completely, only selected parts of\r
+ //manip.save(false);\r
+\r
+ // #5849 workaround\r
+// XSupport xs = stagingConfig.session.getService(XSupport.class);\r
+// xs.initClusterIdMap(stagingConfig.workspaceRoot.getAbsolutePath());\r
+ if (null != stagingConfig.clusterMapFolder) {\r
+ String file = "clusterIdMap.dat";\r
+ File f = new File(stagingConfig.clusterMapFolder, file);\r
+ File tFolder = new File(stagingConfig.workspaceRoot, ".metadata/.plugins/org.simantics.db.procore");\r
+ tFolder.mkdirs();\r
+ FileUtils.copyFile(f, new File(tFolder, file));\r
+ String file2 = "nextId.dat";\r
+ File f2 = new File(stagingConfig.clusterMapFolder, file2);\r
+ File tFolder2 = new File(stagingConfig.workspaceRoot, "db");\r
+ FileUtils.copyFile(f2, new File(tFolder2, file2));\r
+ }\r
+ if (DEBUG)\r
+ System.out.println("LAUNCHING\n" + manip);\r
+ Process process;\r
+ {\r
+ LauncherData launcherData = ld;\r
+ if (DEBUG)\r
+ System.out.println("Framework JAR: " + ld.getFwJar().toURI().toString());\r
+ Utils.checkAbsoluteFile(launcherData.getFwJar(), "fwJar"); //$NON-NLS-1$\r
+ File cwd = stagingConfig.workspaceRoot;\r
+ Utils.checkAbsoluteDir(cwd, "cwd"); //$NON-NLS-1$\r
+\r
+ List<String> cmdList = new LinkedList<String>();\r
+ if (launcherData.getJvm() != null)\r
+ cmdList.add(launcherData.getJvm().getAbsolutePath());\r
+ else\r
+ cmdList.add("java"); //$NON-NLS-1$\r
+\r
+ if (launcherData.getJvmArgs() != null)\r
+ for (int i = 0; i < launcherData.getJvmArgs().length; i++)\r
+ cmdList.add(launcherData.getJvmArgs()[i]);\r
+\r
+ cmdList.add("-jar"); //$NON-NLS-1$\r
+ cmdList.add("\"" + launcherData.getFwJar().getAbsolutePath() + "\"");\r
+\r
+ //EquinoxManipulatorImpl.checkConsistencyOfFwConfigLocAndFwPersistentDataLoc(launcherData);\r
+ cmdList.add(EquinoxConstants.OPTION_CONFIGURATION);\r
+ cmdList.add("\"" + launcherData.getFwPersistentDataLocation().getAbsolutePath() + "\"");\r
+\r
+ cmdList.add("-data");\r
+ cmdList.add(stagingConfig.workspaceRoot.getAbsolutePath());\r
+\r
+ if (launcherData.isClean())\r
+ cmdList.add(EquinoxConstants.OPTION_CLEAN);\r
+\r
+ String[] cmdarray = cmdList.toArray(new String[cmdList.size()]);\r
+\r
+ if (DEBUG_EXEC)\r
+ System.out.println("Launching import, CWD=" + cwd + "\n\t" + EString.implode(cmdarray, "\n\t"));\r
+\r
+ process = Runtime.getRuntime().exec(cmdarray, null, cwd);\r
+ }\r
+\r
+ long startTime = System.nanoTime();\r
+ int exitValue = Integer.MIN_VALUE;\r
+ InputStream is = process.getInputStream();\r
+ InputStream es = process.getErrorStream();\r
+ while (true) {\r
+ try {\r
+ long endTime = System.nanoTime();\r
+ //System.out.println("Checking for process exit value");\r
+ exitValue = process.exitValue();\r
+ System.out.println("finished in " + (endTime-startTime)*1e-6 + "ms");\r
+ System.out.println("exit value: " + exitValue);\r
+ } catch (IllegalThreadStateException e) {\r
+ try {\r
+ int n = is.available(); \r
+ if (n > 0) {\r
+ byte[] bytes = new byte[n];\r
+ int nr = is.read(bytes);\r
+ if (nr > 0)\r
+ System.out.println("DEBUG: STDOUT:" + org.simantics.team.Utils.bytesToStringASCII(bytes, 0, nr));\r
+ }\r
+ n = es.available();\r
+ if (n > 0) {\r
+ byte[] bytes = new byte[n];\r
+ int nr = es.read(bytes);\r
+ if (nr > 0)\r
+ System.out.println("DEBUG: STDERR:" + org.simantics.team.Utils.bytesToStringASCII(bytes, 0, nr));\r
+ }\r
+ Thread.sleep(100);\r
+ } catch (InterruptedException e1) {\r
+ e1.printStackTrace();\r
+ }\r
+ continue;\r
+ }\r
+ String ins = FileUtils.getContents(is);\r
+ if (!ins.isEmpty())\r
+ System.out.println("--- STDOUT ---\n" + ins);\r
+ String errs = FileUtils.getContents(es);\r
+ if (!errs.isEmpty())\r
+ System.out.println("--- STDERR ---\n" + errs);\r
+ break;\r
+ }\r
+\r
+ // #5849 workaround\r
+// xs.mergeClusterIdMap(stagingConfig.workspaceRoot.getAbsolutePath());\r
+\r
+ return new StagingResult(exitValue, logFile, null);\r
+ } finally {\r
+ faContext.ungetService(ref);\r
+ }\r
+ }\r
+\r
+ private static Set<String> startUpBundles = new HashSet<String>();\r
+ static {\r
+ //startUpBundles.add("org.eclipse.equinox.ds");\r
+ startUpBundles.add("org.eclipse.equinox.simpleconfigurator");\r
+ }\r
+\r
+ private static Set<String> startedBundles = new HashSet<String>();\r
+ static {\r
+ startedBundles.add("org.eclipse.core.runtime");\r
+ }\r
+\r
+ private static boolean isStartUpBundle(BundleInfo bi) {\r
+ return startUpBundles.contains(bi.getSymbolicName());\r
+ }\r
+\r
+ private static boolean isMarkedAsStarted(BundleInfo bi) {\r
+ // Prevent unnecessary bundles from being\r
+ // activated by only marking as started the plug-ins that\r
+ // are vital to the environment, which always have lower\r
+ // start level than the default 4.\r
+ return bi.isMarkedAsStarted() && (bi.getStartLevel() < 4 || startedBundles.contains(bi.getSymbolicName()));\r
+ }\r
+\r
+ private static void writeProperties(Properties properties, File target, String comment) throws IOException {\r
+ FileWriter writer = new FileWriter(target);\r
+ try {\r
+ properties.store(writer, comment);\r
+ } finally {\r
+ writer.close();\r
+ }\r
+ }\r
+\r
+ private static void writeFile(String string, File target) throws IOException {\r
+ FileWriter writer = new FileWriter(target);\r
+ try {\r
+ writer.write(string);\r
+ } finally {\r
+ writer.close();\r
+ }\r
+ }\r
+ static public final class StagingResult {\r
+\r
+ private final int exitValue;\r
+\r
+ private final File logFile;\r
+ \r
+ private final String messageLog;\r
+\r
+ public StagingResult(String message) {\r
+ this.exitValue = Integer.MIN_VALUE;\r
+ this.logFile = null;\r
+ this.messageLog = message;\r
+ }\r
+ public StagingResult(int exitValue, File logFile, String messageLog) {\r
+ this.exitValue = exitValue;\r
+ this.logFile = logFile;\r
+ this.messageLog = messageLog;\r
+ }\r
+\r
+ public int getExitValue() {\r
+ return exitValue;\r
+ }\r
+\r
+ public File getLogFile() {\r
+ return logFile;\r
+ }\r
+ \r
+ public String getMessageLog() {\r
+ return messageLog;\r
+ }\r
+\r
+ }\r
+ public static final class Config {\r
+ public final Session session;\r
+ public final Resource targetLibrary;\r
+ public final File workspaceRoot;\r
+ public final File clusterMapFolder;\r
+ public final File teamFolder;\r
+ public final PrintStream out;\r
+ // Outputs\r
+ public Resource createdModel;\r
+ public Resource createdState;\r
+ public List<String> messages = new ArrayList<String>();\r
+ public String titlePrefix = "Staging";\r
+\r
+ public Config(Session session, Resource library, File workspaceRoot, File clusterMapFolder)\r
+ throws DatabaseException {\r
+ this(session, library, workspaceRoot, clusterMapFolder, null);\r
+ }\r
+\r
+ public Config( Session session, Resource library, File workspaceRoot, File clusterMapFolder, PrintStream out)\r
+ throws DatabaseException {\r
+ this.session = session;\r
+ this.targetLibrary = library;\r
+ this.workspaceRoot = workspaceRoot;\r
+ this.clusterMapFolder = clusterMapFolder;\r
+ this.teamFolder = org.simantics.team.Utils.getTeamFolder();\r
+ this.out = out;\r
+ }\r
+ }\r
+ static final class Constants {\r
+\r
+ /**\r
+ * Tells the launched application to dump all its system properties into the\r
+ * standard output / log.\r
+ */\r
+ public static final String PROP_DUMP_PROPERTIES = "dump.properties";\r
+\r
+ /**\r
+ * Controls where the launched application log is written. Specified as an URI.\r
+ */\r
+ public static final String PROP_LOGFILE = "staging.logfile";\r
+\r
+ /**\r
+ * Prefix for the window title.\r
+ */\r
+ public static final String PROP_WINDOW_TITLE = "staging.window.title";\r
+\r
+ /**\r
+ * Tells the launched application where its team data comes from.\r
+ */\r
+ public static final String PROP_TEAM_FOLDER = "staging.team.folder";\r
+\r
+ /**\r
+ * Workspace root directory of the workbench process that launched the\r
+ * import process.\r
+ */\r
+ public static final String PROP_WORKSPACE_ROOT = "import.workspace.root";\r
+ }\r
+}\r