/******************************************************************************* * Copyright (c) 2004, 2006 * Thomas Hallgren, Kenneth Olwing, Mitch Sonies * Pontus Rydin, Nils Unden, Peer Torngren * The code, documentation and other materials contained herein have been * licensed under the Eclipse Public License - v 1.0 by the individual * copyright holders listed above, as Initial Contributors under such license. * The text of such license is available at www.eclipse.org. *******************************************************************************/ package org.eclipse.buckminster.core.materializer; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import org.eclipse.buckminster.core.CorePlugin; import org.eclipse.buckminster.core.Messages; import org.eclipse.buckminster.core.metadata.model.BillOfMaterials; import org.eclipse.buckminster.core.metadata.model.Resolution; import org.eclipse.buckminster.core.mspec.model.MaterializationSpec; import org.eclipse.buckminster.runtime.Buckminster; import org.eclipse.buckminster.runtime.BuckminsterException; import org.eclipse.buckminster.runtime.BuckminsterPreferences; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.IJobChangeEvent; import org.eclipse.core.runtime.jobs.IJobChangeListener; import org.eclipse.core.runtime.jobs.IJobManager; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.core.runtime.jobs.JobChangeAdapter; import org.eclipse.core.runtime.preferences.IEclipsePreferences; import org.osgi.service.prefs.BackingStoreException; /** * A job that will materialize according to specifications. * * @author Thomas Hallgren */ public class MaterializationJob extends Job { public static final String MAX_PARALLEL_JOBS = "maxParallelMaterializationJobs"; //$NON-NLS-1$ public static final int MAX_PARALLEL_JOBS_DEFAULT = 4; public static int getMaxParallelJobs() { return BuckminsterPreferences.getNode().getInt(MAX_PARALLEL_JOBS, MAX_PARALLEL_JOBS_DEFAULT); } public static void run(MaterializationContext context) throws CoreException { try { MaterializationJob mbJob = new MaterializationJob(context); mbJob.schedule(); mbJob.join(); // long running IStatus status = mbJob.getResult(); if (status.getSeverity() == IStatus.CANCEL) throw new OperationCanceledException(); // We wait to give the event manager a chance to deliver all // events while the JobBlocker still active. This gives us // a chance to add dynamic dependencies to projects // Thread.sleep(3000); } catch (InterruptedException e) { throw new OperationCanceledException(); } catch (OperationCanceledException e) { throw e; } catch (Throwable t) { throw BuckminsterException.wrap(t); } } /** * Runs this job immediately without scheduling. This method is intended to * be called when the materialization is done from the GUI and uses a * <code>IRunnableWithProgress</code> * * @param context * @param monitor * @throws CoreException */ public static void runDelegated(MaterializationContext context, IProgressMonitor monitor) throws CoreException { MaterializationJob mbJob = new MaterializationJob(context); mbJob.internalRun(monitor, false); } public static void setMaxParallelJobs(int maxJobs) { if (maxJobs > 0 && maxJobs <= 20) BuckminsterPreferences.getNode().putInt(MAX_PARALLEL_JOBS, maxJobs); } public static void setUp() { IEclipsePreferences defaultNode = BuckminsterPreferences.getDefaultNode(); defaultNode.putInt(MAX_PARALLEL_JOBS, MAX_PARALLEL_JOBS_DEFAULT); try { defaultNode.flush(); } catch (BackingStoreException e) { Buckminster.getLogger().error(e, e.toString()); } } private final MaterializationContext context; public MaterializationJob(MaterializationContext ctx) { super(Messages.Materializing); context = ctx; // Report using the standard job reporter. // this.setSystem(false); this.setUser(false); this.setPriority(LONG); } @Override public IStatus run(IProgressMonitor monitor) { try { internalRun(monitor, true); } catch (CoreException e) { CorePlugin.getLogger().error(e, e.getMessage()); } catch (OperationCanceledException e) { return Status.CANCEL_STATUS; } context.emitWarningAndErrorTags(); return Status.OK_STATUS; } protected MaterializationContext getMaterializationContext() { return context; } protected void internalRun(final IProgressMonitor monitor, boolean waitForCompletion) throws CoreException { BillOfMaterials bom = context.getBillOfMaterials(); Queue<MaterializerJob> allJobs = prepareJobs(monitor, bom); if (allJobs != null) { triggerJobs(monitor, allJobs); waitForJobs(monitor, allJobs, bom); } if (context.getStatus().getSeverity() == IStatus.ERROR) throw BuckminsterException.wrap(context.getStatus()); InstallerJob installerJob = new InstallerJob(context, !waitForCompletion); installerJob.schedule(); if (waitForCompletion) { try { installerJob.join(); } catch (InterruptedException e) { throw new OperationCanceledException(); } } } protected Queue<MaterializerJob> prepareJobs(IProgressMonitor monitor, BillOfMaterials bom) throws CoreException { CorePlugin corePlugin = CorePlugin.getDefault(); Map<String, List<Resolution>> resPerMat = new LinkedHashMap<String, List<Resolution>>(); MaterializationSpec mspec = context.getMaterializationSpec(); for (Resolution cr : bom.findMaterializationCandidates(context, mspec)) { String materializer = mspec.getMaterializerID(cr); List<Resolution> crs = resPerMat.get(materializer); if (crs == null) { crs = new ArrayList<Resolution>(); resPerMat.put(materializer, crs); } crs.add(cr); } if (resPerMat.size() == 0) return null; final Queue<MaterializerJob> allJobs = new ConcurrentLinkedQueue<MaterializerJob>(); for (Map.Entry<String, List<Resolution>> entry : resPerMat.entrySet()) { IMaterializer materializer = corePlugin.getMaterializer(entry.getKey()); List<Resolution> resolutions = entry.getValue(); if (materializer.canWorkInParallel()) { // Start one job for each resolution // for (Resolution res : resolutions) allJobs.offer(new MaterializerJob(entry.getKey(), materializer, Collections.singletonList(res), context)); } else allJobs.offer(new MaterializerJob(entry.getKey(), materializer, resolutions, context)); } return allJobs; } protected void triggerJobs(final IProgressMonitor monitor, final Queue<MaterializerJob> allJobs) { // -- Schedule at most maxParallelJobs. After that, let the // termination of // a job schedule a new one until the queue is empty. // // This is the listener that schedule a new job on termination of // another // IJobChangeListener listener = new JobChangeAdapter() { @Override public void aboutToRun(IJobChangeEvent event) { if (monitor.isCanceled() || (!context.isContinueOnError() && context.getStatus().getSeverity() == IStatus.ERROR)) cancel(); } @Override public void done(IJobChangeEvent event) { if (!monitor.isCanceled()) { MaterializerJob mjob = allJobs.poll(); if (mjob != null) { mjob.addJobChangeListener(this); mjob.schedule(); } } } }; int maxJobs = context.getMaxParallelJobs(); for (int idx = 0; idx < maxJobs; ++idx) { MaterializerJob job = allJobs.poll(); if (job == null) break; job.addJobChangeListener(listener); job.schedule(); } } protected void waitForJobs(IProgressMonitor monitor, Queue<MaterializerJob> allJobs, BillOfMaterials bom) throws CoreException { // Wait until all jobs have completed // IJobManager jobManager = Job.getJobManager(); try { jobManager.join(context, monitor); } catch (OperationCanceledException e) { jobManager.cancel(context); allJobs.clear(); throw e; } catch (InterruptedException e) { jobManager.cancel(context); allJobs.clear(); throw new OperationCanceledException(); } } }