/******************************************************************************* * Copyright (c) 2011 SpringSource, a divison of VMware, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * SpringSource, a division of VMware, Inc. - initial API and implementation *******************************************************************************/ package org.eclipse.virgo.ide.jdt.internal.core.classpath; import java.util.Queue; import java.util.Set; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicInteger; import org.eclipse.core.resources.WorkspaceJob; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; 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.Job; import org.eclipse.core.runtime.jobs.JobChangeAdapter; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.osgi.util.NLS; import org.eclipse.ui.statushandlers.StatusManager; import org.eclipse.virgo.ide.jdt.core.JdtCorePlugin; import org.eclipse.virgo.ide.jdt.internal.core.util.ClasspathUtils; import org.eclipse.virgo.ide.manifest.core.IBundleManifestChangeListener.Type; /** * {@link WorkspaceJob} that triggers the class path container refresh. * * @author Christian Dupuis * @since 1.0.0 */ public class ServerClasspathContainerUpdateJob extends WorkspaceJob { /** Internal cache of scheduled and <b>unfinished</b> update jobs */ private static final Queue<IJavaProject> SCHEDULED_PROJECTS = new ConcurrentLinkedQueue<IJavaProject>(); /** * Manages the jobs, so there will not be a huge amount of concurrent * updates */ private static LimitConcurrentClasspathUpdatesListener limitConcurrentClasspathUpdatesListener = new LimitConcurrentClasspathUpdatesListener(); /** * The {@link IJavaProject} this jobs should refresh the class path * container for */ private final IJavaProject javaProject; private Set<Type> types; /** * Private constructor to create an instance * * @param javaProject * the {@link IJavaProject} the class path container should be * updated for * @param types * the change types happened to the manifest */ private ServerClasspathContainerUpdateJob(IJavaProject javaProject, Set<Type> types) { super("Updating bundle classpath container for project '" + javaProject.getElementName() + "'"); this.javaProject = javaProject; this.types = types; } /** * Returns the internal {@link IJavaProject} */ public IJavaProject getJavaProject() { return javaProject; } /** * Runs the job in the context of the workspace. Simply delegates refreshing * of the class path container to * {@link ClasspathUtils#updateClasspathContainer(IJavaProject, IProgressMonitor)} * . */ @Override public IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException { if (!javaProject.getProject().isOpen() || monitor.isCanceled()) { return Status.CANCEL_STATUS; } try { ClasspathUtils .updateClasspathContainer(javaProject, types, monitor); } catch (Exception e) { return Status.CANCEL_STATUS; } return new Status(IStatus.OK, JdtCorePlugin.PLUGIN_ID, "Updated SpringSource dm Server classpath container"); } /** * Helper method to schedule a new {@link ServerClasspathContainerUpdateJob} * . * * @param javaProject * the {@link IJavaProject} the class path container should be * updated for * @param types * the change types of the manifest */ public static void scheduleClasspathContainerUpdateJob( IJavaProject javaProject, Set<Type> types) { if (javaProject != null && !SCHEDULED_PROJECTS.contains(javaProject) && types.size() > 0 && ClasspathUtils.hasClasspathContainer(javaProject)) { newClasspathContainerUpdateJob(javaProject, types); } } /** * Creates a new instance of {@link ServerClasspathContainerUpdateJob} and * configures required properties and schedules it to the workbench. */ private static ServerClasspathContainerUpdateJob newClasspathContainerUpdateJob( IJavaProject javaProject, Set<Type> types) { ServerClasspathContainerUpdateJob job = new ServerClasspathContainerUpdateJob( javaProject, types); // job.setRule(ResourcesPlugin.getWorkspace().getRuleFactory().buildRule()); job.setPriority(Job.BUILD); job.setSystem(true); job.addJobChangeListener(new DuplicateJobListener()); job.addJobChangeListener(limitConcurrentClasspathUpdatesListener); job.sleep(); limitConcurrentClasspathUpdatesListener.schedule(job); return job; } /** * Internal {@link IJobChangeListener} to detect duplicates in the scheduled * list of {@link ServerClasspathContainerUpdateJob Jobs}. */ private static class DuplicateJobListener extends JobChangeAdapter implements IJobChangeListener { @Override public void done(IJobChangeEvent event) { SCHEDULED_PROJECTS .remove(((ServerClasspathContainerUpdateJob) event.getJob()) .getJavaProject()); } @Override public void scheduled(IJobChangeEvent event) { SCHEDULED_PROJECTS.add(((ServerClasspathContainerUpdateJob) event .getJob()).getJavaProject()); } } /** * Internal {@link IJobChangeListener} to limit the number of concurrent * builds. */ private static class LimitConcurrentClasspathUpdatesListener extends JobChangeAdapter implements IJobChangeListener { /** * Queue with the jobs which are scheduled, used to make sure a limited * amount of projects is being rebuilt at the same time */ private final Queue<ServerClasspathContainerUpdateJob> SCHEDULED_JOBS = new LinkedBlockingQueue<ServerClasspathContainerUpdateJob>(); /** * Maximum number of concurrent jobs, defaults to the number of * available processors */ private int maxNrOfConcurrentJobs = Runtime.getRuntime() .availableProcessors(); /** Holds the number of builds in progress */ private AtomicInteger nrOfBuildingProjects = new AtomicInteger(0); @Override public void done(IJobChangeEvent event) { nrOfBuildingProjects.decrementAndGet(); startNextJob(); } @Override public void running(IJobChangeEvent event) { ServerClasspathContainerUpdateJob job = (ServerClasspathContainerUpdateJob) event .getJob(); StatusManager .getManager() .handle(new Status(IStatus.INFO, JdtCorePlugin.PLUGIN_ID, "Updating classpath of: " + job.javaProject.getProject().getName() + ". Queue size: " + SCHEDULED_JOBS.size())); } @Override public void scheduled(IJobChangeEvent event) { nrOfBuildingProjects.incrementAndGet(); } private void schedule(ServerClasspathContainerUpdateJob job) { SCHEDULED_JOBS.offer(job); startNextJob(); } private void startNextJob() { if (nrOfBuildingProjects.get() < maxNrOfConcurrentJobs) { Job job = SCHEDULED_JOBS.poll(); if (job != null) { // null is returned when queue is empty job.schedule(); } } } } }