Commit 22158ece by Juho Salli

Merge branch 'mapqueue' into 'master'

Map queue rewrite

Map queue was a global concept. Rewrite and restructure of the code to allow per-map queues. Also changed to use more performant concurrent versions of collections instead of 'synchronized' stuff

See merge request !282
2 parents 71dd6834 5afca424
package fi.codecrew.moya.beans;
package fi.codecrew.moya.beans.map;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.LinkedBlockingQueue;
import javax.ejb.Local;
......@@ -24,36 +26,34 @@ public interface QueueBeanLocal {
MapReservationQueueEntry remove(EventMap map, EventUser user);
int getMinimumSlotsInQueue();
void setMinimumSlotsInQueue(int minimumSlotsInQueue);
Date getReservationTimeout(EventMap map);
MapQueueI getMapQueue(EventMap map);
Date getReservationTimeout(EventMap map);
public interface MapQueueI {
void setReservingSize(int reservingSize);
Collection<MapReservationQueueEntry> getQueue();
int getReservingSize();
Collection<EventUser> getReserving();
void setDefaultTimeoutMin(int defaultTimeoutMin);
void setReservingSize(int reservingSize);
int getDefaultTimeoutMin();
int getReservingSize();
MapQueueI getMapQueue(EventMap map);
void setDefaultTimeoutMin(int defaultTimeoutMin);
public interface MapQueueI {
int getDefaultTimeoutMin();
LinkedBlockingQueue<EventUser> getQueue();
int getMinimumSlotsInQueue();
Set<EventUser> getReserving();
void setMinimumSlotsInQueue(int minimumSlotsInQueue);
MapReservationQueueEntry getEntry(EventUser u);
}
void forceAddToReserving(EventMap map, EventUser u, Date time);
void forceRemove(EventMap e, EventUser u);
void forceRemove(EventMap e, EventUser u);
}
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 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 volatile 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 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 60 seconds
Date idleTimeout = new Date(System.currentTimeMillis() - 1000 * 60);
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 Object reservingLock = new Object();
private void checkReservingEntry() {
if (reserving.size() < getReservingSize()) {
synchronized (reservingLock) {
if (reserving.size() < getReservingSize()) {
MapReservationQueueEntry queEntry = queue.poll();
if (queEntry != null) {
reserving.add(queEntry.getUser());
queEntry.setReservationTimeout(new Date(System.currentTimeMillis() + getDefaultTimeoutMin() * 60 * 1000));
}
}
}
}
}
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;
}
}
package fi.codecrew.moya.beans.map;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import javax.annotation.security.DeclareRoles;
import javax.annotation.security.RolesAllowed;
import javax.ejb.Asynchronous;
import javax.ejb.EJB;
import javax.ejb.LocalBean;
import javax.ejb.Lock;
import javax.ejb.LockType;
import javax.ejb.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import fi.codecrew.moya.beans.EventBeanLocal;
import fi.codecrew.moya.beans.PermissionBeanLocal;
import fi.codecrew.moya.beans.PlaceBeanLocal;
import fi.codecrew.moya.enums.apps.MapPermission;
import fi.codecrew.moya.facade.PlaceSlotFacade;
import fi.codecrew.moya.model.EventMap;
import fi.codecrew.moya.model.EventUser;
import fi.codecrew.moya.model.LanEventPropertyKey;
import fi.codecrew.moya.model.PlaceSlot;
import fi.codecrew.moya.model.map.MapReservationQueueEntry;
/**
* Session Bean implementation class QueueBean
*/
@Singleton
@LocalBean
@Lock(LockType.READ)
@DeclareRoles({ MapPermission.S_MANAGE_MAPS })
public class QueueBean implements QueueBeanLocal {
private static final Logger logger = LoggerFactory.getLogger(QueueBean.class);
/**
* Default constructor.
*/
public QueueBean() {
logger.info("Initialized2 QueueBean, {}", mapqueues);
}
@EJB
private PlaceBeanLocal placebean;
private final ConcurrentHashMap<Integer, MapQueue> mapqueues = new ConcurrentHashMap<Integer, MapQueue>();
@EJB
private EventBeanLocal eventbean;
private AtomicLong nextReservingTimeoutCheck = new AtomicLong();
@EJB
private PlaceSlotFacade slotfacade;
@EJB
private PermissionBeanLocal permbean;
@Lock(LockType.READ)
@Override
public boolean isReserving(EventMap map, EventUser user) {
// If queue is not enabled, user can always reserve
if (!isQueueEnabled())
return true;
if (!permbean.isCurrentUser(user) && !permbean.hasPermission(MapPermission.MANAGE_OTHERS))
throw new RuntimeException("Use " + permbean.getCurrentUser() + "tried to enter to queue in behalf of another user: " + user);
if (map == null || user == null) {
logger.warn("Can not check map {}, user {}", map, user);
return false;
}
boolean ret = getMapque(map).isReserving(user);
// Do some housekeeping, but only on each 120
long now = System.currentTimeMillis();
long nextTime = nextReservingTimeoutCheck.get();
// Update next checktime to 120 seconds in to the future, so we should have plenty of time
// to do the checks we need..
if (now > nextTime && nextReservingTimeoutCheck.compareAndSet(nextTime, now + 1000 * 120)) {
logger.info("Launcing reservingTimeout check ");
checkReservingTimeouts();
logger.info("Done launching reservingTimeoutCheck");
}
return ret;
}
@Lock(LockType.READ)
@Asynchronous
private void checkReservingTimeouts() {
try {
final long oldTime = nextReservingTimeoutCheck.get();
for (MapQueue m : mapqueues.values()) {
m.timeoutEntries();
}
// DO housekeeping every 10 seconds.
nextReservingTimeoutCheck.compareAndSet(oldTime, System.currentTimeMillis() + 1000 * 10);
} catch (Exception t) {
logger.warn("Exception while checking reservingTimeouts", t);
}
}
@Lock(LockType.READ)
private MapQueue getMapque(EventMap map) {
if (map == null) {
return null;
}
MapQueue ret = mapqueues.get(map.getId());
if (ret == null) {
ret = new MapQueue(map);
MapQueue nret = mapqueues.putIfAbsent(map.getId(), ret);
if (nret != null) {
ret = nret;
}
}
logger.info("returning queue {} for map {}", ret, map);
return ret;
}
@Lock(LockType.READ)
@Override
public Date getReservationTimeout(EventMap map)
{
Date d = null;
MapReservationQueueEntry ret = getMapque(map).getEntry(permbean.getCurrentUser());
if (ret != null)
d = ret.getReservationTimeout();
return d;
}
public Date getReservingTimeout(EventMap map)
{
MapReservationQueueEntry ret = getMapque(map).getEntry(permbean.getCurrentUser());
Date time = null;
if (ret != null)
time = ret.getReservationTimeout();
return time;
}
@Lock(LockType.READ)
@Override
public Integer getQueuePosition(EventMap map, EventUser user)
{
if (!permbean.isCurrentUser(user) && !permbean.hasPermission(MapPermission.MANAGE_OTHERS))
throw new RuntimeException("Use " + permbean.getCurrentUser() + "tried to get queuepos in behalf of another user: " + user);
return getMapque(map).getPosition(permbean.getCurrentUser());
}
@Lock(LockType.READ)
@Override
public boolean isQueueEnabled() {
return eventbean.getPropertyBoolean(LanEventPropertyKey.MAP_QUEUE);
}
@Lock(LockType.READ)
@Override
public MapReservationQueueEntry remove(EventMap map, EventUser user) {
if (!permbean.isCurrentUser(user) && !permbean.hasPermission(MapPermission.MANAGE_OTHERS))
throw new RuntimeException("Use " + permbean.getCurrentUser() + "tried to enter to queue in behalf of another user: " + user);
MapQueue queue = getMapque(map);
return queue.remove(user);
}
@Override
@Lock(LockType.READ)
@RolesAllowed(MapPermission.S_MANAGE_MAPS)
public void forceAddToReserving(EventMap map, EventUser u, Date time) {
MapQueue q = getMapque(map);
List<PlaceSlot> slots = slotfacade.findFreePlaceSlots(u, map);
q.forceAdd(u, time, slots);
}
@Override
@Lock(LockType.READ)
public MapReservationQueueEntry enterQueue(EventMap map, EventUser user) {
if (!isQueueEnabled()) {
return null;
}
if (!permbean.isCurrentUser(user) && !permbean.hasPermission(MapPermission.MANAGE_OTHERS))
throw new RuntimeException("Use " + permbean.getCurrentUser() + "tried to enter to queue in behalf of another user: " + user);
if (user == null || user.isAnonymous()) {
return null;
}
MapQueue queue = getMapque(map);
MapReservationQueueEntry ret = null;
if (queue.isInQueue(user)) {
logger.info("User {} already in queue");
ret = queue.getEntry(user);
} else {
List<PlaceSlot> slots = slotfacade.findFreePlaceSlots(user, map);
logger.info("User {} not yet in queue. User has {} slots", user, slots.size());
if (!slots.isEmpty() && slots.size() >= queue.getMinimumSlotsInQueue()) {
ret = queue.enter(user, slots);
}
}
return ret;
}
@Lock(LockType.READ)
@RolesAllowed(MapPermission.S_MANAGE_MAPS)
@Override
public MapQueue getMapQueue(EventMap map) {
return getMapque(map);
}
@Override
@Lock(LockType.READ)
@RolesAllowed(MapPermission.S_MANAGE_MAPS)
public void forceRemove(EventMap e, EventUser u) {
MapQueue q = getMapque(e);
q.remove(u);
}
}
......@@ -25,12 +25,13 @@ import fi.codecrew.moya.model.GenericEntity;
// @Entity
// @Table(name = "map_reservation_queue_entry")
public class MapReservationQueueEntry extends GenericEntity {
public class MapReservationQueueEntry extends GenericEntity implements Comparable<MapReservationQueueEntry> {
/**
*
*/
private static final long serialVersionUID = -1529588850152306791L;
private static final Logger logger = LoggerFactory.getLogger(MapReservationQueueEntry.class);
@Column()
@JoinColumn(nullable = false)
......@@ -46,36 +47,11 @@ public class MapReservationQueueEntry extends GenericEntity {
private Date reservationTimeout;
@Transient
private Date seenTime;
// @OneToOne()
// @JoinColumn(nullable = true)
// private MapReservationQueueEntry previous;
//
// @OneToOne(mappedBy = "previous")
// private MapReservationQueueEntry next;
private static final Logger logger = LoggerFactory.getLogger(MapReservationQueueEntry.class);
private Date seenTime = new Date();
// public void removeFromQueue() {
// if (previous != null) {
// if (!this.equals(previous.getNext())) {
// logger.warn("WTF!! Previous entrys next value does not match this! This '{}', Previous '{}', Next of previous '{}'", this, getPrevious(), getPrevious().getNext());
// }
// previous.setNext(next);
// previous = null;
// next = null;
// }
// }
//
// public void addToQueue(MapReservationQueueEntry previous) {
// if (previous != null) {
// next = previous.getNext();
// previous.setNext(this);
// if (next != null) {
// next.setPrevious(this);
// }
// }
// }
/** Initialized in bean and repopulated when fetching from database */
@Transient
private Integer placeslotcount;
public MapReservationQueue getQueue() {
return queue;
......@@ -117,4 +93,21 @@ public class MapReservationQueueEntry extends GenericEntity {
this.seenTime = seenTime;
}
@Override
public int compareTo(MapReservationQueueEntry o) {
return getPlaceslotcount().compareTo(o.getPlaceslotcount());
}
public Integer getPlaceslotcount() {
if (placeslotcount == null) {
placeslotcount = 0;
}
return placeslotcount;
}
public void setPlaceslotcount(Integer placeslotcount) {
this.placeslotcount = placeslotcount;
}
}
......@@ -3,6 +3,7 @@ package fi.codecrew.moya.web.cdiview.map;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.ejb.EJB;
import javax.enterprise.context.RequestScoped;
......@@ -16,7 +17,8 @@ import org.slf4j.LoggerFactory;
import fi.codecrew.moya.beans.EventBeanLocal;
import fi.codecrew.moya.beans.PermissionBeanLocal;
import fi.codecrew.moya.beans.PlaceBeanLocal;
import fi.codecrew.moya.beans.QueueBeanLocal;
import fi.codecrew.moya.beans.map.QueueBeanLocal;
import fi.codecrew.moya.beans.map.QueueBeanLocal.MapQueueI;
import fi.codecrew.moya.enums.apps.MapPermission;
import fi.codecrew.moya.exceptions.BortalCatchableException;
import fi.codecrew.moya.model.EventMap;
......@@ -26,7 +28,6 @@ import fi.codecrew.moya.model.PlaceSlot;
import fi.codecrew.moya.model.Product;
import fi.codecrew.moya.model.map.MapReservationQueueEntry;
import fi.codecrew.moya.web.cdiview.GenericCDIView;
import fi.codecrew.moya.web.cdiview.shop.ProductShopView;
import fi.codecrew.moya.web.cdiview.user.UserView;
@Named
......@@ -89,13 +90,13 @@ public class AjaxMapView extends GenericCDIView {
EventUser u = userview.getSelectedUser();
EventMap map = initMap();
List<PlaceSlot> usersPlaceslots = placebean.getPlaceslots(u);
if (usersPlaceslots.isEmpty()) {
MapQueueI mapqueue = quebean.getMapQueue(map);
if (usersPlaceslots == null || usersPlaceslots.isEmpty()) {
logger.info("There are no placeslots available for user");
map = null;
super.navihandler.forward("/shop/createBill?faces-redirect=true");
} else if (usersPlaceslots.size() < quebean.getMinimumSlotsInQueue()) {
} else if (usersPlaceslots.size() < mapqueue.getMinimumSlotsInQueue()) {
map = null;
super.navihandler.forward("/neomap/notenoughslots?faces-redirect=true");
} else {
......@@ -107,7 +108,7 @@ public class AjaxMapView extends GenericCDIView {
private static Collection<Slotcounter> countPlaceslots(List<PlaceSlot> slots)
{
HashMap<Product, Slotcounter> prodmap = new HashMap<Product, Slotcounter>();
Map<Product, Slotcounter> prodmap = new HashMap<>();
for (PlaceSlot p : slots) {
if (!prodmap.containsKey(p.getProduct())) {
prodmap.put(p.getProduct(), new Slotcounter(p.getProduct()));
......@@ -135,16 +136,16 @@ public class AjaxMapView extends GenericCDIView {
return queEnabled;
}
public String enterQueue()
{
logger.info("Entering queue");
if (isQueueEnabled())
queueEntry = quebean.enterQueue(initMap(), userview.getSelectedUser());
else {
logger.warn("QueueNot enabled. Not entering queue");
}
return null;
}
// public String enterQueue()
// {
// logger.info("Entering queue");
// if (isQueueEnabled())
// queueEntry = quebean.enterQueue(initMap(), userview.getSelectedUser());
// else {
// logger.warn("QueueNot enabled. Not entering queue");
// }
// return null;
// }
private EventMap initMap() {
if (map == null && mapId != null) {
......
......@@ -6,7 +6,6 @@ import java.util.List;
import javax.ejb.EJB;
import javax.enterprise.context.ConversationScoped;
import javax.enterprise.context.RequestScoped;
import javax.faces.model.ListDataModel;
import javax.inject.Named;
......@@ -15,8 +14,8 @@ import org.slf4j.LoggerFactory;
import fi.codecrew.moya.beans.PermissionBeanLocal;
import fi.codecrew.moya.beans.PlaceBeanLocal;
import fi.codecrew.moya.beans.QueueBeanLocal;
import fi.codecrew.moya.beans.QueueBeanLocal.MapQueueI;
import fi.codecrew.moya.beans.map.QueueBeanLocal;
import fi.codecrew.moya.beans.map.QueueBeanLocal.MapQueueI;
import fi.codecrew.moya.beans.UserBeanLocal;
import fi.codecrew.moya.enums.apps.MapPermission;
import fi.codecrew.moya.model.EventMap;
......@@ -39,7 +38,7 @@ public class QueueManageView extends GenericCDIView {
private EventMap map;
private MapQueueI queue;
private fi.codecrew.moya.beans.map.QueueBeanLocal.MapQueueI queue;
private String username;
private Date time = new Date(System.currentTimeMillis() + 1000 * 60 * 60);
......@@ -92,9 +91,8 @@ public class QueueManageView extends GenericCDIView {
List<MapReservationQueueEntry> ret = new ArrayList<>();
if (queue == null)
return null;
for (EventUser u : queue.getQueue())
{
ret.add(queue.getEntry(u));
for (MapReservationQueueEntry u : queue.getQueue()) {
ret.add(queue.getEntry(u.getUser()));
}
ListDataModel<MapReservationQueueEntry> retMod = new ListDataModel<>(ret);
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!