/** * Copyright (c) 2011, SOCIETIES Consortium (WATERFORD INSTITUTE OF TECHNOLOGY (TSSG), HERIOT-WATT UNIVERSITY (HWU), SOLUTA.NET * (SN), GERMAN AEROSPACE CENTRE (Deutsches Zentrum fuer Luft- und Raumfahrt e.V.) (DLR), Zavod za varnostne tehnologije * informacijske družbe in elektronsko poslovanje (SETCCE), INSTITUTE OF COMMUNICATION AND COMPUTER SYSTEMS (ICCS), LAKE * COMMUNICATIONS (LAKE), INTEL PERFORMANCE LEARNING SOLUTIONS LTD (INTEL), PORTUGAL TELECOM INOVAÇÃO, SA (PTIN), IBM Corp., * INSTITUT TELECOM (ITSUD), AMITEC DIACHYTI EFYIA PLIROFORIKI KAI EPIKINONIES ETERIA PERIORISMENIS EFTHINIS (AMITEC), TELECOM * ITALIA S.p.a.(TI), TRIALOG (TRIALOG), Stiftelsen SINTEF (SINTEF), NEC EUROPE LTD (NEC)) * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following * conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.societies.useragent.feedback; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.societies.api.comm.xmpp.exceptions.CommunicationException; import org.societies.api.comm.xmpp.exceptions.XMPPError; import org.societies.api.comm.xmpp.interfaces.ICommManager; 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.identity.Requestor; import org.societies.api.identity.util.RequestorUtils; import org.societies.api.internal.schema.useragent.feedback.NegotiationDetailsBean; 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.feedback.IUserFeedbackResponseEventListener; import org.societies.api.internal.useragent.model.ExpProposalContent; import org.societies.api.internal.useragent.model.ExpProposalType; import org.societies.api.internal.useragent.model.FeedbackForm; import org.societies.api.internal.useragent.model.ImpProposalContent; import org.societies.api.osgi.event.EventTypes; import org.societies.api.privacytrust.privacy.util.privacypolicy.ResponseItemUtils; import org.societies.api.schema.identity.RequestorBean; import org.societies.api.schema.privacytrust.privacy.model.privacypolicy.Action; import org.societies.api.schema.privacytrust.privacy.model.privacypolicy.Condition; import org.societies.api.schema.privacytrust.privacy.model.privacypolicy.ResponseItem; import org.societies.api.schema.privacytrust.privacy.model.privacypolicy.ResponsePolicy; 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.springframework.beans.factory.annotation.Autowired; import java.util.*; import java.util.concurrent.Future; public class UserFeedback implements IUserFeedback, IInternalUserFeedback, Subscriber { private static final Logger log = LoggerFactory.getLogger(UserFeedback.class); //pubsub event schemas private static final List<String> EVENT_SCHEMA_CLASSES = Collections.unmodifiableList(Arrays.asList( "org.societies.api.internal.schema.useragent.feedback.UserFeedbackAccessControlEvent", "org.societies.api.internal.schema.useragent.feedback.UserFeedbackHistoryRequest", "org.societies.api.internal.schema.useragent.feedback.UserFeedbackPrivacyNegotiationEvent", "org.societies.api.schema.useragent.feedback.ExpFeedbackResultBean", "org.societies.api.schema.useragent.feedback.ImpFeedbackResultBean", "org.societies.api.schema.useragent.feedback.UserFeedbackBean" )); @Autowired private ICommManager commsMgr; @Autowired private PubsubClient pubsub; @Autowired private IUserFeedbackHistoryRepository userFeedbackHistoryRepository; @Autowired private IPrivacyPolicyNegotiationHistoryRepository privacyPolicyNegotiationHistoryRepository; @Autowired private IAccessControlHistoryRepository accessControlHistoryRepository; //NB: To avoid deadlocks, ALWAYS synchronise on the incomplete beans map first, then results, then callbacks private final Map<String, UserFeedbackResult<List<String>>> expResults = new HashMap<String, UserFeedbackResult<List<String>>>(); private final Map<String, IUserFeedbackResponseEventListener<List<String>>> expCallbacks = new HashMap<String, IUserFeedbackResponseEventListener<List<String>>>(); private final Map<String, UserFeedbackResult<Boolean>> impResults = new HashMap<String, UserFeedbackResult<Boolean>>(); private final Map<String, IUserFeedbackResponseEventListener<Boolean>> impCallbacks = new HashMap<String, IUserFeedbackResponseEventListener<Boolean>>(); private final Map<String, UserFeedbackResult<ResponsePolicy>> negotiationResults = new HashMap<String, UserFeedbackResult<ResponsePolicy>>(); private final Map<String, IUserFeedbackResponseEventListener<ResponsePolicy>> negotiationCallbacks = new HashMap<String, IUserFeedbackResponseEventListener<ResponsePolicy>>(); private final Map<String, UserFeedbackResult<List<ResponseItem>>> accessCtrlResults = new HashMap<String, UserFeedbackResult<List<ResponseItem>>>(); private final Map<String, IUserFeedbackResponseEventListener<List<ResponseItem>>> accessCtrlCallbacks = new HashMap<String, IUserFeedbackResponseEventListener<List<ResponseItem>>>(); private final Map<String, UserFeedbackBean> incompleteUserFeedbackBeans = new HashMap<String, UserFeedbackBean>(); private final Map<String, UserFeedbackPrivacyNegotiationEvent> incompletePrivacyNegotiationEvents = new HashMap<String, UserFeedbackPrivacyNegotiationEvent>(); private final Map<String, UserFeedbackAccessControlEvent> incompleteAccessControlEvents = new HashMap<String, UserFeedbackAccessControlEvent>(); private final RequestManager requestMgr = new RequestManager(); private IIdentity myCloudID; public void initialiseUserFeedback() { log.debug("User Feedback initialising"); expResults.clear(); impResults.clear(); negotiationResults.clear(); accessCtrlResults.clear(); expCallbacks.clear(); impCallbacks.clear(); negotiationCallbacks.clear(); accessCtrlCallbacks.clear(); incompleteUserFeedbackBeans.clear(); incompletePrivacyNegotiationEvents.clear(); incompleteAccessControlEvents.clear(); //get cloud ID myCloudID = commsMgr.getIdManager().getThisNetworkNode(); log.debug("Got my cloud ID: " + myCloudID); //create pubsub node try { log.debug("Creating user feedback pubsub node"); pubsub.addSimpleClasses(EVENT_SCHEMA_CLASSES); pubsub.ownerCreate(myCloudID, UserFeedbackEventTopics.REQUEST); pubsub.ownerCreate(myCloudID, UserFeedbackEventTopics.EXPLICIT_RESPONSE); pubsub.ownerCreate(myCloudID, UserFeedbackEventTopics.IMPLICIT_RESPONSE); pubsub.ownerCreate(myCloudID, UserFeedbackEventTopics.COMPLETE); pubsub.ownerCreate(myCloudID, EventTypes.UF_PRIVACY_NEGOTIATION); pubsub.ownerCreate(myCloudID, EventTypes.UF_PRIVACY_NEGOTIATION_RESPONSE); pubsub.ownerCreate(myCloudID, EventTypes.UF_PRIVACY_NEGOTIATION_REMOVE_POPUP); pubsub.ownerCreate(myCloudID, EventTypes.UF_PRIVACY_ACCESS_CONTROL); pubsub.ownerCreate(myCloudID, EventTypes.UF_PRIVACY_ACCESS_CONTROL_REMOVE_POPUP); pubsub.ownerCreate(myCloudID, EventTypes.UF_PRIVACY_ACCESS_CONTROL_RESPONSE); log.debug("Pubsub node created!"); } catch (Exception e) { log.error("Error creating user feedback pubsub nodes", e); } //register for events from created pubsub node try { log.debug("Registering for user feedback pubsub node"); pubsub.subscriberSubscribe(myCloudID, UserFeedbackEventTopics.REQUEST, this); pubsub.subscriberSubscribe(myCloudID, UserFeedbackEventTopics.EXPLICIT_RESPONSE, this); pubsub.subscriberSubscribe(myCloudID, UserFeedbackEventTopics.IMPLICIT_RESPONSE, this); pubsub.subscriberSubscribe(myCloudID, EventTypes.UF_PRIVACY_NEGOTIATION_RESPONSE, this); pubsub.subscriberSubscribe(myCloudID, EventTypes.UF_PRIVACY_NEGOTIATION_REMOVE_POPUP, this); pubsub.subscriberSubscribe(myCloudID, EventTypes.UF_PRIVACY_ACCESS_CONTROL_RESPONSE, this); pubsub.subscriberSubscribe(myCloudID, EventTypes.UF_PRIVACY_ACCESS_CONTROL_REMOVE_POPUP, this); log.debug("Pubsub registration complete!"); } catch (Exception e) { log.error("Error registering for user feedback pubsub nodes", e); } // TODO: Disabled persistence until we can get the hibernate issues sorted. // Persistence may prove to be a lot more complicated than usual (due to the need to send our entities over pubsub) // but it may ultimately not be needed - no point in saving requests if the process which spawned the request // has died and can't resume // recallStoredUFRequests(); // recallStoredPpnRequests(); // recallStoredAccessControlRequests(); String msg = "User Feedback Initialised\n" + " Exp UF requests: %s\n" + " Imp UF requests: %s\n" + " PPN requests: %s\n" + " AC requests: %s"; log.debug(String.format(msg, expResults.size(), impResults.size(), negotiationResults.size(), accessCtrlResults.size())); } private void recallStoredPpnRequests() { try { log.debug("Recalling stored PPN requests"); List<UserFeedbackPrivacyNegotiationEvent> privacyNegotiationEvents = privacyPolicyNegotiationHistoryRepository.listIncomplete(); for (UserFeedbackPrivacyNegotiationEvent event : privacyNegotiationEvents) { String requestId = event.getRequestId(); UserFeedbackResult<ResponsePolicy> result = new UserFeedbackResult<ResponsePolicy>(requestId); negotiationResults.put(requestId, result); // if (event.getStage() != FeedbackStage.COMPLETED) // not necessary - we're calling .listIncomplete incompletePrivacyNegotiationEvents.put(requestId, event); // TODO: there's no way to store the callback for the PPN request // If the platform has been restarted, there's a good bet the requesting service will have been restarted // too, so a callback would be pointless anyway. It's going to have to resume its operations based on the database records // if (callback != null) { // expCallbacks.put(requestId, callback); // } } log.debug(String.format("Finished recalling %s stored PPN requests", privacyNegotiationEvents.size())); } catch (Exception ex) { log.error("Error recalling stored PPN requests #216 - UserFeedback will continue without database support. \n" + ex.getMessage()); } } private void recallStoredAccessControlRequests() { try { log.debug("Recalling stored AC requests"); List<UserFeedbackAccessControlEvent> accessControlEvents = accessControlHistoryRepository.listIncomplete(); for (UserFeedbackAccessControlEvent event : accessControlEvents) { String requestId = event.getRequestId(); UserFeedbackResult<ResponsePolicy> result = new UserFeedbackResult<ResponsePolicy>(requestId); negotiationResults.put(requestId, result); // if (event.getStage() != FeedbackStage.COMPLETED) // not necessary - we're calling .listIncomplete incompleteAccessControlEvents.put(requestId, event); // TODO: there's no way to store the callback for the PPN request // If the platform has been restarted, there's a good bet the requesting service will have been restarted // too, so a callback would be pointless anyway. It's going to have to resume its operations based on the database records // if (callback != null) { // expCallbacks.put(requestId, callback); // } } log.debug(String.format("Finished recalling %s stored AC requests", accessControlEvents.size())); } catch (Exception ex) { log.error("Error recalling stored AC requests #268 - UserFeedback will continue without database support. \n" + ex.getMessage()); } } private void recallStoredUFRequests() { try { log.debug("Recalling stored UF requests"); List<UserFeedbackBean> userFeedbackBeans = userFeedbackHistoryRepository.listIncomplete(); for (UserFeedbackBean userFeedbackBean : userFeedbackBeans) { String requestId = userFeedbackBean.getRequestId(); switch (userFeedbackBean.getMethod()) { case GET_EXPLICIT_FB: case SHOW_NOTIFICATION: UserFeedbackResult<List<String>> expResult = new UserFeedbackResult<List<String>>(requestId); expResults.put(requestId, expResult); break; case GET_IMPLICIT_FB: UserFeedbackResult<Boolean> impResult = new UserFeedbackResult<Boolean>(requestId); impResults.put(requestId, impResult); break; } // if (userFeedbackBean.getStage() != FeedbackStage.COMPLETED) // not necessary - we're calling .listIncomplete incompleteUserFeedbackBeans.put(requestId, userFeedbackBean); // TODO: there's no way to store the callback for the UF request // If the platform has been restarted, there's a good bet the requesting service will have been restarted // too, so a callback would be pointless anyway. It's going to have to resume its operations based on the database records // if (callback != null) { // expCallbacks.put(requestId, callback); // } } log.debug(String.format("Finished recalling %s stored UF requests", userFeedbackBeans.size())); } catch (Exception ex) { log.error("Error recalling stored UF requests #193 - UserFeedback will continue without database support. \n" + ex.getMessage()); } } @Override public Future<List<String>> getExplicitFB(String requestId, int type, ExpProposalContent content) { Future<List<String>> result = getExplicitFBAsync(requestId, type, content, null); // wait until complete, or timeout has expired while (!result.isDone()) { try { synchronized (result) { result.wait(100); } } catch (InterruptedException e) { log.warn("Error waiting for result", e); } } try { // return result.get(); return result; } catch (Exception e) { log.warn("Error parsing result from Future", e); return null; } } @Override public Future<List<String>> getExplicitFB(int type, ExpProposalContent content) { //generate unique ID for this pubsub event and feedback request String requestId = UUID.randomUUID().toString(); return getExplicitFB(requestId, type, content); } @Override public Future<List<String>> getExplicitFBAsync(int type, ExpProposalContent content) { return getExplicitFBAsync(type, content, null); } @Override public Future<List<String>> getExplicitFBAsync(int type, ExpProposalContent content, IUserFeedbackResponseEventListener<List<String>> callback) { //generate unique ID for this pubsub event and feedback request String requestId = UUID.randomUUID().toString(); return getExplicitFBAsync(requestId, type, content, callback); } @Override public Future<List<String>> getExplicitFBAsync(String requestId, int type, ExpProposalContent content, IUserFeedbackResponseEventListener<List<String>> callback) { if (log.isDebugEnabled()) { log.debug("Received request for explicit feedback\n" + " Content: " + content.getProposalText()); } //create user feedback bean to fire in pubsub event UserFeedbackBean ufBean = new UserFeedbackBean(); // ufBean.setRequestDate(new Date()); ufBean.setStage(FeedbackStage.PENDING_USER_RESPONSE); ufBean.setRequestId(requestId); ufBean.setType(type); ufBean.setProposalText(content.getProposalText()); List<String> optionsList = new ArrayList<String>(); Collections.addAll(optionsList, content.getOptions()); ufBean.setOptions(optionsList); ufBean.setMethod(FeedbackMethodType.GET_EXPLICIT_FB); //add new request to result hashmap UserFeedbackResult<List<String>> result = new UserFeedbackResult<List<String>>(requestId); //NB: To avoid deadlocks, ALWAYS synchronise on the incomplete beans map first, then results, then callbacks synchronized (incompleteUserFeedbackBeans) { incompleteUserFeedbackBeans.put(requestId, ufBean); } synchronized (expResults) { expResults.put(requestId, result); } if (callback != null) { synchronized (expCallbacks) { expCallbacks.put(requestId, callback); } } // store in database before sending pubsub event try { if (userFeedbackHistoryRepository == null) { log.warn("userFeedbackHistoryRepository is null - cannot store user feedback request bean in database"); } else { if (log.isDebugEnabled()) log.debug("Storing user feedback bean in database"); userFeedbackHistoryRepository.insert(ufBean); } } catch (Exception ex) { log.error("Error storing user feedback request bean to database #318 - UserFeedback will continue without database support. \n" + ex.getMessage()); } //send pubsub event to all user agents try { if (log.isDebugEnabled()) log.debug("Sending user feedback request event via pubsub with ID " + requestId); // HACK: When hibernate persists the ufBean object, it changes the options list to a org.hibernate.collection.PersistentList // When this is deserialised at the other side, hibernate gets upset. Really the serialiser should be converting any // PersistentList back to an ArrayList ufBean.setOptions(optionsList); pubsub.publisherPublish(myCloudID, UserFeedbackEventTopics.REQUEST, requestId, ufBean); } catch (Exception ex) { log.error("Error transmitting user feedback request bean via pubsub", ex); } return result; } @Override public Future<Boolean> getImplicitFB(String requestId, int type, ImpProposalContent content) { Future<Boolean> result = getImplicitFBAsync(requestId, type, content, null); // wait until complete, or timeout has expired while (!result.isDone()) { try { synchronized (result) { result.wait(100); } } catch (InterruptedException e) { log.warn("Error waiting for result", e); } } try { // return result.get(); return result; } catch (Exception e) { log.warn("Error parsing result from Future", e); return null; } } @Override public Future<Boolean> getImplicitFB(int type, ImpProposalContent content) { //generate unique ID for this pubsub event and feedback request String requestId = UUID.randomUUID().toString(); return getImplicitFB(requestId, type, content); } @Override public Future<Boolean> getImplicitFBAsync(int type, ImpProposalContent content) { return getImplicitFBAsync(type, content, null); } @Override public Future<Boolean> getImplicitFBAsync(int type, ImpProposalContent content, IUserFeedbackResponseEventListener<Boolean> callback) { //generate unique ID for this pubsub event and feedback request String requestId = UUID.randomUUID().toString(); return getImplicitFBAsync(requestId, type, content, callback); } @Override public Future<Boolean> getImplicitFBAsync(String requestId, int type, ImpProposalContent content, IUserFeedbackResponseEventListener<Boolean> callback) { if (log.isDebugEnabled()) { log.debug("Received request for implicit feedback\n" + " Content: " + content.getProposalText()); } if (content.getTimeout() < 1000) { log.warn("Implicit (Timed Abort) request timeout is < 1000ms - timeouts should be specified in milliseconds"); } //create user feedback bean to fire in pubsub event UserFeedbackBean ufBean = new UserFeedbackBean(); // ufBean.setRequestDate(new Date()); ufBean.setStage(FeedbackStage.PENDING_USER_RESPONSE); ufBean.setRequestId(requestId); ufBean.setType(type); ufBean.setProposalText(content.getProposalText()); ufBean.setTimeout(content.getTimeout()); ufBean.setMethod(FeedbackMethodType.GET_IMPLICIT_FB); //add new request to result hashmap UserFeedbackResult<Boolean> result = new UserFeedbackResult<Boolean>(requestId); //NB: To avoid deadlocks, ALWAYS synchronise on the incomplete beans map first, then results, then callbacks synchronized (incompleteUserFeedbackBeans) { incompleteUserFeedbackBeans.put(requestId, ufBean); } synchronized (impResults) { impResults.put(requestId, result); } if (callback != null) { synchronized (impCallbacks) { impCallbacks.put(requestId, callback); } } // store in database before sending pubsub event try { if (userFeedbackHistoryRepository == null) { log.warn("userFeedbackHistoryRepository is null - cannot store user feedback request bean in database"); } else { if (log.isDebugEnabled()) log.debug("Storing user feedback bean in database"); userFeedbackHistoryRepository.insert(ufBean); } } catch (Exception ex) { log.error("Error storing user feedback request bean to database #427 - UserFeedback will continue without database support. \n" + ex.getMessage()); } //send pubsub event to all user agents try { if (log.isDebugEnabled()) log.debug("Sending user feedback request event via pubsub with ID " + requestId); // HACK: When hibernate persists the ufBean object, it changes the options list to a org.hibernate.collection.PersistentList // When this is deserialised at the other side, hibernate gets upset. Really the serialiser should be converting any // PersistentList back to an ArrayList ufBean.setOptions(new ArrayList<String>()); // list is empty anyway for implicit feedback pubsub.publisherPublish(myCloudID, UserFeedbackEventTopics.REQUEST, requestId, ufBean); } catch (Exception ex) { log.error("Error transmitting user feedback request bean via pubsub", ex); } return result; } @Override public Future<ResponsePolicy> getPrivacyNegotiationFB(String requestId, ResponsePolicy policy, NegotiationDetailsBean details) { Future<ResponsePolicy> result = getPrivacyNegotiationFBAsync(requestId, policy, details, null); // wait until complete, or timeout has expired while (!result.isDone()) { try { synchronized (result) { result.wait(100); } } catch (InterruptedException e) { log.warn("Error waiting for result", e); } } try { // return result.get(); return result; } catch (Exception e) { log.warn("Error parsing result from Future", e); return null; } } @Override public Future<ResponsePolicy> getPrivacyNegotiationFB(ResponsePolicy policy, NegotiationDetailsBean details) { //generate unique ID for this pubsub event and feedback request String requestId = UUID.randomUUID().toString(); return getPrivacyNegotiationFB(requestId, policy, details); } @Override public Future<ResponsePolicy> getPrivacyNegotiationFBAsync(ResponsePolicy policy, NegotiationDetailsBean details) { return getPrivacyNegotiationFBAsync(policy, details, null); } @Override public Future<ResponsePolicy> getPrivacyNegotiationFBAsync(ResponsePolicy policy, NegotiationDetailsBean details, IUserFeedbackResponseEventListener<ResponsePolicy> callback) { //generate unique ID for this pubsub event and feedback request String requestId = UUID.randomUUID().toString(); return getPrivacyNegotiationFBAsync(requestId, policy, details, callback); } @Override public Future<ResponsePolicy> getPrivacyNegotiationFBAsync(String requestId, ResponsePolicy policy, NegotiationDetailsBean details, IUserFeedbackResponseEventListener<ResponsePolicy> callback) { if (log.isDebugEnabled()) { log.debug("processing negotiationFeedback request"); if (policy == null) { log.debug("Policy parameter is null"); } else { log.debug("Policy contains: " + policy.getResponseItems().size() + " responseItems"); } } UserFeedbackPrivacyNegotiationEvent event = new UserFeedbackPrivacyNegotiationEvent(); event.setStage(FeedbackStage.PENDING_USER_RESPONSE); event.setMethod(FeedbackMethodType.GET_EXPLICIT_FB); event.setType(ExpProposalType.PRIVACY_NEGOTIATION); event.setRequestId(requestId); event.setNegotiationDetails(details); event.setResponsePolicy(policy); //add new request to result hashmap UserFeedbackResult<ResponsePolicy> result = new UserFeedbackResult<ResponsePolicy>(requestId); //NB: To avoid deadlocks, ALWAYS synchronise on the incomplete beans map first, then results, then callbacks synchronized (incompletePrivacyNegotiationEvents) { incompletePrivacyNegotiationEvents.put(requestId, event); } synchronized (negotiationResults) { negotiationResults.put(requestId, result); } if (callback != null) { synchronized (negotiationCallbacks) { negotiationCallbacks.put(requestId, callback); } } // store in database before sending pubsub event try { if (privacyPolicyNegotiationHistoryRepository == null) { log.warn("privacyPolicyNegotiationHistoryRepository is null - cannot store PPN request bean in database"); } else { if (log.isDebugEnabled()) log.debug("Storing PPN bean in database"); privacyPolicyNegotiationHistoryRepository.insert(event); } } catch (Exception ex) { log.error("Error storing PPN request bean to database #537 - UserFeedback will continue without database support. \n" + ex.getMessage()); } //send pubsub event to all user agents try { if (log.isDebugEnabled()) log.debug("Sending PPN request event via pubsub"); // HACK: When hibernate persists the ufBean object, it changes the options list to a org.hibernate.collection.PersistentList // When this is deserialised at the other side, hibernate gets upset. Really the serialiser should be converting any // PersistentList back to an ArrayList event.getResponsePolicy().setResponseItems(new ArrayList<ResponseItem>(event.getResponsePolicy().getResponseItems())); for (ResponseItem responseItem : event.getResponsePolicy().getResponseItems()) { responseItem.getRequestItem().setActions(new ArrayList<Action>(responseItem.getRequestItem().getActions())); responseItem.getRequestItem().setConditions(new ArrayList<Condition>(responseItem.getRequestItem().getConditions())); } pubsub.publisherPublish(myCloudID, EventTypes.UF_PRIVACY_NEGOTIATION, requestId, event); } catch (Exception ex) { log.error("Error transmitting PPN request bean via pubsub", ex); } return result; } @Override public Future<List<ResponseItem>> getAccessControlFB(String requestId, Requestor requestor, List<ResponseItem> items) { Future<List<ResponseItem>> result = getAccessControlFBAsync(requestId, requestor, items, null); // wait until complete, or timeout has expired while (!result.isDone()) { try { synchronized (result) { result.wait(100); } } catch (InterruptedException e) { log.warn("Error waiting for result", e); } } try { // return result.get(); return result; } catch (Exception e) { log.warn("Error parsing result from Future", e); return null; } } @Override public Future<List<ResponseItem>> getAccessControlFB(Requestor requestor, List<ResponseItem> items) { //generate unique ID for this pubsub event and feedback request String requestId = UUID.randomUUID().toString(); return getAccessControlFB(requestId, requestor, items); } @Override public Future<List<ResponseItem>> getAccessControlFBAsync(Requestor requestor, List<ResponseItem> items) { return getAccessControlFBAsync(requestor, items, null); } @Override public Future<List<ResponseItem>> getAccessControlFBAsync(Requestor requestor, List<ResponseItem> items, IUserFeedbackResponseEventListener<List<ResponseItem>> callback) { //generate unique ID for this pubsub event and feedback request String requestId = UUID.randomUUID().toString(); return getAccessControlFBAsync(requestId, requestor, items, callback); } @Override public Future<List<ResponseItem>> getAccessControlFBAsync(String requestId, Requestor requestor, List<ResponseItem> items, IUserFeedbackResponseEventListener<List<ResponseItem>> callback) { UserFeedbackAccessControlEvent event = new UserFeedbackAccessControlEvent(); event.setStage(FeedbackStage.PENDING_USER_RESPONSE); event.setMethod(FeedbackMethodType.GET_EXPLICIT_FB); event.setType(ExpProposalType.PRIVACY_ACCESS_CONTROL); event.setRequestId(requestId); event.setRequestor(RequestorUtils.toRequestorBean(requestor)); event.setResponseItems(items); //NB: To avoid deadlocks, ALWAYS synchronise on the incomplete beans map first, then results, then callbacks synchronized (incompleteAccessControlEvents) { incompleteAccessControlEvents.put(requestId, event); } UserFeedbackResult<List<ResponseItem>> result = new UserFeedbackResult<List<ResponseItem>>(requestId); synchronized (accessCtrlResults) { accessCtrlResults.put(requestId, result); } if (callback != null) { synchronized (accessCtrlCallbacks) { accessCtrlCallbacks.put(requestId, callback); } } // store in database before sending pubsub event try { if (accessControlHistoryRepository == null) { log.warn("accessControlHistoryRepository is null - cannot store user feedback request bean in database"); } else { if (log.isDebugEnabled()) log.debug("Storing AC bean in database"); accessControlHistoryRepository.insert(event); } } catch (Exception ex) { log.error("Error storing AC request bean to database #698 - UserFeedback will continue without database support. \n" + ex.getMessage()); } try { if (log.isDebugEnabled()) log.debug("Sending access control request event via pubsub"); // HACK: When hibernate persists the ufBean object, it changes the options list to a org.hibernate.collection.PersistentList // When this is deserialised at the other side, hibernate gets upset. Really the serialiser should be converting any // PersistentList back to an ArrayList event.setResponseItems(new ArrayList<ResponseItem>(event.getResponseItems())); for (ResponseItem responseItem : event.getResponseItems()) { responseItem.getRequestItem().setActions(new ArrayList<Action>(responseItem.getRequestItem().getActions())); responseItem.getRequestItem().setConditions(new ArrayList<Condition>(responseItem.getRequestItem().getConditions())); } this.pubsub.publisherPublish(myCloudID, EventTypes.UF_PRIVACY_ACCESS_CONTROL, requestId, event); } catch (Exception ex) { log.error("Error transmitting access control request bean via pubsub", ex); } return result; } @Override public void showNotification(String notificationTxt) { if (log.isDebugEnabled()) { log.debug("Received request for notification\n" + " Content: " + notificationTxt); } //generate unique ID for this pubsub event and feedback request String requestId = UUID.randomUUID().toString(); //create user feedback bean to fire in pubsub event UserFeedbackBean ufBean = new UserFeedbackBean(); // ufBean.setRequestDate(new Date()); ufBean.setStage(FeedbackStage.PENDING_USER_RESPONSE); ufBean.setRequestId(requestId); ufBean.setProposalText(notificationTxt); ufBean.setMethod(FeedbackMethodType.SHOW_NOTIFICATION); //add new request to result hashmap UserFeedbackResult<List<String>> result = new UserFeedbackResult<List<String>>(requestId); //NB: To avoid deadlocks, ALWAYS synchronise on the incomplete beans map first, then results, then callbacks synchronized (incompleteUserFeedbackBeans) { incompleteUserFeedbackBeans.put(requestId, ufBean); } synchronized (expResults) { expResults.put(requestId, result); } // store in database before sending pubsub event try { if (userFeedbackHistoryRepository == null) { log.warn("userFeedbackHistoryRepository is null - cannot store user feedback request bean in database"); } else { if (log.isDebugEnabled()) log.debug("Storing user feedback bean in database"); userFeedbackHistoryRepository.insert(ufBean); } } catch (Exception ex) { log.error("Error storing user feedback request bean to database #696 - UserFeedback will continue without database support. \n" + ex.getMessage()); } //send pubsub event to all user agents try { if (log.isDebugEnabled()) log.debug("Sending user feedback request event via pubsub with ID " + requestId); // HACK: When hibernate persists the ufBean object, it changes the options list to a org.hibernate.collection.PersistentList // When this is deserialised at the other side, hibernate gets upset. Really the serialiser should be converting any // PersistentList back to an ArrayList ufBean.setOptions(new ArrayList<String>()); // list is empty anyway for implicit feedback pubsub.publisherPublish(myCloudID, UserFeedbackEventTopics.REQUEST, requestId, ufBean); } catch (Exception ex) { log.error("Error transmitting user feedback request bean via pubsub", ex); } } @Override public void pubsubEvent(IIdentity identity, String eventTopic, String itemID, Object item) { if (eventTopic == null) { log.warn(String.format("Received pubsub event with NULL EVENT TOPIC - payload '%s', ID '%s'", item != null ? item.getClass().getSimpleName() : "null", itemID )); return; } if (item == null) { log.warn(String.format("Received pubsub event with NULL PAYLOAD - topic '%s', ID '%s'", eventTopic, itemID )); return; } if (log.isDebugEnabled()) { log.debug(String.format("Received pubsub event with topic '%s', ID '%s' and class '%s'", eventTopic, itemID, item.getClass().getSimpleName() )); } if (eventTopic.equalsIgnoreCase(UserFeedbackEventTopics.REQUEST)) { // TODO: It would be nice to refactor all GUI code into a separate class (UserFeedbackGuiFactory?) //read from request bean UserFeedbackBean ufBean = (UserFeedbackBean) item; switch (ufBean.getMethod()) { case GET_EXPLICIT_FB: String expRequestID = ufBean.getRequestId(); int expType = ufBean.getType(); String expProposalText = ufBean.getProposalText(); List<String> optionsList = ufBean.getOptions(); this.processExpFeedbackRequestEvent(expRequestID, expType, expProposalText, optionsList); break; case GET_IMPLICIT_FB: String impRequestID = ufBean.getRequestId(); int impType = ufBean.getType(); String impProposalText = ufBean.getProposalText(); int timeout = ufBean.getTimeout(); this.processImpFeedbackRequestEvent(impRequestID, impType, impProposalText, timeout); break; case SHOW_NOTIFICATION: String notRequestID = ufBean.getRequestId(); String notProposalText = ufBean.getProposalText(); this.processNotificationRequestEvent(notRequestID, notProposalText); break; } } else if (eventTopic.equalsIgnoreCase(UserFeedbackEventTopics.EXPLICIT_RESPONSE)) { //read from explicit response bean ExpFeedbackResultBean expFeedbackBean = (ExpFeedbackResultBean) item; this.processExpResponseEvent(expFeedbackBean); } else if (eventTopic.equalsIgnoreCase(UserFeedbackEventTopics.IMPLICIT_RESPONSE)) { //read from implicit response bean ImpFeedbackResultBean impFeedbackBean = (ImpFeedbackResultBean) item; this.processImpResponseEvent(impFeedbackBean); } else if (eventTopic.equalsIgnoreCase(EventTypes.UF_PRIVACY_NEGOTIATION_RESPONSE)) { UserFeedbackPrivacyNegotiationEvent event = (UserFeedbackPrivacyNegotiationEvent) item; this.processPrivacyPolicyNegotiationResponseEvent(event); } else if (eventTopic.equalsIgnoreCase(EventTypes.UF_PRIVACY_ACCESS_CONTROL_RESPONSE)) { UserFeedbackAccessControlEvent event = (UserFeedbackAccessControlEvent) item; this.processAccessControlResponseEvent(event); } else { log.warn(String.format("Unhandled pubsub event '%s' with ID '%s'", eventTopic, itemID)); } } /* * Handle explicit feedback request and response events */ private void processExpFeedbackRequestEvent(String requestId, int type, String proposalText, List<String> optionsList) { //create feedback form FeedbackForm fbForm = UserFeedbackGuiFactory.generateExpFeedbackForm(requestId, type, proposalText, optionsList); //add new request to queue requestMgr.addRequest(fbForm); } private void processExpResponseEvent(ExpFeedbackResultBean expFeedbackBean) { String responseID = expFeedbackBean.getRequestId(); //remove from request manager list if exists synchronized (requestMgr) { requestMgr.removeRequest(responseID); } //NB: To avoid deadlocks, ALWAYS synchronise on the incomplete beans map first, then results, then callbacks synchronized (incompleteUserFeedbackBeans) { // remove from incomplete list if (incompleteUserFeedbackBeans.containsKey(responseID)) incompleteUserFeedbackBeans.remove(responseID); //set result value in hashmap synchronized (expResults) { if (!expResults.containsKey(responseID)) { if (log.isDebugEnabled()) log.debug(String.format("This isn't the node where the exp feedback request ID [%s] originated", responseID)); if (log.isDebugEnabled()) { StringBuilder bld = new StringBuilder(); bld.append("Exp feedback requests outstanding:-\n"); for (String s : expResults.keySet()) { bld.append(" - "); bld.append(s); bld.append('\n'); } log.debug(bld.toString()); } return; } if (log.isDebugEnabled()) log.debug("This is the node where the exp feedback request originated"); // update result try { if (userFeedbackHistoryRepository != null) { userFeedbackHistoryRepository.completeExpFeedback(responseID, expFeedbackBean.getFeedback()); } } catch (Exception ex) { log.error("Error updating user feedback stage in database #844 - UserFeedback will continue without database support. \n" + ex.getMessage()); } // inform clients that UF is complete try { pubsub.publisherPublish(myCloudID, UserFeedbackEventTopics.COMPLETE, responseID, expFeedbackBean); } catch (Exception ex) { log.error("Error transmitting user feedback complete via pubsub", ex); } final UserFeedbackResult<List<String>> userFeedbackResult = expResults.get(responseID); synchronized (userFeedbackResult) { userFeedbackResult.complete(expFeedbackBean.getFeedback()); userFeedbackResult.notifyAll(); } if (expCallbacks.containsKey(responseID)) { IUserFeedbackResponseEventListener<List<String>> callback = expCallbacks.remove(responseID); callback.responseReceived(expFeedbackBean.getFeedback()); } this.expResults.notifyAll(); } } } private void processImpFeedbackRequestEvent(String requestId, int type, String proposalText, int timeout) { //create feedback form FeedbackForm fbForm = UserFeedbackGuiFactory.generateImpFeedbackForm(requestId, type, proposalText, timeout); //add new request to queue requestMgr.addRequest(fbForm); } private void processImpResponseEvent(ImpFeedbackResultBean impFeedbackBean) { String responseID = impFeedbackBean.getRequestId(); //remove from request manager list if exists synchronized (requestMgr) { requestMgr.removeRequest(responseID); } //NB: To avoid deadlocks, ALWAYS synchronise on the incomplete beans map first, then results, then callbacks synchronized (incompleteUserFeedbackBeans) { // remove from incomplete list if (incompleteUserFeedbackBeans.containsKey(responseID)) incompleteUserFeedbackBeans.remove(responseID); //set result value in hashmap synchronized (impResults) { if (!impResults.containsKey(responseID)) { if (log.isDebugEnabled()) log.debug(String.format("This isn't the node where the imp feedback request ID [%s] originated", responseID)); if (log.isDebugEnabled()) { StringBuilder bld = new StringBuilder(); bld.append("Imp feedback requests outstanding:-\n"); for (String s : impResults.keySet()) { bld.append(" - "); bld.append(s); bld.append('\n'); } log.debug(bld.toString()); } return; } if (log.isDebugEnabled()) log.debug("This is the node where the imp feedback request originated"); // update result try { if (userFeedbackHistoryRepository != null) { userFeedbackHistoryRepository.completeImpFeedback(responseID, impFeedbackBean.isAccepted()); } } catch (Exception ex) { log.error("Error updating user feedback stage in database #917 - UserFeedback will continue without database support. \n" + ex.getMessage()); } // inform clients that UF is complete try { pubsub.publisherPublish(myCloudID, UserFeedbackEventTopics.COMPLETE, responseID, impFeedbackBean); } catch (Exception ex) { log.error("Error transmitting user feedback complete via pubsub", ex); } final UserFeedbackResult<Boolean> userFeedbackResult = impResults.get(responseID); synchronized (userFeedbackResult) { userFeedbackResult.complete(impFeedbackBean.isAccepted()); userFeedbackResult.notifyAll(); } if (impCallbacks.containsKey(responseID)) { IUserFeedbackResponseEventListener<Boolean> callback = impCallbacks.remove(responseID); callback.responseReceived(impFeedbackBean.isAccepted()); } this.impResults.notifyAll(); } } } /* * Handle notification request events */ private void processNotificationRequestEvent(String requestId, String proposalText) { //create feedback form FeedbackForm fbForm = UserFeedbackGuiFactory.generateNotificationForm(requestId, proposalText); //add new request to queue requestMgr.addRequest(fbForm); } private void processPrivacyPolicyNegotiationResponseEvent(UserFeedbackPrivacyNegotiationEvent result) { String responseID = result.getRequestId(); //remove from request manager list if exists synchronized (requestMgr) { requestMgr.removeRequest(responseID); } //NB: To avoid deadlocks, ALWAYS synchronise on the incomplete beans map first, then results, then callbacks synchronized (incompletePrivacyNegotiationEvents) { // remove from incomplete list if (incompletePrivacyNegotiationEvents.containsKey(responseID)) incompletePrivacyNegotiationEvents.remove(responseID); //set result value in hashmap synchronized (negotiationResults) { if (!negotiationResults.containsKey(responseID)) { if (log.isDebugEnabled()) log.debug(String.format("This isn't the node where the PPN request ID [%s] originated", responseID)); if (log.isDebugEnabled()) { StringBuilder bld = new StringBuilder(); bld.append("PPN requests outstanding:-\n"); for (String s : negotiationResults.keySet()) { bld.append(" - "); bld.append(s); bld.append('\n'); } log.debug(bld.toString()); } return; } if (log.isDebugEnabled()) log.debug("This is the node where the PPN request originated"); // update result try { if (privacyPolicyNegotiationHistoryRepository != null) { privacyPolicyNegotiationHistoryRepository.updateStage(responseID, FeedbackStage.COMPLETED); } } catch (Exception ex) { log.error("Error updating PPN stage in database #993 - UserFeedback will continue without database support. \n" + ex.getMessage()); } // inform clients that negotiation is complete try { pubsub.publisherPublish(myCloudID, EventTypes.UF_PRIVACY_NEGOTIATION_REMOVE_POPUP, responseID, result); } catch (Exception ex) { log.error("Error transmitting PPN complete via pubsub", ex); } final UserFeedbackResult<ResponsePolicy> userFeedbackResult = negotiationResults.get(responseID); synchronized (userFeedbackResult) { userFeedbackResult.complete(result.getResponsePolicy()); userFeedbackResult.notifyAll(); } if (negotiationCallbacks.containsKey(responseID)) { IUserFeedbackResponseEventListener<ResponsePolicy> callback = negotiationCallbacks.remove(responseID); callback.responseReceived(result.getResponsePolicy()); } this.negotiationResults.notifyAll(); } } } private void processAccessControlResponseEvent(UserFeedbackAccessControlEvent result) { String responseID = result.getRequestId(); //remove from request manager list if exists synchronized (requestMgr) { requestMgr.removeRequest(responseID); } //NB: To avoid deadlocks, ALWAYS synchronise on the incomplete beans map first, then results, then callbacks synchronized (incompleteAccessControlEvents) { // remove from incomplete list if (incompleteAccessControlEvents.containsKey(responseID)) incompleteAccessControlEvents.remove(responseID); //set result value in hashmap synchronized (accessCtrlResults) { if (!accessCtrlResults.containsKey(responseID)) { if (log.isDebugEnabled()) log.debug(String.format("This isn't the node where the AC request ID [%s] originated", responseID)); if (log.isDebugEnabled()) { StringBuilder bld = new StringBuilder(); bld.append("AC requests outstanding:-\n"); for (String s : accessCtrlResults.keySet()) { bld.append(" - "); bld.append(s); bld.append('\n'); } log.debug(bld.toString()); } return; } if (log.isDebugEnabled()) log.debug("This is the node where the AC feedback request originated"); // update result try { if (accessControlHistoryRepository != null) { accessControlHistoryRepository.updateStage(responseID, FeedbackStage.COMPLETED); } } catch (Exception ex) { log.error("Error updating access control request stage in database #1139 - UserFeedback will continue without database support. \n" + ex.getMessage()); } // inform clients that negotiation is complete try { pubsub.publisherPublish(myCloudID, EventTypes.UF_PRIVACY_ACCESS_CONTROL_REMOVE_POPUP, responseID, result); } catch (Exception ex) { log.error("Error transmitting access control complete via pubsub", ex); } final UserFeedbackResult<List<ResponseItem>> userFeedbackResult = accessCtrlResults.get(responseID); synchronized (userFeedbackResult) { userFeedbackResult.complete(result.getResponseItems()); userFeedbackResult.notifyAll(); } if (accessCtrlCallbacks.containsKey(responseID)) { IUserFeedbackResponseEventListener<List<ResponseItem>> callback = accessCtrlCallbacks.remove(responseID); callback.responseReceived(result.getResponseItems()); } this.accessCtrlResults.notifyAll(); } } } /* * The following methods are called by the UserFeedbackController as part of the platform web-app * * (non-Javadoc) * @see org.societies.api.internal.useragent.feedback.IUserFeedback#getNextRequest() */ @Override public FeedbackForm getNextRequest() { return requestMgr.getNextRequest(); } @Override public void submitExplicitResponse(String requestId, List<String> result) { //create user feedback response bean ExpFeedbackResultBean resultBean = new ExpFeedbackResultBean(); resultBean.setRequestId(requestId); resultBean.setFeedback(result); //fire response pubsub event to all user agents try { pubsub.publisherPublish(myCloudID, UserFeedbackEventTopics.EXPLICIT_RESPONSE, requestId, resultBean); } catch (XMPPError e) { log.error("Error submitting explicit response", e); } catch (CommunicationException e) { log.error("Error submitting explicit response", e); } } @Override public void submitImplicitResponse(String requestId, Boolean result) { //create user feedback response bean ImpFeedbackResultBean resultBean = new ImpFeedbackResultBean(); resultBean.setRequestId(requestId); resultBean.setAccepted(result); //fire response pubsub event to all user agents try { pubsub.publisherPublish(myCloudID, UserFeedbackEventTopics.IMPLICIT_RESPONSE, requestId, resultBean); } catch (XMPPError e) { log.error("Error submitting implicit response", e); } catch (CommunicationException e) { log.error("Error submitting implicit response", e); } } @Override public void submitPrivacyNegotiationResponse(String requestId, NegotiationDetailsBean negotiationDetails, ResponsePolicy result) { //create user feedback response bean UserFeedbackPrivacyNegotiationEvent resultBean = new UserFeedbackPrivacyNegotiationEvent(); resultBean.setMethod(FeedbackMethodType.GET_EXPLICIT_FB); resultBean.setType(ExpProposalType.PRIVACY_NEGOTIATION); resultBean.setRequestId(requestId); resultBean.setNegotiationDetails(negotiationDetails); resultBean.setResponsePolicy(result); //fire response pubsub event to all user agents try { log.info("####### Publish " + EventTypes.UF_PRIVACY_NEGOTIATION_RESPONSE + ": " + ResponseItemUtils.toXmlString(result.getResponseItems())); pubsub.publisherPublish(myCloudID, EventTypes.UF_PRIVACY_NEGOTIATION_RESPONSE, requestId, resultBean); } catch (XMPPError e) { log.error("Error submitting negotiation response", e); } catch (CommunicationException e) { log.error("Error submitting negotiation response", e); } } @Override public void submitAccessControlResponse(String requestId, List<ResponseItem> responseItems, RequestorBean requestorBean) { //create user feedback response bean UserFeedbackAccessControlEvent resultBean = new UserFeedbackAccessControlEvent(); resultBean.setMethod(FeedbackMethodType.GET_EXPLICIT_FB); resultBean.setType(ExpProposalType.PRIVACY_NEGOTIATION); resultBean.setRequestId(requestId); resultBean.setResponseItems(responseItems); resultBean.setRequestor(requestorBean); //fire response pubsub event to all user agents try { log.info("####### Publish " + EventTypes.UF_PRIVACY_ACCESS_CONTROL_RESPONSE + ": " + ResponseItemUtils.toXmlString(responseItems)); pubsub.publisherPublish(myCloudID, EventTypes.UF_PRIVACY_ACCESS_CONTROL_RESPONSE, requestId, resultBean); } catch (XMPPError e) { log.error("Error submitting negotiation response", e); } catch (CommunicationException e) { log.error("Error submitting negotiation response", e); } } /* *Called by UACommsServer to request explicit feedback for remote User Agent * * (non-Javadoc) * @see org.societies.useragent.api.feedback.IInternalUserFeedback#getExplicitFBforRemote(int, org.societies.api.internal.useragent.model.ExpProposalContent) */ @Override public Future<List<String>> getExplicitFBforRemote(int type, ExpProposalContent content) { // TODO: Added this delegate so I didn't have to change classes consuming UF. Would be nice to refactor all GUI code into a separate class return UserFeedbackGuiFactory.getExplicitFBforRemote(type, content); } /* * Called by UACommsServer to request implicit feedback for remote User Agent * * (non-Javadoc) * @see org.societies.useragent.api.feedback.IInternalUserFeedback#getImplicitFBforRemote(int, org.societies.api.internal.useragent.model.ImpProposalContent) */ @Override public Future<Boolean> getImplicitFBforRemote(int type, ImpProposalContent content) { // TODO: Added this delegate so I didn't have to change classes consuming UF. Would be nice to refactor all GUI code into a separate class return UserFeedbackGuiFactory.getImplicitFBforRemote(type, content); } @Override public List<UserFeedbackBean> listStoredFeedbackBeans(int howMany) { return userFeedbackHistoryRepository.listPrevious(howMany); } @Override public List<UserFeedbackBean> listStoredFeedbackBeans(Date sinceWhen) { return userFeedbackHistoryRepository.listSince(sinceWhen); } @Override public List<UserFeedbackBean> listIncompleteFeedbackBeans() { List<UserFeedbackBean> list = new ArrayList<UserFeedbackBean>(incompleteUserFeedbackBeans.values()); return Collections.unmodifiableList(list); } @Override public List<UserFeedbackPrivacyNegotiationEvent> listIncompletePrivacyRequests() { List<UserFeedbackPrivacyNegotiationEvent> list = new ArrayList<UserFeedbackPrivacyNegotiationEvent>(incompletePrivacyNegotiationEvents.values()); return Collections.unmodifiableList(list); } @Override public List<UserFeedbackAccessControlEvent> listIncompleteAccessRequests() { List<UserFeedbackAccessControlEvent> list = new ArrayList<UserFeedbackAccessControlEvent>(incompleteAccessControlEvents.values()); return Collections.unmodifiableList(list); } public void setCommsMgr(ICommManager commsMgr) { this.commsMgr = commsMgr; } public void setPubsub(PubsubClient pubsub) { this.pubsub = pubsub; } public void setUserFeedbackHistoryRepository(IUserFeedbackHistoryRepository userFeedbackHistoryRepository) { this.userFeedbackHistoryRepository = userFeedbackHistoryRepository; } public void setPrivacyPolicyNegotiationHistoryRepository(IPrivacyPolicyNegotiationHistoryRepository privacyPolicyNegotiationHistoryRepository) { this.privacyPolicyNegotiationHistoryRepository = privacyPolicyNegotiationHistoryRepository; } public void setAccessControlHistoryRepository(IAccessControlHistoryRepository accessControlHistoryRepository) { this.accessControlHistoryRepository = accessControlHistoryRepository; } /** * This is a non-api method which is used by integration tests to clear the internal state of the UF module */ @Override public void clear() { synchronized (this) { // notify all nodes that UF has been cleared List<UserFeedbackBean> userFeedbackBeans = new ArrayList<UserFeedbackBean>(incompleteUserFeedbackBeans.values()); List<UserFeedbackPrivacyNegotiationEvent> privacyNegotiationEvents = new ArrayList<UserFeedbackPrivacyNegotiationEvent>(incompletePrivacyNegotiationEvents.values()); List<UserFeedbackAccessControlEvent> accessControlEvents = new ArrayList<UserFeedbackAccessControlEvent>(incompleteAccessControlEvents.values()); log.warn(String.format("UserFeedback clear requested: Clearing %s UF events, %s PPN events, %s AC events", userFeedbackBeans.size(), privacyNegotiationEvents.size(), accessControlEvents.size())); expResults.clear(); impResults.clear(); negotiationResults.clear(); accessCtrlResults.clear(); expCallbacks.clear(); impCallbacks.clear(); negotiationCallbacks.clear(); accessCtrlCallbacks.clear(); incompleteUserFeedbackBeans.clear(); incompletePrivacyNegotiationEvents.clear(); incompleteAccessControlEvents.clear(); // notify other nodes that UF events are cleared for (UserFeedbackBean ufBean : userFeedbackBeans) { if (ufBean.getMethod() == FeedbackMethodType.GET_EXPLICIT_FB) { ExpFeedbackResultBean expFeedbackBean = new ExpFeedbackResultBean(); expFeedbackBean.setRequestId(ufBean.getRequestId()); expFeedbackBean.setFeedback(new ArrayList<String>()); try { pubsub.publisherPublish(myCloudID, UserFeedbackEventTopics.COMPLETE, ufBean.getRequestId(), expFeedbackBean); } catch (Exception ex) { log.warn("Error transmitting user feedback complete via pubsub", ex); } } else if (ufBean.getMethod() == FeedbackMethodType.GET_IMPLICIT_FB) { ImpFeedbackResultBean impFeedbackBean = new ImpFeedbackResultBean(); impFeedbackBean.setRequestId(ufBean.getRequestId()); impFeedbackBean.setAccepted(false); try { pubsub.publisherPublish(myCloudID, UserFeedbackEventTopics.COMPLETE, ufBean.getRequestId(), impFeedbackBean); } catch (Exception ex) { log.warn("Error transmitting user feedback clear via pubsub", ex); } } } // notify other nodes that PPN events are cleared for (UserFeedbackPrivacyNegotiationEvent privacyNegotiationEvent : privacyNegotiationEvents) { UserFeedbackPrivacyNegotiationEvent resultBean = new UserFeedbackPrivacyNegotiationEvent(); resultBean.setMethod(FeedbackMethodType.GET_EXPLICIT_FB); resultBean.setType(ExpProposalType.PRIVACY_NEGOTIATION); resultBean.setRequestId(privacyNegotiationEvent.getRequestId()); resultBean.setNegotiationDetails(privacyNegotiationEvent.getNegotiationDetails()); resultBean.setResponsePolicy(privacyNegotiationEvent.getResponsePolicy()); try { pubsub.publisherPublish(myCloudID, EventTypes.UF_PRIVACY_NEGOTIATION_REMOVE_POPUP, resultBean.getRequestId(), resultBean); } catch (Exception ex) { log.warn("Error transmitting user feedback clear via pubsub", ex); } } // notify other nodes that AC events are cleared for (UserFeedbackAccessControlEvent accessControlEvent : accessControlEvents) { UserFeedbackAccessControlEvent resultBean = new UserFeedbackAccessControlEvent(); resultBean.setMethod(FeedbackMethodType.GET_EXPLICIT_FB); resultBean.setType(ExpProposalType.PRIVACY_NEGOTIATION); resultBean.setRequestId(accessControlEvent.getRequestId()); resultBean.setResponseItems(accessControlEvent.getResponseItems()); resultBean.setRequestor(accessControlEvent.getRequestor()); try { pubsub.publisherPublish(myCloudID, EventTypes.UF_PRIVACY_ACCESS_CONTROL_REMOVE_POPUP, resultBean.getRequestId(), resultBean); } catch (Exception ex) { log.warn("Error transmitting user feedback clear via pubsub", ex); } } } } }