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 results = redmine.getIssueManager().getIssues(params); List 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("#" + issue.getId() + "").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 Redmine REST API * * @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: #" + id + ""; 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
\n");
        appendProductInformation(report, sb);
        sb.append("
\n"); sb.append("h2. Exception stack trace").append(LINE_SEPARATOR); sb.append("\n
\n");
        appendStackTrace(status.getException(), sb);
        sb.append("
\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 Redmine REST API * * @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 { } }