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