/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * Copyright (c) 2013, 6WIND S.A. All rights reserved.                 *
 *                                                                     *
 * This file is part of the Jenkins Lockable Resources Plugin and is   *
 * published under the MIT license.                                    *
 *                                                                     *
 * See the "LICENSE.txt" file for more information.                    *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
package org.jenkins.plugins.lockableresources.queue;

import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import hudson.matrix.MatrixConfiguration;
import hudson.matrix.MatrixProject;
import hudson.model.AbstractProject;
import hudson.model.Job;
import hudson.model.Queue;
import hudson.model.queue.QueueTaskDispatcher;
import hudson.model.queue.CauseOfBlockage;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.lang.time.DateUtils;
import org.jenkins.plugins.lockableresources.LockableResource;
import org.jenkins.plugins.lockableresources.LockableResourcesManager;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;

@Extension
public class LockableResourcesQueueTaskDispatcher extends QueueTaskDispatcher {

	private transient Map<Queue.Item,Date> lastLogged = new HashMap<>();

	static final Logger LOGGER = Logger
			.getLogger(LockableResourcesQueueTaskDispatcher.class.getName());

	@Override
	public CauseOfBlockage canRun(Queue.Item item) {
		// Skip locking for multiple configuration projects,
		// only the child jobs will actually lock resources.
		if (item.task instanceof MatrixProject)
			return null;

		Job<?, ?> project = Utils.getProject(item);
		if (project == null)
			return null;

		LockableResourcesStruct resources = Utils.requiredResources(project);
		if (resources == null ||
			(resources.required.isEmpty() && resources.label.isEmpty() && resources.getResourceMatchScript() == null)) {
			return null;
		}

		int resourceNumber;
		try {
			resourceNumber = Integer.parseInt(resources.requiredNumber);
		} catch (NumberFormatException e) {
			resourceNumber = 0;
		}

		LOGGER.finest(project.getName() +
			" trying to get resources with these details: " + resources);

		if (resourceNumber > 0 || !resources.label.isEmpty() || resources.getResourceMatchScript() != null) {
			Map<String, Object> params = new HashMap<String, Object>();
			if (item.task instanceof MatrixConfiguration) {
			    MatrixConfiguration matrix = (MatrixConfiguration) item.task;
			    params.putAll(matrix.getCombination());
			}
                        
			final List<LockableResource> selected ;
			try {
				selected = LockableResourcesManager.get().tryQueue(
					resources,
					item.getId(),
					project.getFullName(),
					resourceNumber,
					params,
					LOGGER);
			} catch(ExecutionException ex) {
				Throwable toReport = ex.getCause();
				if (toReport == null) { // We care about the cause only
					toReport = ex;
				}	
				if (LOGGER.isLoggable(Level.WARNING)) {
					if (lastLogged.get(item) == null || lastLogged.get(item).before(DateUtils.addMinutes(new Date(), -30))) {
						lastLogged.put(item, new Date());

						String itemName = project.getFullName() + " (id=" + item.getId() + ")";
						LOGGER.log(Level.WARNING, "Failed to queue item " + itemName, toReport.getMessage());
					}
				}
				
				return new BecauseResourcesQueueFailed(resources, toReport);
			}

			if (selected != null) {
				LOGGER.finest(project.getName() + " reserved resources " + selected);
				return null;
			} else {
				LOGGER.finest(project.getName() + " waiting for resources");
				return new BecauseResourcesLocked(resources);
			}

		} else {
			if (LockableResourcesManager.get().queue(resources.required, item.getId())) {
				LOGGER.finest(project.getName() + " reserved resources " + resources.required);
				return null;
			} else {
				LOGGER.finest(project.getName() + " waiting for resources "
					+ resources.required);
				return new BecauseResourcesLocked(resources);
			}
		}
	}

	public static class BecauseResourcesLocked extends CauseOfBlockage {

		private final LockableResourcesStruct rscStruct;

		public BecauseResourcesLocked(LockableResourcesStruct r) {
			this.rscStruct = r;
		}

		@Override
		public String getShortDescription() {
			if (this.rscStruct.label.isEmpty())
				return "Waiting for resources " + rscStruct.required.toString();
			else
				return "Waiting for resources with label " + rscStruct.label;
		}
	}
        
	// Only for UI
	@Restricted(NoExternalUse.class)
	public static class BecauseResourcesQueueFailed extends CauseOfBlockage {
		
		@NonNull
		private final LockableResourcesStruct resources;
		@NonNull
		private final Throwable cause;
		
		public BecauseResourcesQueueFailed(@NonNull LockableResourcesStruct resources, @NonNull Throwable cause) {
			this.cause = cause;
			this.resources = resources;
		}

		@Override
		public String getShortDescription() {
			//TODO: Just a copy-paste from BecauseResourcesLocked, seems strange
			String resourceInfo = (resources.label.isEmpty()) ? resources.required.toString() : "with label " + resources.label;
			return "Execution failed while acquiring the resource " + resourceInfo + ". " + cause.getMessage();
		}
	}
}

@@ -628,24 +628,107 @@
 ces.queue;%0A%0A
+import com.google.common.cache.Cache;%0Aimport com.google.common.cache.CacheBuilder;%0A
 import edu.u
@@ -763,16 +763,16 @@
 onNull;%0A
-
 import h
@@ -1184,24 +1184,62 @@
 nException;%0A
+import java.util.concurrent.TimeUnit;%0A
 import java.
@@ -1681,22 +1681,18 @@
 ent 
-Map%3CQueue.Item
+Cache%3CLong
 ,Dat
@@ -1711,21 +1711,78 @@
 d = 
-new HashMap%3C%3E
+CacheBuilder.newBuilder().expireAfterWrite(30, TimeUnit.MINUTES).build
 ();%0A
@@ -3516,92 +3516,39 @@
 .get
-(item) == null %7C%7C lastLogged.get(item).before(DateUtils.addMinutes(new Date(), -30))
+IfPresent(item.getId()) == null
 ) %7B%0A
@@ -3572,16 +3572,24 @@
 put(item
+.getId()
 , new Da

Rework to use a Cache.