]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - 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
diff --git a/bundles/org.simantics.aeri.ui.redmine/src/org/simantics/aeri/ui/redmine/RedmineServerConnection.java b/bundles/org.simantics.aeri.ui.redmine/src/org/simantics/aeri/ui/redmine/RedmineServerConnection.java
new file mode 100644 (file)
index 0000000..8548b7a
--- /dev/null
@@ -0,0 +1,290 @@
+package org.simantics.aeri.ui.redmine;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.Objects;
+
+import javax.annotation.PostConstruct;
+import javax.inject.Inject;
+
+import org.eclipse.core.runtime.IConfigurationElement;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.e4.core.contexts.IEclipseContext;
+import org.eclipse.epp.internal.logging.aeri.ide.IServerDescriptor;
+import org.eclipse.epp.logging.aeri.core.ILink;
+import org.eclipse.epp.logging.aeri.core.IModelFactory;
+import org.eclipse.epp.logging.aeri.core.IProblemState;
+import org.eclipse.epp.logging.aeri.core.IReport;
+import org.eclipse.epp.logging.aeri.core.IReportProcessor;
+import org.eclipse.epp.logging.aeri.core.ISendOptions;
+import org.eclipse.epp.logging.aeri.core.IServerConnection;
+import org.eclipse.epp.logging.aeri.core.IStackTraceElement;
+import org.eclipse.epp.logging.aeri.core.ISystemSettings;
+import org.eclipse.epp.logging.aeri.core.IThrowable;
+import org.eclipse.epp.logging.aeri.core.ProblemStatus;
+import org.eclipse.epp.logging.aeri.core.Severity;
+import org.eclipse.epp.logging.aeri.core.util.Links;
+import org.eclipse.epp.logging.aeri.core.util.Reports;
+import org.eclipse.epp.logging.aeri.core.util.Statuses;
+import org.simantics.aeri.redmine.core.settings.RedmineAERISettings;
+
+import com.google.common.util.concurrent.AbstractIdleService;
+import com.taskadapter.redmineapi.Params;
+import com.taskadapter.redmineapi.RedmineException;
+import com.taskadapter.redmineapi.RedmineManager;
+import com.taskadapter.redmineapi.RedmineManagerFactory;
+import com.taskadapter.redmineapi.bean.Issue;
+import com.taskadapter.redmineapi.bean.IssueFactory;
+import com.taskadapter.redmineapi.internal.ResultsWrapper;
+
+/**
+ * @author jsjani
+ *
+ */
+public class RedmineServerConnection extends AbstractIdleService implements IServerConnection {
+
+    private static final String LINE_SEPARATOR = System.lineSeparator();
+
+    private final IServerDescriptor server;
+    private final ISystemSettings systemSettings;
+    private final RedmineAERISettings redmineSettings;
+    private final File configurationArea;
+    private final ILink redmineUrl;
+    private final String projectId;
+
+    private RedmineManager redmine;
+
+    @Inject
+    public RedmineServerConnection(IServerDescriptor descriptor, RedmineAERISettings redmineSettings, ISystemSettings system, File configurationArea) {
+        this.systemSettings = Objects.requireNonNull(system);
+        this.redmineSettings = Objects.requireNonNull(redmineSettings);
+        this.configurationArea = Objects.requireNonNull(configurationArea);
+        this.server = Objects.requireNonNull(descriptor);
+        this.projectId = Objects.requireNonNull(getProjectIdParameter(descriptor));
+        
+        this.redmineUrl = Links.Link(descriptor, "org.simantics.aeri.ui.redmine.redmine.link");
+    }
+    
+    private static String getProjectIdParameter(IServerDescriptor server) {
+        IConfigurationElement element = server.getConfigurationElement();
+        IConfigurationElement[] children = element.getChildren("parameter"); //$NON-NLS-1$
+        for (IConfigurationElement child : children) {
+            if ("org.simantics.aeri.ui.redmine.redmine.project_id".equals(child.getAttribute("key"))) {
+                return child.getAttribute("value");
+            }
+        }
+        return null;
+    }
+    
+    @PostConstruct
+    private void e4Start() {
+        startAsync();
+    }
+
+    @Override
+    public IProblemState interested(IStatus status, IEclipseContext eventScopeContext, IProgressMonitor monitor) {
+        IProblemState res = IModelFactory.eINSTANCE.createProblemState();
+        
+        String fingerprint = Statuses.traceIdentityHash(status);
+        
+        Params params = new Params().add("project_id", projectId).add("f[]", "subject").add("op[subject]", "~").add("v[subject][]", fingerprint);
+        try {
+            ResultsWrapper<Issue> results = redmine.getIssueManager().getIssues(params);
+            List<Issue> issues = results.getResults();
+            if (issues.isEmpty()) {
+                res.setStatus(ProblemStatus.NEW);
+                res.setMessage("New issue found! Please send a report");
+            } else {
+                res.setStatus(ProblemStatus.CONFIRMED);
+                
+                StringBuilder sb = new StringBuilder();
+                sb.append("This issue has already been reported ").append(issues.size()).append(" times:").append(LINE_SEPARATOR).append(LINE_SEPARATOR);
+                
+                for (Issue issue : issues) {
+                    String bugurl = redmineUrl.getHref() + "/issues/" + issue.getId();
+                    sb.append("<a href=\"" + bugurl + "\">#" + issue.getId() + "</a>").append(LINE_SEPARATOR);
+                }
+                sb.append(LINE_SEPARATOR);
+                sb.append("Either edit an existing issue listed above or send a new report.");
+                res.setMessage(sb.toString());
+            }
+        } catch (RedmineException e) {
+            e.printStackTrace();
+            res.setStatus(ProblemStatus.FAILURE);
+            res.setMessage(e.getMessage());
+        }
+        return res;
+    }
+
+    /**
+     * 
+     * See <a href="http://www.redmine.org/projects/redmine/wiki/Rest_IssueStatuses">Redmine REST API</a>
+     * 
+     * @param issue
+     * @return
+     */
+    private static ProblemStatus resolveStatus(Issue issue) {
+        switch (issue.getStatusId()) {
+        case 1:
+        case 2:
+            return ProblemStatus.CONFIRMED;
+        case 3:
+        case 5:
+            return ProblemStatus.FIXED;
+        case 4:
+            return ProblemStatus.NEEDINFO;
+        case 6:
+            return ProblemStatus.WONTFIX;
+        case 8:
+            return ProblemStatus.NEEDINFO;
+        default:
+            return ProblemStatus.NEW;
+        }
+    }
+
+    @Override
+    public IReport transform(IStatus status, IEclipseContext context) {
+        ISendOptions options = context.get(ISendOptions.class);
+        IReport report = Reports.newReport(status);
+        report.setComment(options.getComment());
+        for (IReportProcessor processor : options.getEnabledProcessors()) {
+            processor.process(report, status, context);
+        }
+        report.setAnonymousId(options.getReporterId());
+        report.setName(options.getReporterName());
+        report.setEmail(options.getReporterEmail());
+        report.setSeverity(options.getSeverity());
+        return report;
+    }
+
+    @Override
+    public IProblemState submit(IStatus status, IEclipseContext context, IProgressMonitor monitor) throws IOException {
+        IReport report = transform(status, context);
+        String fingerprint = Statuses.traceIdentityHash(status);
+        IProblemState response = upload(report, monitor, fingerprint);
+
+        String message = response.getMessage();
+        response.setMessage(message);
+        return response;
+    }
+    
+    private static void appendStackTrace(IThrowable throwable, StringBuilder builder) {
+        builder.append(String.format("%s: %s", throwable.getClassName(), throwable.getMessage())).append(LINE_SEPARATOR);
+        for (IStackTraceElement element : throwable.getStackTrace()) {
+            builder.append(String.format("\t at %s.%s(%s:%s)", element.getClassName(), element.getMethodName(),
+                    element.getFileName(), element.getLineNumber())).append(LINE_SEPARATOR);
+        }
+        IThrowable cause = throwable.getCause();
+        if (cause != null) {
+            builder.append("Caused by: ");
+            appendStackTrace(cause, builder);
+        }
+    }
+
+    private IProblemState upload(IReport report, IProgressMonitor monitor, String fingerprint) {
+        IProblemState result = IModelFactory.eINSTANCE.createProblemState();
+        org.eclipse.epp.logging.aeri.core.IStatus status = report.getStatus();
+        String issueName = status.getMessage();
+        String description = generateReportMessage(report);
+        
+        try {
+            Issue newIssue = IssueFactory.create(27, issueName + " [AERI=" + fingerprint+"]");
+            newIssue.setDescription(description);
+            newIssue.setPriorityId(resolvePriority(report));
+            
+            Issue createdIssue = redmine.getIssueManager().createIssue(newIssue);
+            Integer id = createdIssue.getId();
+            Links.addLink(result, Links.REL_SUBMISSION, redmineUrl.getHref() + "/issues/" + id, "Submission URL: ");
+            
+            String message = "View reported issue: <a href=\"" + result.getLinks().iterator().next().getValue().getHref() + "\">#" + id + "</a>"; 
+            result.setMessage(message);
+        } catch (RedmineException e) {
+            e.printStackTrace();
+            result.setStatus(ProblemStatus.FAILURE);
+            result.setMessage(e.getMessage());
+        }
+        return result;
+    }
+    
+    private String generateReportMessage(IReport report) {
+        String comment = report.getComment();
+        org.eclipse.epp.logging.aeri.core.IStatus status = report.getStatus();
+        
+        StringBuilder sb = new StringBuilder();
+        if (comment != null)
+            sb.append(comment).append("\n");
+        
+        sb.append(LINE_SEPARATOR);
+        sb.append("h2. Product Information").append(LINE_SEPARATOR);
+        
+        sb.append("\n<pre>\n");
+        appendProductInformation(report, sb);
+        sb.append("</pre>\n");
+        
+        sb.append("h2. Exception stack trace").append(LINE_SEPARATOR);
+        sb.append("\n<pre>\n");
+        appendStackTrace(status.getException(), sb);
+        sb.append("</pre>\n");
+        
+        return sb.toString();
+    }
+
+    
+    /**
+     *  eclipseBuildId      some-id
+        eclipseProduct      org.simantics.desktop.product.desktopProduct
+        javaRuntimeVersion  1.8.0_111-b14
+        osgiWs              win32
+        osgiOs              win32
+        osgiOsVersion       6.1.0
+        osgiArch            x86_64
+     */
+    private void appendProductInformation(IReport report, StringBuilder sb) {
+        sb.append(LINE_SEPARATOR);
+        sb.append("eclipseProduct     ").append(report.getEclipseProduct()).append(LINE_SEPARATOR);
+        sb.append("eclipseBuildId     ").append(report.getEclipseBuildId()).append(LINE_SEPARATOR);
+        sb.append("javaRuntimeVersion ").append(report.getJavaRuntimeVersion()).append(LINE_SEPARATOR);
+        sb.append("osgiWs             ").append(report.getOsgiWs()).append(LINE_SEPARATOR);
+        sb.append("osgiOs             ").append(report.getOsgiOs()).append(LINE_SEPARATOR);
+        sb.append("osgiOsVersion      ").append(report.getOsgiOsVersion()).append(LINE_SEPARATOR);
+        sb.append("osgiArch           ").append(report.getOsgiArch()).append(LINE_SEPARATOR);
+        sb.append(LINE_SEPARATOR);
+    }
+
+    /**
+     * See <a href="http://www.redmine.org/projects/redmine/wiki/Rest_Enumerations">Redmine REST API</a>
+     * 
+     * @param report
+     * @return
+     */
+    private Integer resolvePriority(IReport report) {
+        Severity severity = report.getSeverity();
+        switch (severity.getValue()) {
+        case Severity.CRITICAL_VALUE:
+            return 7;
+        case Severity.MAJOR_VALUE:
+            return 5;
+        case Severity.MINOR_VALUE:
+            return 13;
+        default:
+            return 4;
+        }
+    }
+
+    @Override
+    public void discarded(IStatus status, IEclipseContext eventScopeContext) {
+        // do nothing
+    }
+
+    @Override
+    protected void startUp() throws Exception {
+        this.redmine = RedmineManagerFactory.createWithApiKey(redmineUrl.getHref(), redmineSettings.getApiKey());
+    }
+
+    @Override
+    protected void shutDown() throws Exception {
+    }
+
+}