source: josm/trunk/src/org/openstreetmap/josm/actions/RestartAction.java@ 13693

Last change on this file since 13693 was 13647, checked in by Don-vip, 6 years ago

see #16204 - Allow to start and close JOSM in WebStart sandbox mode (where every external access is denied). This was very useful to reproduce some very tricky bugs that occured in real life but were almost impossible to diagnose.

  • Property svn:eol-style set to native
File size: 9.4 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.actions;
3
4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
5import static org.openstreetmap.josm.tools.I18n.tr;
6import static org.openstreetmap.josm.tools.Utils.getSystemProperty;
7
8import java.awt.event.ActionEvent;
9import java.awt.event.KeyEvent;
10import java.io.File;
11import java.io.IOException;
12import java.lang.management.ManagementFactory;
13import java.util.ArrayList;
14import java.util.Arrays;
15import java.util.Collection;
16import java.util.List;
17
18import org.openstreetmap.josm.Main;
19import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec;
20import org.openstreetmap.josm.gui.MainApplication;
21import org.openstreetmap.josm.gui.io.SaveLayersDialog;
22import org.openstreetmap.josm.spi.preferences.Config;
23import org.openstreetmap.josm.tools.ImageProvider;
24import org.openstreetmap.josm.tools.Logging;
25import org.openstreetmap.josm.tools.Shortcut;
26
27/**
28 * Restarts JOSM as it was launched. Comes from "restart" plugin, originally written by Upliner.
29 * <br><br>
30 * Mechanisms have been improved based on #8561 discussions and
31 * <a href="http://lewisleo.blogspot.jp/2012/08/programmatically-restart-java.html">this article</a>.
32 * @since 5857
33 */
34public class RestartAction extends JosmAction {
35
36 // AppleScript to restart OS X package
37 private static final String RESTART_APPLE_SCRIPT =
38 "tell application \"System Events\"\n"
39 + "repeat until not (exists process \"JOSM\")\n"
40 + "delay 0.2\n"
41 + "end repeat\n"
42 + "end tell\n"
43 + "tell application \"JOSM\" to activate";
44
45 /**
46 * Constructs a new {@code RestartAction}.
47 */
48 public RestartAction() {
49 super(tr("Restart"), "restart", tr("Restart the application."),
50 Shortcut.registerShortcut("file:restart", tr("File: {0}", tr("Restart")), KeyEvent.VK_J, Shortcut.ALT_CTRL_SHIFT), false);
51 putValue("help", ht("/Action/Restart"));
52 putValue("toolbar", "action/restart");
53 if (MainApplication.getToolbar() != null) {
54 MainApplication.getToolbar().register(this);
55 }
56 setEnabled(isRestartSupported());
57 }
58
59 @Override
60 public void actionPerformed(ActionEvent e) {
61 try {
62 restartJOSM();
63 } catch (IOException ex) {
64 Logging.error(ex);
65 }
66 }
67
68 /**
69 * Determines if restarting the application should be possible on this platform.
70 * @return {@code true} if the mandatory system property {@code sun.java.command} is defined, {@code false} otherwise.
71 * @since 5951
72 */
73 public static boolean isRestartSupported() {
74 return getSystemProperty("sun.java.command") != null;
75 }
76
77 /**
78 * Restarts the current Java application.
79 * @throws IOException in case of any I/O error
80 */
81 public static void restartJOSM() throws IOException {
82 // If JOSM has been started with property 'josm.restart=true' this means
83 // it is executed by a start script that can handle restart.
84 // Request for restart is indicated by exit code 9.
85 String scriptRestart = getSystemProperty("josm.restart");
86 if ("true".equals(scriptRestart)) {
87 MainApplication.exitJosm(true, 9, SaveLayersDialog.Reason.RESTART);
88 }
89
90 if (isRestartSupported() && !MainApplication.exitJosm(false, 0, SaveLayersDialog.Reason.RESTART)) return;
91 final List<String> cmd;
92 // special handling for OSX .app package
93 if (Main.isPlatformOsx() && getSystemProperty("java.library.path").contains("/JOSM.app/Contents/MacOS")) {
94 cmd = getAppleCommands();
95 } else {
96 cmd = getCommands();
97 }
98 Logging.info("Restart "+cmd);
99 if (Logging.isDebugEnabled() && Config.getPref().getBoolean("restart.debug.simulation")) {
100 Logging.debug("Restart cancelled to get debug info");
101 return;
102 }
103 // execute the command in a shutdown hook, to be sure that all the
104 // resources have been disposed before restarting the application
105 Runtime.getRuntime().addShutdownHook(new Thread("josm-restarter") {
106 @Override
107 public void run() {
108 try {
109 Runtime.getRuntime().exec(cmd.toArray(new String[0]));
110 } catch (IOException e) {
111 Logging.error(e);
112 }
113 }
114 });
115 // exit
116 System.exit(0);
117 }
118
119 private static List<String> getAppleCommands() {
120 final List<String> cmd = new ArrayList<>();
121 cmd.add("/usr/bin/osascript");
122 for (String line : RESTART_APPLE_SCRIPT.split("\n")) {
123 cmd.add("-e");
124 cmd.add(line);
125 }
126 return cmd;
127 }
128
129 private static List<String> getCommands() throws IOException {
130 final List<String> cmd = new ArrayList<>();
131 // java binary
132 cmd.add(getJavaRuntime());
133 // vm arguments
134 addVMArguments(cmd);
135 // Determine webstart JNLP file. Use jnlpx.origFilenameArg instead of jnlp.application.href,
136 // because only this one is present when run from j2plauncher.exe (see #10795)
137 final String jnlp = getSystemProperty("jnlpx.origFilenameArg");
138 // program main and program arguments (be careful a sun property. might not be supported by all JVM)
139 final String javaCommand = getSystemProperty("sun.java.command");
140 if (javaCommand == null) {
141 throw new IOException("Unable to retrieve sun.java.command property");
142 }
143 String[] mainCommand = javaCommand.split(" ");
144 if (javaCommand.endsWith(".jnlp") && jnlp == null) {
145 // see #11751 - jnlp on Linux
146 Logging.debug("Detected jnlp without jnlpx.origFilenameArg property set");
147 cmd.addAll(Arrays.asList(mainCommand));
148 } else {
149 // look for a .jar in all chunks to support paths with spaces (fix #9077)
150 StringBuilder sb = new StringBuilder(mainCommand[0]);
151 for (int i = 1; i < mainCommand.length && !mainCommand[i-1].endsWith(".jar"); i++) {
152 sb.append(' ').append(mainCommand[i]);
153 }
154 String jarPath = sb.toString();
155 // program main is a jar
156 if (jarPath.endsWith(".jar")) {
157 // if it's a jar, add -jar mainJar
158 cmd.add("-jar");
159 cmd.add(new File(jarPath).getPath());
160 } else {
161 // else it's a .class, add the classpath and mainClass
162 cmd.add("-cp");
163 cmd.add('"' + getSystemProperty("java.class.path") + '"');
164 cmd.add(mainCommand[0].replace("jdk.plugin/", "")); // Main class appears to be invalid on Java WebStart 9
165 }
166 // add JNLP file.
167 if (jnlp != null) {
168 cmd.add(jnlp);
169 }
170 }
171 // finally add program arguments
172 cmd.addAll(MainApplication.getCommandLineArgs());
173 return cmd;
174 }
175
176 private static String getJavaRuntime() throws IOException {
177 final String java = getSystemProperty("java.home") + File.separator + "bin" + File.separator +
178 (Main.isPlatformWindows() ? "java.exe" : "java");
179 if (!new File(java).isFile()) {
180 throw new IOException("Unable to find suitable java runtime at "+java);
181 }
182 return java;
183 }
184
185 private static void addVMArguments(Collection<String> cmd) {
186 List<String> arguments = ManagementFactory.getRuntimeMXBean().getInputArguments();
187 Logging.debug("VM arguments: {0}", arguments);
188 for (String arg : arguments) {
189 // When run from jp2launcher.exe, jnlpx.remove is true, while it is not when run from javaws
190 // Always set it to false to avoid error caused by a missing jnlp file on the second restart
191 arg = arg.replace("-Djnlpx.remove=true", "-Djnlpx.remove=false");
192 // if it's the agent argument : we ignore it otherwise the
193 // address of the old application and the new one will be in conflict
194 if (!arg.contains("-agentlib")) {
195 cmd.add(arg);
196 }
197 }
198 }
199
200 /**
201 * Returns a new {@code ButtonSpec} instance that performs this action.
202 * @return A new {@code ButtonSpec} instance that performs this action.
203 */
204 public static ButtonSpec getRestartButtonSpec() {
205 return new ButtonSpec(
206 tr("Restart"),
207 ImageProvider.get("restart"),
208 tr("Restart the application."),
209 ht("/Action/Restart"),
210 isRestartSupported()
211 );
212 }
213
214 /**
215 * Returns a new {@code ButtonSpec} instance that do not perform this action.
216 * @return A new {@code ButtonSpec} instance that do not perform this action.
217 */
218 public static ButtonSpec getCancelButtonSpec() {
219 return new ButtonSpec(
220 tr("Cancel"),
221 ImageProvider.get("cancel"),
222 tr("Click to restart later."),
223 null /* no specific help context */
224 );
225 }
226
227 /**
228 * Returns default {@code ButtonSpec} instances for this action (Restart/Cancel).
229 * @return Default {@code ButtonSpec} instances for this action.
230 * @see #getRestartButtonSpec
231 * @see #getCancelButtonSpec
232 */
233 public static ButtonSpec[] getButtonSpecs() {
234 return new ButtonSpec[] {
235 getRestartButtonSpec(),
236 getCancelButtonSpec()
237 };
238 }
239}
Note: See TracBrowser for help on using the repository browser.