source: josm/trunk/src/org/openstreetmap/josm/tools/bugreport/BugReportSender.java@ 14746

Last change on this file since 14746 was 14746, checked in by simon04, 5 years ago

Refactoring: use StandardCharsets

  • Property svn:eol-style set to native
File size: 7.2 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.tools.bugreport;
3
4import java.io.IOException;
5import java.io.InputStream;
6import java.net.URL;
7import java.nio.charset.StandardCharsets;
8import java.util.Base64;
9import java.util.Objects;
10
11import javax.xml.parsers.ParserConfigurationException;
12import javax.xml.xpath.XPath;
13import javax.xml.xpath.XPathConstants;
14import javax.xml.xpath.XPathExpressionException;
15import javax.xml.xpath.XPathFactory;
16
17import org.openstreetmap.josm.spi.preferences.Config;
18import org.openstreetmap.josm.tools.HttpClient;
19import org.openstreetmap.josm.tools.HttpClient.Response;
20import org.openstreetmap.josm.tools.Logging;
21import org.openstreetmap.josm.tools.OpenBrowser;
22import org.openstreetmap.josm.tools.Utils;
23import org.openstreetmap.josm.tools.XmlUtils;
24import org.w3c.dom.Document;
25import 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 */
35public 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}
Note: See TracBrowser for help on using the repository browser.