MapQueue.java 6.18 KB
package fi.codecrew.moya.beans.map;

import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicBoolean;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import fi.codecrew.moya.beans.map.QueueBeanLocal.MapQueueI;
import fi.codecrew.moya.model.EventMap;
import fi.codecrew.moya.model.EventUser;
import fi.codecrew.moya.model.PlaceSlot;
import fi.codecrew.moya.model.map.MapReservationQueueEntry;

public class MapQueue implements MapQueueI {
	private int defaultTimeoutMin = 10;
	private int minimumSlotsInQueue = 1;
	private int reservingSize = 5;
	// private final Set<MapReservationQueueEntry> reserving = new HashSet<>();
	private final Set<EventUser> reserving = Collections.newSetFromMap(new ConcurrentHashMap<>());
	private final PriorityQueue<MapReservationQueueEntry> queue = new PriorityQueue<>();
	private final ConcurrentMap<EventUser, MapReservationQueueEntry> queEntries = new ConcurrentHashMap<>();
	private final Integer mapId;
	//private final Object reservingLock = new Object();

	private static final Logger logger = LoggerFactory.getLogger(MapQueue.class);

	public MapQueue(EventMap map) {
		this.mapId = map.getId();

	}

	void timeoutEntries() {
		logger.info("Timeouting entries");
		// give 10 seconds mercy ( and give us some time to go through all entries)
		Date now = new Date(System.currentTimeMillis() + 1000 * 15);
		for (EventUser r : reserving) {
			MapReservationQueueEntry entry = queEntries.get(r);
			logger.info("Checking if should remove user from queue {}, timeout {}", r, entry.getReservationTimeout());
			if (entry.getReservationTimeout() == null || now.after(entry.getReservationTimeout())) {
				logger.info("Removing Eventuser {} from reserving queue due to reservationTimeout: {}", r, entry.getReservationTimeout());
				this.remove(r);
			}
		}

		// Set idle time to the past.
		// Idle timeout after 12 seconds
		Date idleTimeout = new Date(System.currentTimeMillis() - 1000 * 120);
		for (MapReservationQueueEntry entry : queue) {

			if (entry.getSeenTime() == null) {
				entry.setSeenTime(new Date());
				continue;
			}
			if (idleTimeout.after(entry.getSeenTime())) {
				remove(entry.getUser());
			}

		}
	}

	public boolean isReserving(EventUser e) {

		// Check queue size and add entry to queue
		checkReservingEntry();
		MapReservationQueueEntry que = queEntries.get(e);
		if (que != null) {
			que.setSeenTime(new Date());
		}
		return reserving.contains(e);

	}

	private final AtomicBoolean modifyReservers = new AtomicBoolean(false);

	private void checkReservingEntry() {

		if (reserving.size() < getReservingSize() && modifyReservers.compareAndSet(false, true)) {

			try {
				if (reserving.size() < getReservingSize()) {
					MapReservationQueueEntry queEntry = queue.poll();
					if (queEntry != null) {
						reserving.add(queEntry.getUser());
						queEntry.setReservationTimeout(new Date(System.currentTimeMillis() + getDefaultTimeoutMin() * 60 * 1000));
					}
				}
			} finally {
				modifyReservers.set(false);
			}

		}
	}

	public MapReservationQueueEntry remove(EventUser user) {
		if (user == null) {
			return null;
		}
		MapReservationQueueEntry ret = null;
		reserving.remove(user);
		MapReservationQueueEntry entry = queEntries.remove(user);
		if (entry != null) {
			// There should neve be more than one instance, but make sure
			while (queue.remove(entry)) {
			}
		}
		checkReservingEntry();

		return ret;
	}

	public MapReservationQueueEntry enter(EventUser user, List<PlaceSlot> slots) {
		MapReservationQueueEntry ret = initEntry(user, slots);
		if (!reserving.contains(user) && !queue.contains(ret)) {

			queue.add(ret);
			// Check if the user can be put to reservation queue immediately
			checkReservingEntry();

		} else {
			ret = queEntries.get(user);
			logger.info("User {} already in queue. Not entering again {}", user, ret);
		}

		return ret;

	}

	public void forceAdd(EventUser u, Date time, List<PlaceSlot> slots) {

		MapReservationQueueEntry entry = initEntry(u, slots);
		entry.setReservationTimeout(time);
		queue.remove(entry);

		reserving.add(u);

	}

	private MapReservationQueueEntry initEntry(EventUser user, List<PlaceSlot> slots) {
		MapReservationQueueEntry ret = queEntries.get(user);
		if (ret == null) {
			ret = new MapReservationQueueEntry();
			ret.setUser(user);
			ret.setSeenTime(new Date());
			ret.setPlaceslotcount(slots.size());
			MapReservationQueueEntry old = queEntries.putIfAbsent(user, ret);
			if (old != null) {
				ret = old;
			}

		}
		queEntries.put(user, ret);
		return ret;
	}

	public Integer getPosition(EventUser user) {
		Integer ret = null;
		MapReservationQueueEntry entry = queEntries.get(user);
		if (reserving.contains(user)) {
			ret = 0;
			logger.info("User in reserving queue {}", user);
		} else if (entry != null && queue.contains(entry)) {
			ret = 1;
			for (Iterator<MapReservationQueueEntry> iterator = queue.iterator(); iterator.hasNext();) {
				if (iterator.next().getUser().equals(user)) {
					break;
				}
				++ret;
			}
		} else {
			logger.info("Not in queue, while checking position");
		}
		return ret;
	}

	public boolean isInQueue(EventUser user) {
		MapReservationQueueEntry entry = queEntries.get(user);
		return reserving.contains(user) || (entry != null && queue.contains(entry));
	}

	public MapReservationQueueEntry getEntry(EventUser user) {
		return queEntries.get(user);
	}

	public Collection<EventUser> getReserving() {
		return reserving;
	}

	@Override
	public PriorityQueue<MapReservationQueueEntry> getQueue() {
		return queue;
	}

	public int getMinimumSlotsInQueue() {
		return minimumSlotsInQueue;
	}

	public void setMinimumSlotsInQueue(int minimumSlotsInQueue) {
		this.minimumSlotsInQueue = minimumSlotsInQueue;
	}

	public int getDefaultTimeoutMin() {
		return defaultTimeoutMin;
	}

	public void setDefaultTimeoutMin(int defaultTimeoutMin) {
		this.defaultTimeoutMin = defaultTimeoutMin;
	}

	public int getReservingSize() {
		return reservingSize;
	}

	public void setReservingSize(int reservingSize) {
		this.reservingSize = reservingSize;
	}

}