source: josm/trunk/src/org/openstreetmap/josm/actions/ShowStatusReportAction.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: 14.0 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.getSystemEnv;
7import static org.openstreetmap.josm.tools.Utils.getSystemProperty;
8
9import java.awt.Dimension;
10import java.awt.DisplayMode;
11import java.awt.GraphicsEnvironment;
12import java.awt.event.ActionEvent;
13import java.awt.event.KeyEvent;
14import java.lang.management.ManagementFactory;
15import java.util.ArrayList;
16import java.util.Arrays;
17import java.util.Collection;
18import java.util.HashSet;
19import java.util.List;
20import java.util.ListIterator;
21import java.util.Locale;
22import java.util.Map;
23import java.util.Map.Entry;
24import java.util.Set;
25import java.util.stream.Collectors;
26
27import org.openstreetmap.josm.Main;
28import org.openstreetmap.josm.data.Version;
29import org.openstreetmap.josm.data.osm.DataSet;
30import org.openstreetmap.josm.data.osm.DatasetConsistencyTest;
31import org.openstreetmap.josm.data.preferences.sources.MapPaintPrefHelper;
32import org.openstreetmap.josm.data.preferences.sources.PresetPrefHelper;
33import org.openstreetmap.josm.data.preferences.sources.SourcePrefHelper;
34import org.openstreetmap.josm.data.preferences.sources.ValidatorPrefHelper;
35import org.openstreetmap.josm.gui.ExtendedDialog;
36import org.openstreetmap.josm.gui.MainApplication;
37import org.openstreetmap.josm.gui.bugreport.DebugTextDisplay;
38import org.openstreetmap.josm.gui.util.GuiHelper;
39import org.openstreetmap.josm.io.OsmApi;
40import org.openstreetmap.josm.plugins.PluginHandler;
41import org.openstreetmap.josm.spi.preferences.Config;
42import org.openstreetmap.josm.spi.preferences.Setting;
43import org.openstreetmap.josm.tools.Logging;
44import org.openstreetmap.josm.tools.PlatformHookUnixoid;
45import org.openstreetmap.josm.tools.Shortcut;
46import org.openstreetmap.josm.tools.Utils;
47import org.openstreetmap.josm.tools.bugreport.BugReportSender;
48
49/**
50 * Opens a dialog with useful status information like version numbers for Java, JOSM and plugins
51 * Also includes preferences with stripped username and password.
52 *
53 * @author xeen
54 */
55public final class ShowStatusReportAction extends JosmAction {
56
57 /**
58 * Constructs a new {@code ShowStatusReportAction}
59 */
60 public ShowStatusReportAction() {
61 super(
62 tr("Show Status Report"),
63 "clock",
64 tr("Show status report with useful information that can be attached to bugs"),
65 Shortcut.registerShortcut("help:showstatusreport", tr("Help: {0}",
66 tr("Show Status Report")), KeyEvent.CHAR_UNDEFINED, Shortcut.NONE), false);
67
68 putValue("help", ht("/Action/ShowStatusReport"));
69 putValue("toolbar", "help/showstatusreport");
70 MainApplication.getToolbar().register(this);
71 }
72
73 private static boolean isRunningJavaWebStart() {
74 try {
75 // See http://stackoverflow.com/a/16200769/2257172
76 return Class.forName("javax.jnlp.ServiceManager") != null;
77 } catch (ClassNotFoundException e) {
78 return false;
79 }
80 }
81
82 /**
83 * Replies the report header (software and system info)
84 * @return The report header (software and system info)
85 */
86 public static String getReportHeader() {
87 StringBuilder text = new StringBuilder(256);
88 String runtimeVersion = getSystemProperty("java.runtime.version");
89 text.append(Version.getInstance().getReleaseAttributes())
90 .append("\nIdentification: ").append(Version.getInstance().getAgentString());
91 String buildNumber = Main.platform.getOSBuildNumber();
92 if (!buildNumber.isEmpty()) {
93 text.append("\nOS Build number: ").append(buildNumber);
94 }
95 text.append("\nMemory Usage: ")
96 .append(Runtime.getRuntime().totalMemory()/1024/1024)
97 .append(" MB / ")
98 .append(Runtime.getRuntime().maxMemory()/1024/1024)
99 .append(" MB (")
100 .append(Runtime.getRuntime().freeMemory()/1024/1024)
101 .append(" MB allocated, but free)\nJava version: ")
102 .append(runtimeVersion != null ? runtimeVersion : getSystemProperty("java.version")).append(", ")
103 .append(getSystemProperty("java.vendor")).append(", ")
104 .append(getSystemProperty("java.vm.name"))
105 .append("\nScreen: ");
106 if (!GraphicsEnvironment.isHeadless()) {
107 text.append(Arrays.stream(GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices()).map(gd -> {
108 StringBuilder b = new StringBuilder(gd.getIDstring());
109 DisplayMode dm = gd.getDisplayMode();
110 if (dm != null) {
111 b.append(' ').append(dm.getWidth()).append('x').append(dm.getHeight());
112 }
113 return b.toString();
114 }).collect(Collectors.joining(", ")));
115 }
116 Dimension maxScreenSize = GuiHelper.getMaximumScreenSize();
117 text.append("\nMaximum Screen Size: ")
118 .append((int) maxScreenSize.getWidth()).append('x')
119 .append((int) maxScreenSize.getHeight()).append('\n');
120
121 if (Main.platform instanceof PlatformHookUnixoid) {
122 // Add Java package details
123 String packageDetails = ((PlatformHookUnixoid) Main.platform).getJavaPackageDetails();
124 if (packageDetails != null) {
125 text.append("Java package: ")
126 .append(packageDetails)
127 .append('\n');
128 }
129 // Add WebStart package details if run from JNLP
130 if (isRunningJavaWebStart()) {
131 String webStartDetails = ((PlatformHookUnixoid) Main.platform).getWebStartPackageDetails();
132 if (webStartDetails != null) {
133 text.append("WebStart package: ")
134 .append(webStartDetails)
135 .append('\n');
136 }
137 }
138 // Add Gnome Atk wrapper details if found
139 String atkWrapperDetails = ((PlatformHookUnixoid) Main.platform).getAtkWrapperPackageDetails();
140 if (atkWrapperDetails != null) {
141 text.append("Java ATK Wrapper package: ")
142 .append(atkWrapperDetails)
143 .append('\n');
144 }
145 }
146 try {
147 // Build a new list of VM parameters to modify it below if needed (default implementation returns an UnmodifiableList instance)
148 List<String> vmArguments = new ArrayList<>(ManagementFactory.getRuntimeMXBean().getInputArguments());
149 for (ListIterator<String> it = vmArguments.listIterator(); it.hasNext();) {
150 String value = it.next();
151 if (value.contains("=")) {
152 String[] param = value.split("=");
153 // Hide some parameters for privacy concerns
154 if (param[0].toLowerCase(Locale.ENGLISH).startsWith("-dproxy")) {
155 it.set(param[0]+"=xxx");
156 } else if ("-Djnlpx.vmargs".equals(param[0])) {
157 // Remove jnlpx.vmargs (base64 encoded copy of VM arguments already included in clear)
158 it.remove();
159 } else {
160 // Replace some paths for readability and privacy concerns
161 String val = paramCleanup(param[1]);
162 if (!val.equals(param[1])) {
163 it.set(param[0] + '=' + val);
164 }
165 }
166 } else if (value.startsWith("-X")) {
167 // Remove arguments like -Xbootclasspath/a, -Xverify:remote, that can be very long and unhelpful
168 it.remove();
169 }
170 }
171 if (!vmArguments.isEmpty()) {
172 text.append("VM arguments: ").append(vmArguments.toString().replace("\\\\", "\\")).append('\n');
173 }
174 } catch (SecurityException e) {
175 Logging.trace(e);
176 }
177 List<String> commandLineArgs = MainApplication.getCommandLineArgs();
178 if (!commandLineArgs.isEmpty()) {
179 text.append("Program arguments: ").append(Arrays.toString(paramCleanup(commandLineArgs).toArray())).append('\n');
180 }
181 if (Main.main != null) {
182 DataSet dataset = MainApplication.getLayerManager().getActiveDataSet();
183 if (dataset != null) {
184 String result = DatasetConsistencyTest.runTests(dataset);
185 if (result.isEmpty()) {
186 text.append("Dataset consistency test: No problems found\n");
187 } else {
188 text.append("\nDataset consistency test:\n").append(result).append('\n');
189 }
190 }
191 }
192 text.append('\n');
193 appendCollection(text, "Plugins", Utils.transform(PluginHandler.getBugReportInformation(), i -> "+ " + i));
194 appendCollection(text, "Tagging presets", getCustomUrls(PresetPrefHelper.INSTANCE));
195 appendCollection(text, "Map paint styles", getCustomUrls(MapPaintPrefHelper.INSTANCE));
196 appendCollection(text, "Validator rules", getCustomUrls(ValidatorPrefHelper.INSTANCE));
197 appendCollection(text, "Last errors/warnings", Utils.transform(Logging.getLastErrorAndWarnings(), i -> "- " + i));
198
199 String osmApi = OsmApi.getOsmApi().getServerUrl();
200 if (!OsmApi.DEFAULT_API_URL.equals(osmApi.trim())) {
201 text.append("OSM API: ").append(osmApi).append("\n\n");
202 }
203
204 return text.toString();
205 }
206
207 private static Collection<String> getCustomUrls(SourcePrefHelper helper) {
208 final Set<String> defaultUrls = helper.getDefault().stream()
209 .map(i -> i.url)
210 .collect(Collectors.toSet());
211 return helper.get().stream()
212 .filter(i -> !defaultUrls.contains(i.url))
213 .map(i -> (i.active ? "+ " : "- ") + i.url)
214 .collect(Collectors.toList());
215 }
216
217 private static List<String> paramCleanup(Collection<String> params) {
218 List<String> result = new ArrayList<>(params.size());
219 for (String param : params) {
220 result.add(paramCleanup(param));
221 }
222 return result;
223 }
224
225 /**
226 * Shortens and removes private informations from a parameter used for status report.
227 * @param param parameter to cleanup
228 * @return shortened/anonymized parameter
229 */
230 private static String paramCleanup(String param) {
231 final String envJavaHome = getSystemEnv("JAVA_HOME");
232 final String envJavaHomeAlt = Main.isPlatformWindows() ? "%JAVA_HOME%" : "${JAVA_HOME}";
233 final String propJavaHome = getSystemProperty("java.home");
234 final String propJavaHomeAlt = "<java.home>";
235 final String prefDir = Config.getDirs().getPreferencesDirectory(false).toString();
236 final String prefDirAlt = "<josm.pref>";
237 final String userDataDir = Config.getDirs().getUserDataDirectory(false).toString();
238 final String userDataDirAlt = "<josm.userdata>";
239 final String userCacheDir = Config.getDirs().getCacheDirectory(false).toString();
240 final String userCacheDirAlt = "<josm.cache>";
241 final String userHomeDir = getSystemProperty("user.home");
242 final String userHomeDirAlt = Main.isPlatformWindows() ? "%UserProfile%" : "${HOME}";
243 final String userName = getSystemProperty("user.name");
244 final String userNameAlt = "<user.name>";
245
246 String val = param;
247 val = paramReplace(val, envJavaHome, envJavaHomeAlt);
248 val = paramReplace(val, envJavaHome, envJavaHomeAlt);
249 val = paramReplace(val, propJavaHome, propJavaHomeAlt);
250 val = paramReplace(val, prefDir, prefDirAlt);
251 val = paramReplace(val, userDataDir, userDataDirAlt);
252 val = paramReplace(val, userCacheDir, userCacheDirAlt);
253 val = paramReplace(val, userHomeDir, userHomeDirAlt);
254 if (userName != null && userName.length() >= 3) {
255 val = paramReplace(val, userName, userNameAlt);
256 }
257 return val;
258 }
259
260 private static String paramReplace(String str, String target, String replacement) {
261 return target == null ? str : str.replace(target, replacement);
262 }
263
264 private static void appendCollection(StringBuilder text, String label, Collection<String> col) {
265 if (!col.isEmpty()) {
266 text.append(label).append(":\n");
267 for (String o : col) {
268 text.append(paramCleanup(o)).append('\n');
269 }
270 text.append('\n');
271 }
272 }
273
274 @Override
275 public void actionPerformed(ActionEvent e) {
276 StringBuilder text = new StringBuilder();
277 String reportHeader = getReportHeader();
278 text.append(reportHeader);
279 Map<String, Setting<?>> settings = Main.pref.getAllSettings();
280 Set<String> keys = new HashSet<>(settings.keySet());
281 for (String key : keys) {
282 // Remove sensitive information from status report
283 if (key.startsWith("marker.show") || key.contains("username") || key.contains("password") || key.contains("access-token")) {
284 settings.remove(key);
285 }
286 }
287 for (Entry<String, Setting<?>> entry : settings.entrySet()) {
288 text.append(paramCleanup(entry.getKey()))
289 .append('=')
290 .append(paramCleanup(entry.getValue().getValue().toString())).append('\n');
291 }
292
293 DebugTextDisplay ta = new DebugTextDisplay(text.toString());
294
295 ExtendedDialog ed = new ExtendedDialog(Main.parent,
296 tr("Status Report"),
297 tr("Copy to clipboard and close"), tr("Report bug"), tr("Close"));
298 ed.setButtonIcons("copy", "bug", "cancel");
299 ed.setContent(ta, false);
300 ed.setMinimumSize(new Dimension(380, 200));
301 ed.setPreferredSize(new Dimension(700, Main.parent.getHeight()-50));
302
303 switch (ed.showDialog().getValue()) {
304 case 1: ta.copyToClipboard(); break;
305 case 2: BugReportSender.reportBug(reportHeader); break;
306 default: // Do nothing
307 }
308 }
309}
Note: See TracBrowser for help on using the repository browser.