package org.societies.webapp.controller.userfeedback;
import org.societies.api.comm.xmpp.pubsub.PubsubClient;
import org.societies.api.comm.xmpp.pubsub.Subscriber;
import org.societies.api.identity.IIdentity;
import org.societies.api.internal.schema.useragent.feedback.UserFeedbackAccessControlEvent;
import org.societies.api.internal.schema.useragent.feedback.UserFeedbackPrivacyNegotiationEvent;
import org.societies.api.internal.useragent.feedback.IUserFeedback;
import org.societies.api.internal.useragent.model.ExpProposalType;
import org.societies.api.osgi.event.EventTypes;
import org.societies.api.schema.useragent.feedback.*;
import org.societies.useragent.api.feedback.IAccessControlHistoryRepository;
import org.societies.useragent.api.feedback.IInternalUserFeedback;
import org.societies.useragent.api.feedback.IPrivacyPolicyNegotiationHistoryRepository;
import org.societies.useragent.api.feedback.IUserFeedbackHistoryRepository;
import org.societies.useragent.api.model.UserFeedbackEventTopics;
import org.societies.webapp.ILoginListener;
import org.societies.webapp.controller.BasePageController;
import org.societies.webapp.entity.NotificationQueueItem;
import org.societies.webapp.service.UserService;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;
import javax.annotation.PostConstruct;
import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;
import javax.faces.bean.SessionScoped;
import javax.inject.Named;
import java.util.*;
@Controller
@Scope("session")
@Named("notifications")
@ManagedBean(name = "notifications", eager = true)
@SessionScoped
public class NotificationsController extends BasePageController {
private class PubSubListener implements Subscriber {
//pubsub event schemas
private final List<String> EVENT_SCHEMA_CLASSES =
Collections.unmodifiableList(Arrays.asList(
"org.societies.api.schema.useragent.feedback.UserFeedbackBean",
"org.societies.api.schema.useragent.feedback.ExpFeedbackResultBean",
"org.societies.api.schema.useragent.feedback.ImpFeedbackResultBean",
"org.societies.api.internal.schema.useragent.feedback.UserFeedbackPrivacyNegotiationEvent",
"org.societies.api.internal.schema.useragent.feedback.UserFeedbackAccessControlEvent"));
private final List<String> EVENT_TYPES =
Collections.unmodifiableList(Arrays.asList(
EventTypes.UF_PRIVACY_NEGOTIATION,
EventTypes.UF_PRIVACY_NEGOTIATION_RESPONSE,
EventTypes.UF_PRIVACY_NEGOTIATION_REMOVE_POPUP,
EventTypes.UF_PRIVACY_ACCESS_CONTROL,
EventTypes.UF_PRIVACY_ACCESS_CONTROL_RESPONSE,
EventTypes.UF_PRIVACY_ACCESS_CONTROL_REMOVE_POPUP,
UserFeedbackEventTopics.EXPLICIT_RESPONSE,
UserFeedbackEventTopics.IMPLICIT_RESPONSE,
UserFeedbackEventTopics.REQUEST,
UserFeedbackEventTopics.COMPLETE));
public void registerForEvents() {
if (log.isDebugEnabled())
log.debug("registerForEvents()");
if (pubsubClient == null) {
log.error("PubSubClient was null, cannot register for events");
return;
}
try {
//register schema classes
pubsubClient.addSimpleClasses(EVENT_SCHEMA_CLASSES);
} catch (Exception e) {
addGlobalMessage("Error subscribing to pubsub schema classes",
e.getMessage(),
FacesMessage.SEVERITY_ERROR);
log.error("Error subscribing to pubsub schema classes", e);
}
for (String eventType : EVENT_TYPES) {
try {
pubsubClient.subscriberSubscribe(userService.getIdentity(),
eventType,
this);
if (log.isDebugEnabled())
log.debug("Subscribed to " + eventType + " events");
} catch (Exception e) {
addGlobalMessage("Error subscribing to pubsub notifications",
e.getMessage(),
FacesMessage.SEVERITY_ERROR);
log.error("Error subscribing to pubsub notifications (id=" + userService.getIdentity() + " event=" + eventType, e);
}
}
}
@Override
public void pubsubEvent(IIdentity pubsubService, String node, String itemId, Object item) {
if (item == null) {
log.warn(String.format("Received pubsub event with NULL PAYLOAD - not recording. Node '%s', ID '%s'",
node,
itemId)
);
return;
}
// create the correct notification type for the incoming event or process the response correctly
if (EventTypes.UF_PRIVACY_NEGOTIATION.equals(node)) {
processPrivacyNegotiationEvent(node, itemId, item);
} else if (EventTypes.UF_PRIVACY_NEGOTIATION_RESPONSE.equals(node)
|| EventTypes.UF_PRIVACY_NEGOTIATION_REMOVE_POPUP.equals(node)) {
processPrivacyNegotiationResponse(node, itemId, item);
} else if (EventTypes.UF_PRIVACY_ACCESS_CONTROL.equals(node)) {
processAccessControlEvent(node, itemId, item);
} else if (EventTypes.UF_PRIVACY_ACCESS_CONTROL_RESPONSE.equals(node)
|| EventTypes.UF_PRIVACY_ACCESS_CONTROL_REMOVE_POPUP.equals(node)) {
processAccessControlResponse(node, itemId, item);
} else if (UserFeedbackEventTopics.REQUEST.equals(node)) {
processUserFeedbackEvent(node, itemId, item);
} else if (UserFeedbackEventTopics.EXPLICIT_RESPONSE.equals(node)
|| UserFeedbackEventTopics.IMPLICIT_RESPONSE.equals(node)
|| UserFeedbackEventTopics.COMPLETE.equals(node)) {
processUserFeedbackResponse(node, itemId, item);
} else {
String fmt = "Unknown event %s, payload type %s with ID %s";
log.warn(String.format(fmt,
node, item.getClass().getSimpleName(), itemId));
return;
}
// notify the user
// TODO: Fix PrimeFaces push
// PushContext pushContext = PushContextFactory.getDefault().getPushContext();
// pushContext.push("/notifications", "");
if (log.isDebugEnabled()) {
log.debug("numUnansweredNotifications=" + getNumUnansweredNotifications());
}
}
private void processPrivacyNegotiationEvent(String node, String itemId, Object item) {
if (!(item instanceof UserFeedbackPrivacyNegotiationEvent)) {
log.warn(String.format("Received pubsub event with topic '%s', ID '%s' and class '%s' - Required UserFeedbackPrivacyNegotiationEvent",
node,
itemId,
item.getClass().getCanonicalName()
));
return;
}
UserFeedbackPrivacyNegotiationEvent ppn = (UserFeedbackPrivacyNegotiationEvent) item;
NotificationQueueItem newItem = NotificationQueueItem.forPrivacyPolicyNotification(String.valueOf(ppn.getRequestId()), ppn);
addItemToQueue(newItem);
synchronized (unansweredPrivacyNegotiationEvents) {
unansweredPrivacyNegotiationEvents.put(ppn.getRequestId(), ppn);
}
}
private void processPrivacyNegotiationResponse(String node, String itemId, Object item) {
if (!(item instanceof UserFeedbackPrivacyNegotiationEvent)) {
log.warn(String.format("Received pubsub event with topic '%s', ID '%s' and class '%s' - Required UserFeedbackPrivacyNegotiationEvent",
node,
itemId,
item.getClass().getCanonicalName()
));
return;
}
String id = String.valueOf(((UserFeedbackPrivacyNegotiationEvent) item).getRequestId());
if (log.isDebugEnabled())
log.debug(String.format("Received %s event for [%s] with options {%s}",
node,
id,
"null"));
markQueueItemComplete(id, null);
synchronized (unansweredPrivacyNegotiationEvents) {
unansweredPrivacyNegotiationEvents.remove(id);
}
}
private void processAccessControlEvent(String node, String itemId, Object item) {
if (!(item instanceof UserFeedbackAccessControlEvent)) {
log.warn(String.format("Received pubsub event with topic '%s', ID '%s' and class '%s' - Required UserFeedbackAccessControlEvent",
node,
itemId,
item.getClass().getCanonicalName()
));
return;
}
UserFeedbackAccessControlEvent bean = (UserFeedbackAccessControlEvent) item;
NotificationQueueItem newItem = NotificationQueueItem.forAccessControl(bean.getRequestId(), bean);
addItemToQueue(newItem);
synchronized (unansweredAccessControlEvents) {
unansweredAccessControlEvents.put(bean.getRequestId(), bean);
}
}
private void processAccessControlResponse(String node, String itemId, Object item) {
if (!(item instanceof UserFeedbackAccessControlEvent)) {
log.warn(String.format("Received pubsub event with topic '%s', ID '%s' and class '%s' - Required UserFeedbackAccessControlEvent",
node,
itemId,
item.getClass().getCanonicalName()
));
return;
}
String id = String.valueOf(((UserFeedbackAccessControlEvent) item).getRequestId());
if (log.isDebugEnabled())
log.debug(String.format("Received %s event for [%s] with options {%s}",
node,
id,
"null"));
markQueueItemComplete(id, null);
synchronized (unansweredAccessControlEvents) {
unansweredAccessControlEvents.remove(id);
}
}
private void processUserFeedbackEvent(String node, String itemId, Object item) {
if (!(item instanceof UserFeedbackBean)) {
log.warn(String.format("Received pubsub event with topic '%s', ID '%s' and class '%s' - Required UserFeedbackBean ",
node,
itemId,
item.getClass().getCanonicalName()
));
return;
}
UserFeedbackBean bean = (UserFeedbackBean) item;
NotificationQueueItem newItem = createNotificationQueueItemFromUserFeedbackBean(bean);
// if we get a null item back, something has gone wrong and we've already logged the error
if (newItem == null)
return;
if (bean.getMethod() == FeedbackMethodType.GET_IMPLICIT_FB) {
// This is a timed abort - add to the list of timed aborts for the watcher thread
synchronized (timedAbortsToWatch) {
timedAbortsToWatch.add(newItem);
}
}
addItemToQueue(newItem);
}
private void processUserFeedbackResponse(String node, String itemId, Object item) {
String id;
String[] options;
if (item instanceof UserFeedbackBean) {
id = ((UserFeedbackBean) item).getRequestId();
options = ((UserFeedbackBean) item).getOptions().toArray(new String[((UserFeedbackBean) item).getOptions().size()]);
} else if (item instanceof ExpFeedbackResultBean) {
id = ((ExpFeedbackResultBean) item).getRequestId();
options = ((ExpFeedbackResultBean) item).getFeedback().toArray(new String[((ExpFeedbackResultBean) item).getFeedback().size()]);
} else if (item instanceof ImpFeedbackResultBean) {
id = ((ImpFeedbackResultBean) item).getRequestId();
options = new String[]{((ImpFeedbackResultBean) item).isAccepted() ? "true" : "false"};
} else {
log.warn(String.format("Received pubsub event with topic '%s', ID '%s' and class '%s' - Required UserFeedbackBean, ExpFeedbackResultBean or ImpFeedbackResultBean",
node,
itemId,
item.getClass().getCanonicalName()
));
return;
}
if (log.isDebugEnabled())
log.debug(String.format("Received %s event for [%s] with options {%s}",
node,
id,
Arrays.toString(options)));
markQueueItemComplete(id, options);
}
}
private class LoginListener implements ILoginListener {
@Override
public void userLoggedIn() {
if (log.isDebugEnabled())
log.debug("userLoggedIn()");
pubSubListener.registerForEvents();
// pre-populate the list of notifications
reloadIncompleteEvents();
}
@Override
public void userLoggedOut() {
if (log.isDebugEnabled())
log.debug("userLoggedOut()");
}
}
private class TimedAbortProcessor implements Runnable {
private boolean abort = false;
private final List<NotificationQueueItem> timedAbortsToWatch;
private boolean enabled = true;
public TimedAbortProcessor(List<NotificationQueueItem> timedAbortsToWatch) {
this.timedAbortsToWatch = timedAbortsToWatch;
}
@Override
public void run() {
while (!abort) {
try {
if (enabled)
processTimedAborts();
} catch (Exception ex) {
log.error("Error on timed abort processing thread", ex);
}
try {
Thread.sleep(500);
} catch (InterruptedException ex) {
log.error("Error sleeping on timed abort processing thread", ex);
}
}
}
public void stop() {
abort = true;
}
private void processTimedAborts() {
synchronized (timedAbortsToWatch) {
for (int i = 0; i < timedAbortsToWatch.size(); i++) {
NotificationQueueItem ta = timedAbortsToWatch.get(i);
// check if this TA has expired
if (!new Date().after(ta.getTimeoutTime())) continue;
// the TA has expired, send the response
submitItem(ta.getItemId(), Boolean.TRUE);
// remove from watch list
markQueueItemComplete(ta.getItemId(), new String[]{"false"});
i--;
}
}
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
public static final int DEFAULT_FETCH_COUNT = 50;
private final PubSubListener pubSubListener = new PubSubListener();
private final LoginListener loginListener = new LoginListener();
@SuppressWarnings("FieldCanBeLocal")
private final Thread timedAbortProcessorThread;
private final TimedAbortProcessor timedAbortProcessor;
@ManagedProperty(value = "#{pubsubClient}")
private PubsubClient pubsubClient;
@ManagedProperty(value = "#{userService}")
private UserService userService;
@ManagedProperty(value = "#{userFeedback}")
private IUserFeedback userFeedback;
@ManagedProperty(value = "#{internalUserFeedback}")
private IInternalUserFeedback internalUserFeedback;
@ManagedProperty(value = "#{userFeedbackHistoryRepository}")
private IUserFeedbackHistoryRepository userFeedbackHistoryRepository;
@ManagedProperty(value = "#{privacyPolicyNegotiationHistoryRepository}")
private IPrivacyPolicyNegotiationHistoryRepository privacyPolicyNegotiationHistoryRepository;
@ManagedProperty(value = "#{accessControlHistoryRepository}")
private IAccessControlHistoryRepository accessControlHistoryRepository;
private final List<NotificationQueueItem> timedAbortsToWatch = new ArrayList<NotificationQueueItem>();
// NB: to avoid deadlocks, always synchronise on allNotifications first, then on allNotificationIDs,
// then unansweredNotifications, then unansweredNotificationIDs
private final List<NotificationQueueItem> unansweredNotifications = new LinkedList<NotificationQueueItem>();
@SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
private final Set<String> unansweredNotificationIDs = new HashSet<String>();
private final List<NotificationQueueItem> allNotifications = new LinkedList<NotificationQueueItem>();
private final Set<String> allNotificationIDs = new HashSet<String>();
private final Map<String, UserFeedbackPrivacyNegotiationEvent> unansweredPrivacyNegotiationEvents = new HashMap<String, UserFeedbackPrivacyNegotiationEvent>();
private final Map<String, UserFeedbackAccessControlEvent> unansweredAccessControlEvents = new HashMap<String, UserFeedbackAccessControlEvent>();
public NotificationsController() {
log.debug("NotificationsController ctor()");
timedAbortProcessor = new TimedAbortProcessor(timedAbortsToWatch);
timedAbortProcessorThread = new Thread(timedAbortProcessor);
timedAbortProcessorThread.setName("TimedAbortProcessor");
timedAbortProcessorThread.setDaemon(true);
}
@SuppressWarnings("MethodMayBeStatic")
public boolean isDebugMode() {
return false;
}
public PubsubClient getPubsubClient() {
return pubsubClient;
}
public void setPubsubClient(PubsubClient pubsubClient) {
this.pubsubClient = pubsubClient;
}
public UserService getUserService() {
return userService;
}
public void setUserService(UserService userService) {
if (log.isDebugEnabled())
log.debug("setUserService() = " + userService);
if (this.userService != null) {
this.userService.removeLoginListener(loginListener);
}
this.userService = userService;
this.userService.addLoginListener(loginListener);
}
public IPrivacyPolicyNegotiationHistoryRepository getPrivacyPolicyNegotiationHistoryRepository() {
return privacyPolicyNegotiationHistoryRepository;
}
public void setPrivacyPolicyNegotiationHistoryRepository(IPrivacyPolicyNegotiationHistoryRepository privacyPolicyNegotiationHistoryRepository) {
if (log.isDebugEnabled())
log.debug("setPrivacyPolicyNegotiationHistoryRepository() = " + privacyPolicyNegotiationHistoryRepository);
this.privacyPolicyNegotiationHistoryRepository = privacyPolicyNegotiationHistoryRepository;
}
public IUserFeedbackHistoryRepository getUserFeedbackHistoryRepository() {
return userFeedbackHistoryRepository;
}
public void setUserFeedbackHistoryRepository(IUserFeedbackHistoryRepository userFeedbackHistoryRepository) {
if (log.isDebugEnabled())
log.debug("setUserFeedbackHistoryRepository() = " + userFeedbackHistoryRepository);
this.userFeedbackHistoryRepository = userFeedbackHistoryRepository;
}
public IAccessControlHistoryRepository getAccessControlHistoryRepository() {
return accessControlHistoryRepository;
}
public void setAccessControlHistoryRepository(IAccessControlHistoryRepository accessControlHistoryRepository) {
if (log.isDebugEnabled())
log.debug("setAccessControlHistoryRepository() = " + accessControlHistoryRepository);
this.accessControlHistoryRepository = accessControlHistoryRepository;
}
public IUserFeedback getUserFeedback() {
return userFeedback;
}
public void setUserFeedback(IUserFeedback userFeedback) {
this.userFeedback = userFeedback;
}
public IInternalUserFeedback getInternalUserFeedback() {
return internalUserFeedback;
}
public void setInternalUserFeedback(IInternalUserFeedback internalUserFeedback) {
this.internalUserFeedback = internalUserFeedback;
}
@PostConstruct
public void postConstruct() {
timedAbortProcessorThread.start();
// NB: Generally you DON'T want to use this method to set up your class - you want to use the LoginListener
// - This method is called whenever the bean is created at the start of the session, while the login listener
// - is called when the user actually logs in and an identity is available
// call this in case we're set up after the user has logged in
if (userService.isUserLoggedIn()) {
loginListener.userLoggedIn();
}
}
public int getNumUnansweredNotifications() {
return unansweredNotifications.size();
}
public List<NotificationQueueItem> getUnansweredNegotiationQueue() {
return unansweredNotifications;
}
public List<NotificationQueueItem> getAllNotificationsQueue() {
return allNotifications;
}
public void acceptTimedAbort(String itemId) {
submitItem(itemId, Boolean.TRUE);
}
public void abortTimedAbort(String itemId) {
submitItem(itemId, Boolean.FALSE);
}
public void submitItem(String itemId) {
submitItem(itemId, null);
}
private void submitItem(String itemId, Object result) {
log.debug("submitItem() id=" + itemId);
if (itemId == null) {
log.warn("Null itemId when calling submitItem(), cannot continue");
return;
}
// find the item
NotificationQueueItem selectedItem = null;
for (NotificationQueueItem item : unansweredNotifications) {
if (itemId.equals(item.getItemId())) {
selectedItem = item;
break;
}
}
if (selectedItem == null) {
log.warn("selected ID not found when calling submitItem(), cannot continue");
return;
}
selectedItem.setComplete(true);
if (selectedItem.getType().equals(NotificationQueueItem.TYPE_ACK_NACK)
|| selectedItem.getType().equals(NotificationQueueItem.TYPE_SELECT_ONE)
|| selectedItem.getType().equals(NotificationQueueItem.TYPE_SELECT_MANY)
|| selectedItem.getType().equals(NotificationQueueItem.TYPE_NOTIFICATION)) {
List<String> feedback = new ArrayList<String>();
if (selectedItem.getType().equals(NotificationQueueItem.TYPE_SELECT_MANY)) {
// add all results
Collections.addAll(feedback, selectedItem.getResults());
} else if (selectedItem.getType().equals(NotificationQueueItem.TYPE_NOTIFICATION)) {
// no response required - but we still have to send the event
feedback.clear();
} else {
// add one result
feedback.add(selectedItem.getResult());
}
try {
userFeedback.submitExplicitResponse(selectedItem.getItemId(), feedback);
if (log.isDebugEnabled())
log.debug("Sent " + UserFeedbackEventTopics.EXPLICIT_RESPONSE + " with ID " + selectedItem.getItemId());
} catch (Exception e) {
addGlobalMessage("Error publishing notification of completed explicit UF request",
e.getMessage(),
FacesMessage.SEVERITY_ERROR);
log.error("Error publishing notification of completed explicit UF request", e);
return;
}
} else if (selectedItem.getType().equals(NotificationQueueItem.TYPE_TIMED_ABORT)) {
try {
userFeedback.submitImplicitResponse(selectedItem.getItemId(), (Boolean) result);
if (log.isDebugEnabled())
log.debug("Sent " + UserFeedbackEventTopics.IMPLICIT_RESPONSE + " with ID " + selectedItem.getItemId());
} catch (Exception e) {
addGlobalMessage("Error publishing notification of completed implicit UF request",
e.getMessage(),
FacesMessage.SEVERITY_ERROR);
log.error("Error publishing notification of completed implicit UF request", e);
return;
}
}
synchronized (unansweredNotifications) {
synchronized (unansweredNotificationIDs) {
unansweredNotifications.remove(selectedItem);
unansweredNotificationIDs.remove(selectedItem.getItemId());
}
}
}
public void clearNotifications() {
reloadIncompleteEvents();
}
public UserFeedbackPrivacyNegotiationEvent getPrivacyNegotiationEvent(String negotiationID) {
synchronized (unansweredPrivacyNegotiationEvents) {
return unansweredPrivacyNegotiationEvents.get(negotiationID);
}
}
public UserFeedbackAccessControlEvent getAcceessControlEvent(String eventId) {
synchronized (unansweredAccessControlEvents) {
return unansweredAccessControlEvents.get(eventId);
}
}
private void reloadIncompleteEvents() {
if (log.isDebugEnabled())
log.debug("Loading incomplete UF, PPN and AC notifications");
if (internalUserFeedback == null) {
log.warn("internalUserFeedback is null - reloading directly from hibernate repositories (some integration tests might not work)");
}
List<UserFeedbackBean> userFeedbackBeans = new ArrayList<UserFeedbackBean>();
List<UserFeedbackPrivacyNegotiationEvent> privacyNegotiationEvents = new ArrayList<UserFeedbackPrivacyNegotiationEvent>();
List<UserFeedbackAccessControlEvent> accessControlEvents = new ArrayList<UserFeedbackAccessControlEvent>();
try {
if (internalUserFeedback != null)
userFeedbackBeans = internalUserFeedback.listIncompleteFeedbackBeans();
else
userFeedbackBeans = userFeedbackHistoryRepository.listIncomplete();
} catch (Exception ex) {
log.warn("Recoverable error: Error recalling UF records: " + ex.getMessage());
}
try {
if (internalUserFeedback != null)
privacyNegotiationEvents = internalUserFeedback.listIncompletePrivacyRequests();
else
privacyNegotiationEvents = privacyPolicyNegotiationHistoryRepository.listIncomplete();
} catch (Exception ex) {
log.warn("Recoverable error: Error recalling PPN records: " + ex.getMessage());
}
try {
if (internalUserFeedback != null)
accessControlEvents = internalUserFeedback.listIncompleteAccessRequests();
else
accessControlEvents = accessControlHistoryRepository.listIncomplete();
} catch (Exception ex) {
log.warn("Recoverable error: Error recalling AC records: " + ex.getMessage());
}
replaceCacheWithList(userFeedbackBeans, privacyNegotiationEvents, accessControlEvents);
}
private void replaceCacheWithList(List<UserFeedbackBean> ufList, List<UserFeedbackPrivacyNegotiationEvent> ppnList, List<UserFeedbackAccessControlEvent> acList) {
synchronized (allNotifications) {
synchronized (allNotificationIDs) {
synchronized (unansweredNotifications) {
synchronized (unansweredNotificationIDs) {
if (log.isDebugEnabled())
log.debug(String.format("Replacing cache with %s UF events, %s PPN events, %S AC events",
ufList != null ? ufList.size() : 0,
ppnList != null ? ppnList.size() : 0,
acList != null ? acList.size() : 0));
allNotifications.clear();
unansweredNotifications.clear();
allNotificationIDs.clear();
unansweredNotificationIDs.clear();
if (ufList != null)
for (UserFeedbackBean uf : ufList) {
NotificationQueueItem item = createNotificationQueueItemFromUserFeedbackBean(uf);
addItemToQueue(item);
}
if (ppnList != null)
for (UserFeedbackPrivacyNegotiationEvent ppn : ppnList) {
NotificationQueueItem item = NotificationQueueItem.forPrivacyPolicyNotification(ppn.getRequestId(), ppn);
addItemToQueue(item);
}
if (acList != null)
for (UserFeedbackAccessControlEvent ac : acList) {
NotificationQueueItem item = NotificationQueueItem.forAccessControl(ac.getRequestId(), ac);
addItemToQueue(item);
}
}
}
}
}
}
private NotificationQueueItem createNotificationQueueItemFromUserFeedbackBean(UserFeedbackBean bean) {
String proposalText = bean.getProposalText();
String[] options = bean.getOptions().toArray(new String[bean.getOptions().size()]);
NotificationQueueItem newItem;
if (bean.getMethod() == FeedbackMethodType.GET_EXPLICIT_FB) {
switch (bean.getType()) {
case ExpProposalType.ACKNACK:
// This is an AckNack notification
newItem = NotificationQueueItem.forAckNack(bean.getRequestId(), proposalText, options);
break;
case ExpProposalType.CHECKBOXLIST:
// This is a select-many notification
newItem = NotificationQueueItem.forSelectMany(bean.getRequestId(), proposalText, options);
break;
case ExpProposalType.RADIOLIST:
// This is a select-one notification
newItem = NotificationQueueItem.forSelectOne(bean.getRequestId(), proposalText, options);
break;
default:
log.error("Unknown UserFeedbackBean type = " + bean.getType());
return null;
}
} else if (bean.getMethod() == FeedbackMethodType.GET_IMPLICIT_FB) {
// This is a timed abort
Date timeout = new Date(new Date().getTime() + bean.getTimeout());
newItem = NotificationQueueItem.forTimedAbort(bean.getRequestId(), proposalText, timeout);
} else if (bean.getMethod() == FeedbackMethodType.SHOW_NOTIFICATION) {
// This is a simple (no response required) notification
newItem = NotificationQueueItem.forNotification(bean.getRequestId(), proposalText);
} else {
log.error("Cannot handle UserFeedbackBean with method " + bean.getMethod().toString());
return null;
}
if (bean.getStage() == FeedbackStage.COMPLETED) {
newItem.setComplete(true);
newItem.setResults(options);
}
return newItem;
}
private void addItemToQueue(NotificationQueueItem item) {
synchronized (allNotifications) {
synchronized (allNotificationIDs) {
if (allNotificationIDs.contains(item.getItemId())) {
log.warn("NQI event ID " + item.getItemId() + " already in cache - ignoring");
return;
}
if (log.isDebugEnabled())
log.debug("Adding new NQI event ID [" + item.getItemId() + "] to cache");
allNotificationIDs.add(item.getItemId());
allNotifications.add(item);
Collections.sort(allNotifications);
if (!item.isComplete()) {
if (log.isDebugEnabled())
log.debug("NQI event ID [" + item.getItemId() + "] is not completed, adding to unanswered cache");
synchronized (unansweredNotifications) {
synchronized (unansweredNotificationIDs) {
unansweredNotificationIDs.add(item.getItemId());
unansweredNotifications.add(item);
Collections.sort(unansweredNotifications);
}
}
}
}
}
}
private void markQueueItemComplete(String itemId, String[] results) {
if (log.isDebugEnabled()) {
String fmt = "Completing notification item ID %s";
log.debug(String.format(fmt, itemId));
}
// NB: All incomplete notifications should be in the unanswered queue, and only incomplete ones should be in this queue
synchronized (unansweredNotifications) {
synchronized (unansweredNotificationIDs) {
for (NotificationQueueItem nqi : allNotifications) {
if (!nqi.getItemId().equals(itemId)) continue;
if (log.isDebugEnabled()) {
String fmt = "Removing notification item of type %s with ID %s";
log.debug(String.format(fmt, nqi.getType(), itemId));
}
synchronized (nqi) {
nqi.setResults(results);
nqi.setComplete(true);
unansweredNotifications.remove(nqi);
unansweredNotificationIDs.remove(itemId);
}
break;
}
}
}
// remove any timed aborts
synchronized (timedAbortsToWatch) {
for (NotificationQueueItem nqi : timedAbortsToWatch) {
if (!nqi.getItemId().equals(itemId)) continue;
timedAbortsToWatch.remove(nqi);
break;
}
}
}
public boolean isTimedAbortProcessorEnabled() {
return timedAbortProcessor.isEnabled();
}
public void setTimedAbortProcessorEnabled(boolean enabled) {
this.timedAbortProcessor.setEnabled(enabled);
}
}