/** * Copyright 2014 SAP AG * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.spotter.eclipse.ui.jobs; import java.util.Map; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.QualifiedName; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.IAction; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.navigator.CommonViewer; import org.eclipse.ui.plugin.AbstractUIPlugin; import org.eclipse.ui.progress.IProgressConstants; import org.spotter.eclipse.ui.Activator; import org.spotter.eclipse.ui.ServiceClientWrapper; import org.spotter.eclipse.ui.handlers.RunHandler; import org.spotter.eclipse.ui.navigator.SpotterProjectResults; import org.spotter.eclipse.ui.navigator.SpotterProjectRunResult; import org.spotter.eclipse.ui.util.DialogUtils; import org.spotter.shared.status.DiagnosisProgress; import org.spotter.shared.status.SpotterProgress; /** * A job to run a DynamicSpotter diagnosis which can be scheduled by the job * manager. This job updates the progress monitor according to the progress of * the DynamicSpotter run that is performed. * * @author Denis Knoepfle * */ public class DynamicSpotterRunJob extends Job { /** * The unique key used to set the job id property of this job. */ public static final QualifiedName JOB_ID_KEY = new QualifiedName(Activator.PLUGIN_ID, "jobId"); /** * The identifier used to identify this job as a member of the job family of * DS runs. */ public static final String DS_RUN_JOB_FAMILY = "org.spotter.eclipse.ui.jobs.DynamicSpotterRunJob"; private static final String ICON_PATH = "icons/diagnosis.png"; //$NON-NLS-1$ private static final int SLEEP_TIME_MILLIS = 1000; private static final int HUNDRED_PERCENT = 100; private static final int KEY_HASH_LENGTH = 7; private static final String MSG_RUN_FINISH = "Finished the DynamicSpotter diagnosis successfully!"; private static final String MSG_RUN_ERROR = "Aborted the DynamicSpotter diagnosis due to an error!"; private static final String MSG_LOST_CONNECTION = "Lost connection to DS Service!"; private static final String MSG_CANCELLED = "The progress report has been cancelled and you will " + "not be informed when the run is finished. DynamicSpotter will continue to run on the " + "server though (cancellation of a running diagnosis is not implemented yet)."; private final IProject project; private final long jobId; private String currentProblem; // determines whether a cancellation will be silent or reported to the user private boolean silentCancel; /** * Create a new job for the given project and job id. * * @param project * The project the job is for * @param jobId * The job id of the new job * @param timestamp * The timestamp when the job was initiated */ public DynamicSpotterRunJob(final IProject project, final long jobId, long timestamp) { super("DynamicSpotter Diagnosis '" + project.getName() + "'"); this.project = project; this.jobId = jobId; this.silentCancel = false; ImageDescriptor imageDescriptor = AbstractUIPlugin.imageDescriptorFromPlugin(Activator.PLUGIN_ID, ICON_PATH); setProperty(IProgressConstants.ICON_PROPERTY, imageDescriptor); IAction gotoAction = new Action("Results") { public void run() { revealResults(); } }; setProperty(IProgressConstants.ACTION_PROPERTY, gotoAction); setProperty(JOB_ID_KEY, String.valueOf(jobId)); setPriority(LONG); setUser(true); if (!JobsContainer.registerJobId(project, jobId, timestamp)) { DialogUtils.openError(RunHandler.DIALOG_TITLE, "There was an error when saving the job id. You may not access the results of the diagnosis run."); } } /** * @return <code>true</code> if cancellation is silent, otherwise * <code>false</code> */ public boolean isSilentCancel() { return silentCancel; } /** * Sets whether cancellation is silent or reported to the user. * * @param silentCancel * <code>true</code> for silent, otherwise <code>false</code> */ public void setSilentCancel(boolean silentCancel) { this.silentCancel = silentCancel; } /** * Returns <code>true</code> only if the family is * {@link #DS_RUN_JOB_FAMILY}, the family of DS run jobs. * * @param family * the job family identifier * @return <code>true</code> for DS run job family, <code>false</code> * otherwise */ @Override public boolean belongsTo(Object family) { return DS_RUN_JOB_FAMILY.equals(family); } @Override protected IStatus run(IProgressMonitor monitor) { currentProblem = null; monitor.beginTask("DynamicSpotter Diagnosis '" + project.getName() + "'", IProgressMonitor.UNKNOWN); ServiceClientWrapper client = Activator.getDefault().getClient(project.getName()); while (client.isRunning(true)) { if (monitor.isCanceled()) { return onUserCancelledJob(null); } Long currentJobId = client.getCurrentJobId(); // check if the job is still being processed if (currentJobId != null && currentJobId == jobId) { updateCurrentRun(client, monitor); try { Thread.sleep(SLEEP_TIME_MILLIS); } catch (InterruptedException e) { return onUserCancelledJob(e); } } else { // assume our job is due break; } } monitor.done(); boolean isConnectionIssue = client.isConnectionIssue(); Exception runException = client.getLastRunException(true); isConnectionIssue |= client.isConnectionIssue(); if (isConnectionIssue) { DialogUtils.openWarning(RunHandler.DIALOG_TITLE, MSG_LOST_CONNECTION); return new Status(Status.OK, Activator.PLUGIN_ID, Status.OK, MSG_LOST_CONNECTION, null); } onFinishedJob(runException); // keep the finished job in the progress view only if // it is not running in the progress dialog Boolean inDialog = (Boolean) getProperty(IProgressConstants.PROPERTY_IN_DIALOG); if (inDialog != null && !inDialog.booleanValue()) { setProperty(IProgressConstants.KEEP_PROPERTY, Boolean.TRUE); } return Status.OK_STATUS; } /** * Creates a progress string representing the progress of the given problem. * * @param spotterProgress * the overall spotter progress to use for the lookup * @param problemId * the id of the concrete problem to create the progress for * @param omitProblemName * whether to omit the problem name in the resulting string * @return a progress string */ public static String createProgressString(SpotterProgress spotterProgress, String problemId, boolean omitProblemName) { if (spotterProgress == null || problemId == null) { return null; } DiagnosisProgress diagProgress = spotterProgress.getProgress(problemId); String estimation = String.format("%.1f", HUNDRED_PERCENT * diagProgress.getEstimatedProgress()); String duration = String.valueOf(diagProgress.getEstimatedRemainingDuration()); String problemName = ""; if (!omitProblemName) { String keyHashTag = createKeyHashTag(problemId); problemName = diagProgress.getName() + "-" + keyHashTag; } String estimates = ": "; switch (diagProgress.getStatus()) { case PENDING: break; case DETECTED: case NOT_DETECTED: estimates = " (100.0 %, 0s remaining): "; break; default: estimates = " (" + estimation + " %, " + duration + "s remaining): "; break; } String progressString = problemName + estimates + diagProgress.getStatus(); return progressString; } private static String createKeyHashTag(String key) { if (key.length() < KEY_HASH_LENGTH) { return key; } else { return key.substring(0, KEY_HASH_LENGTH); } } private void updateCurrentRun(ServiceClientWrapper client, IProgressMonitor monitor) { SpotterProgress spotterProgress = client.getCurrentProgressReport(); if (spotterProgress == null || spotterProgress.getProblemProgressMapping() == null) { return; } Map<String, DiagnosisProgress> progressAll = spotterProgress.getProblemProgressMapping(); if (progressAll.isEmpty()) { return; } currentProblem = spotterProgress.getCurrentProblem(); if (currentProblem != null && progressAll.containsKey(currentProblem)) { monitor.setTaskName(createProgressString(spotterProgress, currentProblem, false)); } } private IStatus onUserCancelledJob(Exception exception) { if (silentCancel) { return Status.OK_STATUS; } DialogUtils.openInformation(RunHandler.DIALOG_TITLE, MSG_CANCELLED); return new Status(Status.CANCEL, Activator.PLUGIN_ID, MSG_CANCELLED, exception); } private void onFinishedJob(final Exception runException) { final Activator activator = Activator.getDefault(); final Display display = PlatformUI.getWorkbench().getDisplay(); display.asyncExec(new Runnable() { @Override public void run() { Map<String, SpotterProjectResults> results = activator.getProjectHistoryElements(); SpotterProjectResults projectResultsNode = results.get(project.getName()); projectResultsNode.refreshChildren(); CommonViewer viewer = activator.getNavigatorViewer(); if (!viewer.isBusy()) { viewer.refresh(projectResultsNode); viewer.expandToLevel(projectResultsNode, 1); } if (runException == null) { DialogUtils.openAsyncInformation(RunHandler.DIALOG_TITLE, MSG_RUN_FINISH); } else { String exceptionMsg = runException.getLocalizedMessage(); if (exceptionMsg == null || exceptionMsg.isEmpty()) { exceptionMsg = runException.getClass().getSimpleName(); } String msg = DialogUtils.appendCause(MSG_RUN_ERROR, exceptionMsg, true); DialogUtils.openWarning(RunHandler.DIALOG_TITLE, msg); } revealResults(); } }); } private void revealResults() { Activator activator = Activator.getDefault(); Map<String, SpotterProjectResults> results = activator.getProjectHistoryElements(); SpotterProjectResults projectResultsNode = results.get(project.getName()); if (projectResultsNode != null) { SpotterProjectRunResult runNode = projectResultsNode.getRunResultForJobId(jobId); if (runNode != null) { CommonViewer viewer = activator.getNavigatorViewer(); viewer.getControl().setFocus(); viewer.setSelection(new StructuredSelection(runNode), true); } } } }