]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.team.ui/src/org/simantics/team/internal/StagingLauncher.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.team.ui / src / org / simantics / team / internal / StagingLauncher.java
1 package org.simantics.team.internal;\r
2 \r
3 import java.io.File;\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
16 \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
39 \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
52         try {\r
53             dsBundle.start(/*Bundle.START_TRANSIENT*/);\r
54             if (DEBUG)\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
58         }\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
66         if (ref == null)\r
67             throw new StagingException("Reference for FrameworkAdmin not available.");\r
68         FrameworkAdmin admin = (FrameworkAdmin)faContext.getService(ref);\r
69         if (null == admin)\r
70             throw new StagingException("FrameworkAdmin not available.");\r
71         try {\r
72             Manipulator rmanip = admin.getRunningManipulator();\r
73             if (rmanip == null)\r
74                 throw new StagingException("No FrameworkAdmin Manipulator available for the currently running environment.");\r
75             if (DEBUG)\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
91                 bundlesInfo\r
92                 .append(bi.getSymbolicName())\r
93                 .append(",")\r
94                 .append(bi.getVersion())\r
95                 .append(",")\r
96                 //.append(bi.getLocation().toString())\r
97                 .append(URLDecoder.decode(bi.getLocation().toString(), "UTF-8"))\r
98                 .append(",")\r
99                 .append(bi.getStartLevel())\r
100                 .append(",")\r
101                 .append(started)\r
102                 .append("\n");\r
103                 if (DEBUG) {\r
104                     System.out.println("bundle: " + bi.getSymbolicName() + "\n\t" + bi.getLocation().toString() + "\n\t" + started);\r
105                     if (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
109                 }\r
110                 if (isStartUpBundle(bi)) {\r
111                     if (osgiBundles.length() > 0) {\r
112                         osgiBundles.append(",");\r
113                     }\r
114                     osgiBundles\r
115                     .append("reference:")\r
116                     .append(URLDecoder.decode(bi.getLocation().toString(), "UTF-8"))\r
117                     .append("@")\r
118                     .append(bi.getStartLevel())\r
119                     .append(":start");\r
120                 }\r
121             }\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
154 \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
162             if (maxHeap == 0)\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
173             }\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
178 \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
184             // OR:\r
185             ld.addJvmArg("-Dosgi.compatibility.bootdelegation=true");\r
186 \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
194                 }\r
195 \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
201             else\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
207             if (DEBUG)\r
208                 System.out.println("JVM ARGS: " + Arrays.toString(ld.getJvmArgs()));\r
209 \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
215 \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
223                 tFolder.mkdirs();\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
229             }\r
230             if (DEBUG)\r
231                 System.out.println("LAUNCHING\n" + manip);\r
232             Process process;\r
233             {\r
234                 LauncherData launcherData = ld;\r
235                 if (DEBUG)\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
240 \r
241                 List<String> cmdList = new LinkedList<String>();\r
242                 if (launcherData.getJvm() != null)\r
243                     cmdList.add(launcherData.getJvm().getAbsolutePath());\r
244                 else\r
245                     cmdList.add("java"); //$NON-NLS-1$\r
246 \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
250 \r
251                 cmdList.add("-jar"); //$NON-NLS-1$\r
252                 cmdList.add("\"" + launcherData.getFwJar().getAbsolutePath() + "\"");\r
253 \r
254                 //EquinoxManipulatorImpl.checkConsistencyOfFwConfigLocAndFwPersistentDataLoc(launcherData);\r
255                 cmdList.add(EquinoxConstants.OPTION_CONFIGURATION);\r
256                 cmdList.add("\"" + launcherData.getFwPersistentDataLocation().getAbsolutePath() + "\"");\r
257 \r
258                 cmdList.add("-data");\r
259                 cmdList.add(stagingConfig.workspaceRoot.getAbsolutePath());\r
260 \r
261                 if (launcherData.isClean())\r
262                     cmdList.add(EquinoxConstants.OPTION_CLEAN);\r
263 \r
264                 String[] cmdarray = cmdList.toArray(new String[cmdList.size()]);\r
265 \r
266                 if (DEBUG_EXEC)\r
267                     System.out.println("Launching import, CWD=" + cwd + "\n\t" + EString.implode(cmdarray, "\n\t"));\r
268 \r
269                 process = Runtime.getRuntime().exec(cmdarray, null, cwd);\r
270             }\r
271 \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
276             while (true) {\r
277                 try {\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
284                     try {\r
285                         int n = is.available(); \r
286                         if (n > 0) {\r
287                             byte[] bytes = new byte[n];\r
288                             int nr = is.read(bytes);\r
289                             if (nr > 0)\r
290                                 System.out.println("DEBUG: STDOUT:" + org.simantics.team.Utils.bytesToStringASCII(bytes, 0, nr));\r
291                         }\r
292                         n = es.available();\r
293                         if (n > 0) {\r
294                             byte[] bytes = new byte[n];\r
295                             int nr = es.read(bytes);\r
296                             if (nr > 0)\r
297                                 System.out.println("DEBUG: STDERR:" + org.simantics.team.Utils.bytesToStringASCII(bytes, 0, nr));\r
298                         }\r
299                         Thread.sleep(100);\r
300                     } catch (InterruptedException e1) {\r
301                         e1.printStackTrace();\r
302                     }\r
303                     continue;\r
304                 }\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
311                 break;\r
312             }\r
313 \r
314             // #5849 workaround\r
315 //            xs.mergeClusterIdMap(stagingConfig.workspaceRoot.getAbsolutePath());\r
316 \r
317             return new StagingResult(exitValue, logFile, null);\r
318         } finally {\r
319             faContext.ungetService(ref);\r
320         }\r
321     }\r
322 \r
323     private static Set<String> startUpBundles = new HashSet<String>();\r
324     static {\r
325         //startUpBundles.add("org.eclipse.equinox.ds");\r
326         startUpBundles.add("org.eclipse.equinox.simpleconfigurator");\r
327     }\r
328 \r
329     private static Set<String> startedBundles = new HashSet<String>();\r
330     static {\r
331         startedBundles.add("org.eclipse.core.runtime");\r
332     }\r
333 \r
334     private static boolean isStartUpBundle(BundleInfo bi) {\r
335         return startUpBundles.contains(bi.getSymbolicName());\r
336     }\r
337 \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
344     }\r
345 \r
346     private static void writeProperties(Properties properties, File target, String comment) throws IOException {\r
347         FileWriter writer = new FileWriter(target);\r
348         try {\r
349             properties.store(writer, comment);\r
350         } finally {\r
351             writer.close();\r
352         }\r
353     }\r
354 \r
355     private static void writeFile(String string, File target) throws IOException {\r
356         FileWriter writer = new FileWriter(target);\r
357         try {\r
358             writer.write(string);\r
359         } finally {\r
360             writer.close();\r
361         }\r
362     }\r
363     static public final class StagingResult {\r
364 \r
365         private final int exitValue;\r
366 \r
367         private final File logFile;\r
368         \r
369         private final String messageLog;\r
370 \r
371         public StagingResult(String message) {\r
372             this.exitValue = Integer.MIN_VALUE;\r
373             this.logFile = null;\r
374             this.messageLog = message;\r
375         }\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
380         }\r
381 \r
382         public int getExitValue() {\r
383             return exitValue;\r
384         }\r
385 \r
386         public File getLogFile() {\r
387             return logFile;\r
388         }\r
389         \r
390         public String getMessageLog() {\r
391             return messageLog;\r
392         }\r
393 \r
394     }\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
402         // Outputs\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
407 \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
411         }\r
412 \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
420             this.out = out;\r
421         }\r
422     }\r
423     static final class Constants {\r
424 \r
425         /**\r
426          * Tells the launched application to dump all its system properties into the\r
427          * standard output / log.\r
428          */\r
429         public static final String PROP_DUMP_PROPERTIES = "dump.properties";\r
430 \r
431         /**\r
432          * Controls where the launched application log is written. Specified as an URI.\r
433          */\r
434         public static final String PROP_LOGFILE = "staging.logfile";\r
435 \r
436         /**\r
437          * Prefix for the window title.\r
438          */\r
439         public static final String PROP_WINDOW_TITLE = "staging.window.title";\r
440 \r
441         /**\r
442          * Tells the launched application where its team data comes from.\r
443          */\r
444         public static final String PROP_TEAM_FOLDER = "staging.team.folder";\r
445 \r
446         /**\r
447          * Workspace root directory of the workbench process that launched the\r
448          * import process.\r
449          */\r
450         public static final String PROP_WORKSPACE_ROOT    = "import.workspace.root";\r
451     }\r
452 }\r