/** * Copyright (c) Codice Foundation * <p> * This is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser * General Public License as published by the Free Software Foundation, either version 3 of the * License, or any later version. * <p> * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. A copy of the GNU Lesser General Public License * is distributed along with this program and can be found at * <http://www.gnu.org/licenses/lgpl.html>. **/ package org.codice.ddf.ui.searchui.query.controller; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.MapUtils; import org.apache.commons.lang.StringUtils; import org.apache.shiro.SecurityUtils; import org.apache.shiro.subject.Subject; import org.codice.ddf.activities.ActivityEvent; import org.codice.ddf.persistence.PersistenceException; import org.codice.ddf.persistence.PersistentItem; import org.codice.ddf.persistence.PersistentStore; import org.cometd.annotation.Listener; import org.cometd.annotation.Service; import org.cometd.bayeux.Message; import org.cometd.bayeux.server.ServerMessage; import org.cometd.bayeux.server.ServerSession; import org.osgi.framework.BundleContext; import org.osgi.service.event.Event; import org.osgi.service.event.EventAdmin; import org.osgi.service.event.EventConstants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The {@code ActivityController} handles the processing and routing of activities. */ @Service public class ActivityController extends AbstractEventController { // CometD requires prepending the topic name with a '/' character, whereas // the OSGi Event Admin doesn't allow it. protected static final String ACTIVITY_TOPIC_COMETD = "/" + ActivityEvent.EVENT_TOPIC; protected static final String ACTIVITY_TOPIC_COMETD_BROADCAST = ACTIVITY_TOPIC_COMETD + "/broadcast"; protected static final String ACTIVITY_TOPIC_COMETD_NEW = ACTIVITY_TOPIC_COMETD + "/new"; private static final Logger LOGGER = LoggerFactory.getLogger(ActivityController.class); private static final String CANCEL_ACTION = "cancel"; private static final String REMOVE_ACTION = "remove"; public ActivityController(PersistentStore persistentStore, BundleContext bundleContext, EventAdmin eventAdmin) { super(persistentStore, bundleContext, eventAdmin); } /** * Implementation of {@link org.osgi.service.event.EventHandler#handleEvent(Event)} that receives * notifications published on the {@link ActivityEvent#EVENT_TOPIC} topic * from the OSGi eventing framework and forwards them to their intended * recipients. * * @throws IllegalArgumentException when any of the following required properties are either * missing from the Event or contain empty values: * <p> * <ul> * <li>{@link ActivityEvent#ID_KEY}</li> * <li>{@link ActivityEvent#MESSAGE_KEY}</li> * <li>{@link ActivityEvent#TIMESTAMP_KEY}</li * <li>{@link ActivityEvent#STATUS_KEY}</li> * <li>{@link ActivityEvent#USER_ID_KEY}</li> * </ul> */ @Override public void handleEvent(Event event) throws IllegalArgumentException { if (null == event.getProperty(ActivityEvent.ID_KEY) || event.getProperty(ActivityEvent.ID_KEY) .toString() .isEmpty()) { throw new IllegalArgumentException( "Activity Event \"" + ActivityEvent.ID_KEY + "\" property is null or empty"); } if (null == event.getProperty(ActivityEvent.MESSAGE_KEY) || event.getProperty(ActivityEvent.MESSAGE_KEY) .toString() .isEmpty()) { throw new IllegalArgumentException("Activity Event \"" + ActivityEvent.MESSAGE_KEY + "\" property is null or empty"); } if (null == event.getProperty(ActivityEvent.TIMESTAMP_KEY)) { throw new IllegalArgumentException( "Activity Event \"" + ActivityEvent.TIMESTAMP_KEY + "\" property is null"); } if (null == event.getProperty(ActivityEvent.STATUS_KEY) || event.getProperty(ActivityEvent.STATUS_KEY) .toString() .isEmpty()) { throw new IllegalArgumentException("Activity Event \"" + ActivityEvent.STATUS_KEY + "\" property is null or empty"); } String sessionId = (String) event.getProperty(ActivityEvent.SESSION_ID_KEY); String userId = (String) event.getProperty(ActivityEvent.USER_ID_KEY); if (StringUtils.isBlank(userId) && StringUtils.isBlank(sessionId)) { throw new IllegalArgumentException("No user information was provided in the Event object. userId and sessionId properties were null"); } ServerSession recipient = null; LOGGER.debug("Getting ServerSession for userId/sessionId {}", userId); recipient = getSessionById(userId, sessionId); if (null != recipient) { Map<String, Object> propMap = new HashMap<>(); for (String key : event.getPropertyNames()) { if (!EventConstants.EVENT_TOPIC.equals(key) && !ActivityEvent.USER_ID_KEY.equals(key) && event.getProperty(key) != null) { propMap.put(key, event.getProperty(key)); } } recipient.deliver(controllerServerSession, ACTIVITY_TOPIC_COMETD_NEW, propMap); } else { LOGGER.debug("Session with ID \"{}\" is not connected to the server. Ignoring activity", sessionId); } } public List<Map<String, Object>> getActivitiesForUser(String userId) { List<Map<String, Object>> activities = new ArrayList<>(); List<Map<String, Object>> results = new ArrayList<>(); try { results = persistentStore.get(PersistentStore.ACTIVITY_TYPE, ActivityEvent.USER_ID_KEY + " = '" + userId + "'"); } catch (PersistenceException e) { LOGGER.debug("PersistenceException trying to get activities for user {}", userId, e); } for (Map<String, Object> result : results) { Map<String, Object> sanitizedResult = PersistentItem.stripSuffixes(result); Map<String, Object> activity = new HashMap<>(); Map<String, String> activityOperations = new HashMap<>(); activity.put(ActivityEvent.OPERATIONS_KEY, activityOperations); for (Map.Entry<String, Object> entry : sanitizedResult.entrySet()) { if (entry.getKey() .contains(ActivityEvent.OPERATIONS_KEY + "_")) { activityOperations.put(entry.getKey() .substring((ActivityEvent.OPERATIONS_KEY + "_").length()), entry.getValue() .toString()); } else { activity.put(entry.getKey(), entry.getValue() .toString()); } } activities.add(activity); } return activities; } @Listener('/' + ActivityEvent.EVENT_TOPIC) public void getPersistedActivities(final ServerSession remote, Message message) { Map<String, Object> data = message.getDataAsMap(); if (MapUtils.isEmpty(data)) { Subject subject = null; try { subject = SecurityUtils.getSubject(); } catch (Exception e) { LOGGER.debug("Couldn't grab user subject from Shiro.", e); } String userId = getUserId(remote, subject); if (null == userId) { throw new IllegalArgumentException("User ID is null"); } List<Map<String, Object>> activities = getActivitiesForUser(userId); if (CollectionUtils.isNotEmpty(activities)) { queuePersistedMessages(remote, activities, ACTIVITY_TOPIC_COMETD_BROADCAST); } } } @Listener("/service/action") public void deletePersistentActivity(ServerSession serverSession, ServerMessage serverMessage) { LOGGER.debug("\nServerSession: {}\nServerMessage: {}", serverSession, serverMessage); if (null == serverSession) { throw new IllegalArgumentException("ServerSession is null"); } if (null == serverMessage) { throw new IllegalArgumentException("ServerMessage is null"); } Subject subject = null; try { subject = SecurityUtils.getSubject(); } catch (Exception e) { LOGGER.debug("Couldn't grab user subject from Shiro.", e); } String userId = getUserId(serverSession, subject); Map<String, Object> dataAsMap = serverMessage.getDataAsMap(); if (dataAsMap != null) { Object activitiesPreCast = dataAsMap.get("data"); Object[] activities = activitiesPreCast instanceof List ? ((List) activitiesPreCast).toArray() : (Object[]) activitiesPreCast; for (Object activityObject : activities) { Map activity = (Map) activityObject; String id = (String) activity.get("id"); String action = (String) activity.get("action"); if (action != null) { if (REMOVE_ACTION.equals(action)) { //You can have a blank id for guest if (id != null) { try { this.persistentStore.delete(PersistentStore.ACTIVITY_TYPE, "id = '" + id + "'"); } catch (PersistenceException e) { throw new IllegalArgumentException( "Unable to delete activity with id = " + id); } } else { throw new IllegalArgumentException("Message id is null"); } } else if (CANCEL_ACTION.equals(action)) { if (null == userId) { throw new IllegalArgumentException("User ID is null"); } if (null == id) { throw new IllegalArgumentException("Metadata ID is null"); } Map<String, Object> jsonPropMap = new HashMap<>(); jsonPropMap.put(ActivityEvent.DOWNLOAD_ID_KEY, id); Event event = new Event(ActivityEvent.EVENT_TOPIC_DOWNLOAD_CANCEL, jsonPropMap); eventAdmin.postEvent(event); } } else { throw new IllegalArgumentException("Message action is null."); } } } else { throw new IllegalArgumentException("Server Message is null."); } } @Override public String getControllerRootTopic() { return ActivityEvent.EVENT_TOPIC; } }