]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.aeri.ui.redmine/src/org/simantics/aeri/ui/redmine/RedmineServerConnection.java
Experiment around with AERI in Simantics Products
[simantics/platform.git] / bundles / org.simantics.aeri.ui.redmine / src / org / simantics / aeri / ui / redmine / RedmineServerConnection.java
1 package org.simantics.aeri.ui.redmine;
2
3 import java.io.File;
4 import java.io.IOException;
5 import java.util.List;
6 import java.util.Objects;
7
8 import javax.annotation.PostConstruct;
9 import javax.inject.Inject;
10
11 import org.eclipse.core.runtime.IConfigurationElement;
12 import org.eclipse.core.runtime.IProgressMonitor;
13 import org.eclipse.core.runtime.IStatus;
14 import org.eclipse.e4.core.contexts.IEclipseContext;
15 import org.eclipse.epp.internal.logging.aeri.ide.IServerDescriptor;
16 import org.eclipse.epp.logging.aeri.core.ILink;
17 import org.eclipse.epp.logging.aeri.core.IModelFactory;
18 import org.eclipse.epp.logging.aeri.core.IProblemState;
19 import org.eclipse.epp.logging.aeri.core.IReport;
20 import org.eclipse.epp.logging.aeri.core.IReportProcessor;
21 import org.eclipse.epp.logging.aeri.core.ISendOptions;
22 import org.eclipse.epp.logging.aeri.core.IServerConnection;
23 import org.eclipse.epp.logging.aeri.core.IStackTraceElement;
24 import org.eclipse.epp.logging.aeri.core.ISystemSettings;
25 import org.eclipse.epp.logging.aeri.core.IThrowable;
26 import org.eclipse.epp.logging.aeri.core.ProblemStatus;
27 import org.eclipse.epp.logging.aeri.core.Severity;
28 import org.eclipse.epp.logging.aeri.core.util.Links;
29 import org.eclipse.epp.logging.aeri.core.util.Reports;
30 import org.eclipse.epp.logging.aeri.core.util.Statuses;
31 import org.simantics.aeri.redmine.core.settings.RedmineAERISettings;
32
33 import com.google.common.util.concurrent.AbstractIdleService;
34 import com.taskadapter.redmineapi.Params;
35 import com.taskadapter.redmineapi.RedmineException;
36 import com.taskadapter.redmineapi.RedmineManager;
37 import com.taskadapter.redmineapi.RedmineManagerFactory;
38 import com.taskadapter.redmineapi.bean.Issue;
39 import com.taskadapter.redmineapi.bean.IssueFactory;
40 import com.taskadapter.redmineapi.internal.ResultsWrapper;
41
42 /**
43  * @author jsjani
44  *
45  */
46 public class RedmineServerConnection extends AbstractIdleService implements IServerConnection {
47
48     private static final String LINE_SEPARATOR = System.lineSeparator();
49
50     private final IServerDescriptor server;
51     private final ISystemSettings systemSettings;
52     private final RedmineAERISettings redmineSettings;
53     private final File configurationArea;
54     private final ILink redmineUrl;
55     private final String projectId;
56
57     private RedmineManager redmine;
58
59     @Inject
60     public RedmineServerConnection(IServerDescriptor descriptor, RedmineAERISettings redmineSettings, ISystemSettings system, File configurationArea) {
61         this.systemSettings = Objects.requireNonNull(system);
62         this.redmineSettings = Objects.requireNonNull(redmineSettings);
63         this.configurationArea = Objects.requireNonNull(configurationArea);
64         this.server = Objects.requireNonNull(descriptor);
65         this.projectId = Objects.requireNonNull(getProjectIdParameter(descriptor));
66         
67         this.redmineUrl = Links.Link(descriptor, "org.simantics.aeri.ui.redmine.redmine.link");
68     }
69     
70     private static String getProjectIdParameter(IServerDescriptor server) {
71         IConfigurationElement element = server.getConfigurationElement();
72         IConfigurationElement[] children = element.getChildren("parameter"); //$NON-NLS-1$
73         for (IConfigurationElement child : children) {
74             if ("org.simantics.aeri.ui.redmine.redmine.project_id".equals(child.getAttribute("key"))) {
75                 return child.getAttribute("value");
76             }
77         }
78         return null;
79     }
80     
81     @PostConstruct
82     private void e4Start() {
83         startAsync();
84     }
85
86     @Override
87     public IProblemState interested(IStatus status, IEclipseContext eventScopeContext, IProgressMonitor monitor) {
88         IProblemState res = IModelFactory.eINSTANCE.createProblemState();
89         
90         String fingerprint = Statuses.traceIdentityHash(status);
91         
92         Params params = new Params().add("project_id", projectId).add("f[]", "subject").add("op[subject]", "~").add("v[subject][]", fingerprint);
93         try {
94             ResultsWrapper<Issue> results = redmine.getIssueManager().getIssues(params);
95             List<Issue> issues = results.getResults();
96             if (issues.isEmpty()) {
97                 res.setStatus(ProblemStatus.NEW);
98                 res.setMessage("New issue found! Please send a report");
99             } else {
100                 res.setStatus(ProblemStatus.CONFIRMED);
101                 
102                 StringBuilder sb = new StringBuilder();
103                 sb.append("This issue has already been reported ").append(issues.size()).append(" times:").append(LINE_SEPARATOR).append(LINE_SEPARATOR);
104                 
105                 for (Issue issue : issues) {
106                     String bugurl = redmineUrl.getHref() + "/issues/" + issue.getId();
107                     sb.append("<a href=\"" + bugurl + "\">#" + issue.getId() + "</a>").append(LINE_SEPARATOR);
108                 }
109                 sb.append(LINE_SEPARATOR);
110                 sb.append("Either edit an existing issue listed above or send a new report.");
111                 res.setMessage(sb.toString());
112             }
113         } catch (RedmineException e) {
114             e.printStackTrace();
115             res.setStatus(ProblemStatus.FAILURE);
116             res.setMessage(e.getMessage());
117         }
118         return res;
119     }
120
121     /**
122      * 
123      * See <a href="http://www.redmine.org/projects/redmine/wiki/Rest_IssueStatuses">Redmine REST API</a>
124      * 
125      * @param issue
126      * @return
127      */
128     private static ProblemStatus resolveStatus(Issue issue) {
129         switch (issue.getStatusId()) {
130         case 1:
131         case 2:
132             return ProblemStatus.CONFIRMED;
133         case 3:
134         case 5:
135             return ProblemStatus.FIXED;
136         case 4:
137             return ProblemStatus.NEEDINFO;
138         case 6:
139             return ProblemStatus.WONTFIX;
140         case 8:
141             return ProblemStatus.NEEDINFO;
142         default:
143             return ProblemStatus.NEW;
144         }
145     }
146
147     @Override
148     public IReport transform(IStatus status, IEclipseContext context) {
149         ISendOptions options = context.get(ISendOptions.class);
150         IReport report = Reports.newReport(status);
151         report.setComment(options.getComment());
152         for (IReportProcessor processor : options.getEnabledProcessors()) {
153             processor.process(report, status, context);
154         }
155         report.setAnonymousId(options.getReporterId());
156         report.setName(options.getReporterName());
157         report.setEmail(options.getReporterEmail());
158         report.setSeverity(options.getSeverity());
159         return report;
160     }
161
162     @Override
163     public IProblemState submit(IStatus status, IEclipseContext context, IProgressMonitor monitor) throws IOException {
164         IReport report = transform(status, context);
165         String fingerprint = Statuses.traceIdentityHash(status);
166         IProblemState response = upload(report, monitor, fingerprint);
167
168         String message = response.getMessage();
169         response.setMessage(message);
170         return response;
171     }
172     
173     private static void appendStackTrace(IThrowable throwable, StringBuilder builder) {
174         builder.append(String.format("%s: %s", throwable.getClassName(), throwable.getMessage())).append(LINE_SEPARATOR);
175         for (IStackTraceElement element : throwable.getStackTrace()) {
176             builder.append(String.format("\t at %s.%s(%s:%s)", element.getClassName(), element.getMethodName(),
177                     element.getFileName(), element.getLineNumber())).append(LINE_SEPARATOR);
178         }
179         IThrowable cause = throwable.getCause();
180         if (cause != null) {
181             builder.append("Caused by: ");
182             appendStackTrace(cause, builder);
183         }
184     }
185
186     private IProblemState upload(IReport report, IProgressMonitor monitor, String fingerprint) {
187         IProblemState result = IModelFactory.eINSTANCE.createProblemState();
188         org.eclipse.epp.logging.aeri.core.IStatus status = report.getStatus();
189         String issueName = status.getMessage();
190         String description = generateReportMessage(report);
191         
192         try {
193             Issue newIssue = IssueFactory.create(27, issueName + " [AERI=" + fingerprint+"]");
194             newIssue.setDescription(description);
195             newIssue.setPriorityId(resolvePriority(report));
196             
197             Issue createdIssue = redmine.getIssueManager().createIssue(newIssue);
198             Integer id = createdIssue.getId();
199             Links.addLink(result, Links.REL_SUBMISSION, redmineUrl.getHref() + "/issues/" + id, "Submission URL: ");
200             
201             String message = "View reported issue: <a href=\"" + result.getLinks().iterator().next().getValue().getHref() + "\">#" + id + "</a>"; 
202             result.setMessage(message);
203         } catch (RedmineException e) {
204             e.printStackTrace();
205             result.setStatus(ProblemStatus.FAILURE);
206             result.setMessage(e.getMessage());
207         }
208         return result;
209     }
210     
211     private String generateReportMessage(IReport report) {
212         String comment = report.getComment();
213         org.eclipse.epp.logging.aeri.core.IStatus status = report.getStatus();
214         
215         StringBuilder sb = new StringBuilder();
216         if (comment != null)
217             sb.append(comment).append("\n");
218         
219         sb.append(LINE_SEPARATOR);
220         sb.append("h2. Product Information").append(LINE_SEPARATOR);
221         
222         sb.append("\n<pre>\n");
223         appendProductInformation(report, sb);
224         sb.append("</pre>\n");
225         
226         sb.append("h2. Exception stack trace").append(LINE_SEPARATOR);
227         sb.append("\n<pre>\n");
228         appendStackTrace(status.getException(), sb);
229         sb.append("</pre>\n");
230         
231         return sb.toString();
232     }
233
234     
235     /**
236      *  eclipseBuildId      some-id
237         eclipseProduct      org.simantics.desktop.product.desktopProduct
238         javaRuntimeVersion  1.8.0_111-b14
239         osgiWs              win32
240         osgiOs              win32
241         osgiOsVersion       6.1.0
242         osgiArch            x86_64
243      */
244     private void appendProductInformation(IReport report, StringBuilder sb) {
245         sb.append(LINE_SEPARATOR);
246         sb.append("eclipseProduct     ").append(report.getEclipseProduct()).append(LINE_SEPARATOR);
247         sb.append("eclipseBuildId     ").append(report.getEclipseBuildId()).append(LINE_SEPARATOR);
248         sb.append("javaRuntimeVersion ").append(report.getJavaRuntimeVersion()).append(LINE_SEPARATOR);
249         sb.append("osgiWs             ").append(report.getOsgiWs()).append(LINE_SEPARATOR);
250         sb.append("osgiOs             ").append(report.getOsgiOs()).append(LINE_SEPARATOR);
251         sb.append("osgiOsVersion      ").append(report.getOsgiOsVersion()).append(LINE_SEPARATOR);
252         sb.append("osgiArch           ").append(report.getOsgiArch()).append(LINE_SEPARATOR);
253         sb.append(LINE_SEPARATOR);
254     }
255
256     /**
257      * See <a href="http://www.redmine.org/projects/redmine/wiki/Rest_Enumerations">Redmine REST API</a>
258      * 
259      * @param report
260      * @return
261      */
262     private Integer resolvePriority(IReport report) {
263         Severity severity = report.getSeverity();
264         switch (severity.getValue()) {
265         case Severity.CRITICAL_VALUE:
266             return 7;
267         case Severity.MAJOR_VALUE:
268             return 5;
269         case Severity.MINOR_VALUE:
270             return 13;
271         default:
272             return 4;
273         }
274     }
275
276     @Override
277     public void discarded(IStatus status, IEclipseContext eventScopeContext) {
278         // do nothing
279     }
280
281     @Override
282     protected void startUp() throws Exception {
283         this.redmine = RedmineManagerFactory.createWithApiKey(redmineUrl.getHref(), redmineSettings.getApiKey());
284     }
285
286     @Override
287     protected void shutDown() throws Exception {
288     }
289
290 }