1 | // License: GPL. For details, see LICENSE file.
|
---|
2 | package org.openstreetmap.josm.tools.bugreport;
|
---|
3 |
|
---|
4 | import java.io.IOException;
|
---|
5 | import java.io.InputStream;
|
---|
6 | import java.net.URL;
|
---|
7 | import java.nio.charset.StandardCharsets;
|
---|
8 | import java.util.Base64;
|
---|
9 | import java.util.Objects;
|
---|
10 |
|
---|
11 | import javax.xml.parsers.ParserConfigurationException;
|
---|
12 | import javax.xml.xpath.XPath;
|
---|
13 | import javax.xml.xpath.XPathConstants;
|
---|
14 | import javax.xml.xpath.XPathExpressionException;
|
---|
15 | import javax.xml.xpath.XPathFactory;
|
---|
16 |
|
---|
17 | import org.openstreetmap.josm.spi.preferences.Config;
|
---|
18 | import org.openstreetmap.josm.tools.HttpClient;
|
---|
19 | import org.openstreetmap.josm.tools.HttpClient.Response;
|
---|
20 | import org.openstreetmap.josm.tools.Logging;
|
---|
21 | import org.openstreetmap.josm.tools.OpenBrowser;
|
---|
22 | import org.openstreetmap.josm.tools.Utils;
|
---|
23 | import org.openstreetmap.josm.tools.XmlUtils;
|
---|
24 | import org.w3c.dom.Document;
|
---|
25 | import org.xml.sax.SAXException;
|
---|
26 |
|
---|
27 | /**
|
---|
28 | * This class handles sending the bug report to JOSM website.
|
---|
29 | * <p>
|
---|
30 | * Currently, we try to open a browser window for the user that displays the bug report.
|
---|
31 | *
|
---|
32 | * @author Michael Zangl
|
---|
33 | * @since 10055
|
---|
34 | */
|
---|
35 | public class BugReportSender extends Thread {
|
---|
36 |
|
---|
37 | /**
|
---|
38 | * Called during bug submission to JOSM bugtracker. Completes the bug report submission and handles errors.
|
---|
39 | * @since 12790
|
---|
40 | */
|
---|
41 | public interface BugReportSendingHandler {
|
---|
42 | /**
|
---|
43 | * Called when a bug is sent to JOSM bugtracker.
|
---|
44 | * @param bugUrl URL to visit to effectively submit the bug report to JOSM website
|
---|
45 | * @param statusText the status text being sent
|
---|
46 | * @return <code>null</code> for success or a string in case of an error
|
---|
47 | */
|
---|
48 | String sendingBugReport(String bugUrl, String statusText);
|
---|
49 |
|
---|
50 | /**
|
---|
51 | * Called when a bug failed to be sent to JOSM bugtracker.
|
---|
52 | * @param errorMessage the error message
|
---|
53 | * @param statusText the status text being sent
|
---|
54 | */
|
---|
55 | void failed(String errorMessage, String statusText);
|
---|
56 | }
|
---|
57 |
|
---|
58 | /**
|
---|
59 | * The fallback bug report sending handler if none is set.
|
---|
60 | * @since 12790
|
---|
61 | */
|
---|
62 | public static final BugReportSendingHandler FALLBACK_BUGREPORT_SENDING_HANDLER = new BugReportSendingHandler() {
|
---|
63 | @Override
|
---|
64 | public String sendingBugReport(String bugUrl, String statusText) {
|
---|
65 | return OpenBrowser.displayUrl(bugUrl);
|
---|
66 | }
|
---|
67 |
|
---|
68 | @Override
|
---|
69 | public void failed(String errorMessage, String statusText) {
|
---|
70 | Logging.error("Unable to send bug report: {0}\n{1}", errorMessage, statusText);
|
---|
71 | }
|
---|
72 | };
|
---|
73 |
|
---|
74 | private static volatile BugReportSendingHandler handler = FALLBACK_BUGREPORT_SENDING_HANDLER;
|
---|
75 |
|
---|
76 | private final String statusText;
|
---|
77 | private String errorMessage;
|
---|
78 |
|
---|
79 | /**
|
---|
80 | * Creates a new sender.
|
---|
81 | * @param statusText The status text to send.
|
---|
82 | */
|
---|
83 | protected BugReportSender(String statusText) {
|
---|
84 | super("Bug report sender");
|
---|
85 | this.statusText = statusText;
|
---|
86 | }
|
---|
87 |
|
---|
88 | @Override
|
---|
89 | public void run() {
|
---|
90 | try {
|
---|
91 | // first, send the debug text using post.
|
---|
92 | String debugTextPasteId = pasteDebugText();
|
---|
93 | String bugUrl = getJOSMTicketURL() + "?pdata_stored=" + debugTextPasteId;
|
---|
94 |
|
---|
95 | // then notify handler
|
---|
96 | errorMessage = handler.sendingBugReport(bugUrl, statusText);
|
---|
97 | if (errorMessage != null) {
|
---|
98 | Logging.warn(errorMessage);
|
---|
99 | handler.failed(errorMessage, statusText);
|
---|
100 | }
|
---|
101 | } catch (BugReportSenderException e) {
|
---|
102 | Logging.warn(e);
|
---|
103 | errorMessage = e.getMessage();
|
---|
104 | handler.failed(errorMessage, statusText);
|
---|
105 | }
|
---|
106 | }
|
---|
107 |
|
---|
108 | /**
|
---|
109 | * Sends the debug text to the server.
|
---|
110 | * @return The token which was returned by the server. We need to pass this on to the ticket system.
|
---|
111 | * @throws BugReportSenderException if sending the report failed.
|
---|
112 | */
|
---|
113 | private String pasteDebugText() throws BugReportSenderException {
|
---|
114 | try {
|
---|
115 | String text = Utils.strip(statusText);
|
---|
116 | String pdata = Base64.getEncoder().encodeToString(text.getBytes(StandardCharsets.UTF_8));
|
---|
117 | String postQuery = "pdata=" + Utils.encodeUrl(pdata);
|
---|
118 | HttpClient client = HttpClient.create(new URL(getJOSMTicketURL()), "POST")
|
---|
119 | .setHeader("Content-Type", "application/x-www-form-urlencoded")
|
---|
120 | .setRequestBody(postQuery.getBytes(StandardCharsets.UTF_8));
|
---|
121 |
|
---|
122 | Response connection = client.connect();
|
---|
123 |
|
---|
124 | if (connection.getResponseCode() >= 500) {
|
---|
125 | throw new BugReportSenderException("Internal server error.");
|
---|
126 | }
|
---|
127 |
|
---|
128 | try (InputStream in = connection.getContent()) {
|
---|
129 | return retrieveDebugToken(XmlUtils.parseSafeDOM(in));
|
---|
130 | }
|
---|
131 | } catch (IOException | SAXException | ParserConfigurationException | XPathExpressionException t) {
|
---|
132 | throw new BugReportSenderException(t);
|
---|
133 | }
|
---|
134 | }
|
---|
135 |
|
---|
136 | private static String getJOSMTicketURL() {
|
---|
137 | return Config.getUrls().getJOSMWebsite() + "/josmticket";
|
---|
138 | }
|
---|
139 |
|
---|
140 | private static String retrieveDebugToken(Document document) throws XPathExpressionException, BugReportSenderException {
|
---|
141 | XPathFactory factory = XPathFactory.newInstance();
|
---|
142 | XPath xpath = factory.newXPath();
|
---|
143 | String status = (String) xpath.compile("/josmticket/@status").evaluate(document, XPathConstants.STRING);
|
---|
144 | if (!"ok".equals(status)) {
|
---|
145 | String message = (String) xpath.compile("/josmticket/error/text()").evaluate(document,
|
---|
146 | XPathConstants.STRING);
|
---|
147 | if (message.isEmpty()) {
|
---|
148 | message = "Error in server response but server did not tell us what happened.";
|
---|
149 | }
|
---|
150 | throw new BugReportSenderException(message);
|
---|
151 | }
|
---|
152 |
|
---|
153 | String token = (String) xpath.compile("/josmticket/preparedid/text()")
|
---|
154 | .evaluate(document, XPathConstants.STRING);
|
---|
155 | if (token.isEmpty()) {
|
---|
156 | throw new BugReportSenderException("Server did not respond with a prepared id.");
|
---|
157 | }
|
---|
158 | return token;
|
---|
159 | }
|
---|
160 |
|
---|
161 | /**
|
---|
162 | * Returns the error message that could have occurred during bug sending.
|
---|
163 | * @return the error message, or {@code null} if successful
|
---|
164 | */
|
---|
165 | public final String getErrorMessage() {
|
---|
166 | return errorMessage;
|
---|
167 | }
|
---|
168 |
|
---|
169 | private static class BugReportSenderException extends Exception {
|
---|
170 | BugReportSenderException(String message) {
|
---|
171 | super(message);
|
---|
172 | }
|
---|
173 |
|
---|
174 | BugReportSenderException(Throwable cause) {
|
---|
175 | super(cause);
|
---|
176 | }
|
---|
177 | }
|
---|
178 |
|
---|
179 | /**
|
---|
180 | * Opens the bug report window on the JOSM server.
|
---|
181 | * @param statusText The status text to send along to the server.
|
---|
182 | * @return bug report sender started thread
|
---|
183 | */
|
---|
184 | public static BugReportSender reportBug(String statusText) {
|
---|
185 | BugReportSender sender = new BugReportSender(statusText);
|
---|
186 | sender.start();
|
---|
187 | return sender;
|
---|
188 | }
|
---|
189 |
|
---|
190 | /**
|
---|
191 | * Sets the {@link BugReportSendingHandler} for bug report sender.
|
---|
192 | * @param bugReportSendingHandler the handler in charge of completing the bug report submission and handle errors. Must not be null
|
---|
193 | * @since 12790
|
---|
194 | */
|
---|
195 | public static void setBugReportSendingHandler(BugReportSendingHandler bugReportSendingHandler) {
|
---|
196 | handler = Objects.requireNonNull(bugReportSendingHandler, "bugReportSendingHandler");
|
---|
197 | }
|
---|
198 | }
|
---|