/**
* Copyright (c) Codice Foundation
*
* 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.
*
* 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.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.collections.CollectionUtils;
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;
import net.minidev.json.JSONObject;
/**
* 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;
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.MESSAGE_KEY
+ "\" property is null or empty");
}
String sessionId = (String) event.getProperty(ActivityEvent.SESSION_ID_KEY);
if (StringUtils.isEmpty(sessionId)) {
throw new IllegalArgumentException("Activity Event \"" + ActivityEvent.SESSION_ID_KEY
+ "\" property is null or empty");
}
String userId = (String) event.getProperty(ActivityEvent.USER_ID_KEY);
// Blank user ID is allowed as this indicates the anonymous user
if (null == userId) {
throw new IllegalArgumentException("Activity Event \"" + ActivityEvent.USER_ID_KEY
+ "\" property is null or empty");
}
ServerSession recipient = null;
if (StringUtils.isNotBlank(userId)) {
LOGGER.debug("Getting ServerSession for userId {}", userId);
recipient = getSessionByUserId(userId);
} else {
LOGGER.debug("Getting ServerSession for sessionId {}", sessionId);
recipient = getSessionByUserId(sessionId);
}
if (null != recipient) {
JSONObject jsonPropMap = new JSONObject();
for (String key : event.getPropertyNames()) {
if (!EventConstants.EVENT_TOPIC.equals(key) && !ActivityEvent.USER_ID_KEY
.equals(key) && event.getProperty(key) != null) {
jsonPropMap.put(key, event.getProperty(key));
}
}
LOGGER.debug("Sending the following property map \"{}\": ", jsonPropMap.toJSONString());
recipient.deliver(controllerServerSession, ACTIVITY_TOPIC_COMETD,
jsonPropMap.toJSONString(), null);
} 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<Map<String, Object>>();
List<Map<String, Object>> results = new ArrayList<Map<String, Object>>();
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<String, Object>();
activity.put(ActivityEvent.OPERATIONS_KEY, new HashMap<String, String>());
for (Map.Entry<String, Object> entry : sanitizedResult.entrySet()) {
if (entry.getKey().contains(ActivityEvent.OPERATIONS_KEY + "_")) {
((Map) activity.get(ActivityEvent.OPERATIONS_KEY)).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 (CollectionUtils.isEmpty((Collection) 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((Collection) activities)) {
queuePersistedMessages(remote, activities, ACTIVITY_TOPIC_COMETD);
}
}
}
@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 anonymous
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");
}
JSONObject jsonPropMap = new JSONObject();
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;
}
}