--- /dev/null
+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 {
+ }
+
+}