X-Git-Url: https://gerrit.simantics.org/r/gitweb?p=simantics%2Fplatform.git;a=blobdiff_plain;f=bundles%2Forg.simantics.team.ui%2Fsrc%2Forg%2Fsimantics%2Fteam%2Finternal%2FStagingLauncher.java;fp=bundles%2Forg.simantics.team.ui%2Fsrc%2Forg%2Fsimantics%2Fteam%2Finternal%2FStagingLauncher.java;h=7744d30498d37be879849ebdf0b8785aef78efb6;hp=0000000000000000000000000000000000000000;hb=969bd23cab98a79ca9101af33334000879fb60c5;hpb=866dba5cd5a3929bbeae85991796acb212338a08 diff --git a/bundles/org.simantics.team.ui/src/org/simantics/team/internal/StagingLauncher.java b/bundles/org.simantics.team.ui/src/org/simantics/team/internal/StagingLauncher.java new file mode 100644 index 000000000..7744d3049 --- /dev/null +++ b/bundles/org.simantics.team.ui/src/org/simantics/team/internal/StagingLauncher.java @@ -0,0 +1,452 @@ +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"; + } +}