/*******************************************************************************
* Copyright (c) 2004, 2007-2009 IBM Corporation and Cambridge Semantics Incorporated.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* File: $Source: /cvsroot/slrp/boca/com.ibm.adtech.boca.notification.web/JavaSource/com/ibm/adtech/boca/notification/UpdateManager.java,v $
* Created by: Joe Betz
* Created on: 3/22/2006
* Revision: $Id: UpdateManager.java 163 2007-07-31 14:11:08Z mroy $
*
* Contributors:
* IBM Corporation - initial API and implementation
* Cambridge Semantics Incorporated - Fork to Anzo
*******************************************************************************/
package org.openanzo.combus.realtime;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.apache.commons.collections15.MultiMap;
import org.apache.commons.collections15.multimap.MultiHashMap;
import org.openanzo.cache.ICache;
import org.openanzo.cache.ICacheProvider;
import org.openanzo.combus.MessageUtils;
import org.openanzo.combus.endpoint.BaseServiceListener;
import org.openanzo.combus.endpoint.ICombusEndpointListener;
import org.openanzo.datasource.IDatasource;
import org.openanzo.exceptions.AnzoException;
import org.openanzo.exceptions.ExceptionConstants;
import org.openanzo.exceptions.LogUtils;
import org.openanzo.exceptions.Messages;
import org.openanzo.glitter.dataset.QueryDataset;
import org.openanzo.rdf.Constants;
import org.openanzo.rdf.Statement;
import org.openanzo.rdf.URI;
import org.openanzo.rdf.Constants.COMBUS;
import org.openanzo.rdf.Constants.NAMESPACES;
import org.openanzo.rdf.utils.Collections;
import org.openanzo.rdf.utils.CopyOnWriteMultiHashMap;
import org.openanzo.rdf.utils.SerializationConstants;
import org.openanzo.services.AnzoPrincipal;
import org.openanzo.services.IAuthenticationService;
import org.openanzo.services.INamedGraphUpdate;
import org.openanzo.services.IOperationContext;
import org.openanzo.services.IUpdateTransaction;
import org.openanzo.services.Privilege;
import org.openanzo.services.ServicesDictionary;
import org.openanzo.services.impl.BaseOperationContext;
import org.openanzo.services.impl.DatasetTracker;
import org.openanzo.services.impl.SelectorTracker;
import org.openanzo.services.serialization.CommonSerializationUtils;
import org.openanzo.services.serialization.IUpdatesHandler;
import org.openanzo.services.serialization.JSONUpdatesReader;
import org.openanzo.services.serialization.NamedGraphUpdate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* RealtimeUpdatePublisher processes update messages from the update service, and sends messages to registered notification clients
*
* @author Matthew Roy ( <a href="mailto:mroy@cambridgesemantics.com">mroy@cambridgesemantics.com </a>)
*/
public class RealtimeUpdatePublisher extends BaseServiceListener implements ICombusEndpointListener {
private static final Logger log = LoggerFactory.getLogger(RealtimeUpdatePublisher.class);
/** Service Endpoint's Name in {@link String} form */
public static final String SERVICE_NAME = NAMESPACES.SERVICE_PREFIX + "RealtimeUpdatePublisher";
/** Map of Users to their destinations */
private final MultiMap<URI, Destination> userDestinations = new CopyOnWriteMultiHashMap<URI, Destination>();
/** Model service to make calls against */
protected final IDatasource datasource;
/** The current server id for the server */
protected String currentServerId = null;
protected final DestinationTrackerManager trackers = new DestinationTrackerManager();
protected final CopyOnWriteMultiHashMap<URI, DestinationDatasetTracker> datasetTrackers = new CopyOnWriteMultiHashMap<URI, DestinationDatasetTracker>();
protected final CopyOnWriteMultiHashMap<URI, DestinationDatasetTracker> datasetUrisTrackers = new CopyOnWriteMultiHashMap<URI, DestinationDatasetTracker>();
protected final Map<DestinationDatasetTracker, DestinationDatasetTracker> datasetExpandedTrackers = new HashMap<DestinationDatasetTracker, DestinationDatasetTracker>();
protected final CopyOnWriteMultiHashMap<URI, DestinationNamedgraphTracker> namedGraphTrackers = new CopyOnWriteMultiHashMap<URI, DestinationNamedgraphTracker>();
protected MessageProducer producer = null;
protected final Dictionary<? extends Object, ? extends Object> configProperties;
protected final CopyOnWriteMultiHashMap<URI, URI> userRolesCache;
protected final ICache<URI, Set<URI>> graphRolesCache;
protected final Set<URI> registeredSysadmins = java.util.Collections.synchronizedSet(new HashSet<URI>());
IOperationContext context;
/**
* Create UpdateManager for configuration properties
*/
RealtimeUpdatePublisher(Dictionary<? extends Object, ? extends Object> configProperties, IAuthenticationService authenticationService, IDatasource datasource, ICacheProvider cacheProvider) {
super(SERVICE_NAME, 1, 1, authenticationService);
this.datasource = datasource;
this.configProperties = configProperties;
this.userRolesCache = new CopyOnWriteMultiHashMap<URI, URI>();
this.graphRolesCache = cacheProvider.<URI, Set<URI>> openCache("RealtimeGraphCache", 20000, true);
try {
this.context = new BaseOperationContext("RealtimeUpdatePublisher", BaseOperationContext.generateOperationId(), authenticationService.getUserPrincipal(null, ServicesDictionary.getUser(configProperties, null)));
} catch (AnzoException ae) {
log.error(LogUtils.DATASOURCE_MARKER, "Error creating default context", ae);
}
}
private MessageProducer getProducer() throws AnzoException {
if (producer == null) {
if (session == null) {
throw new AnzoException(ExceptionConstants.COMBUS.JMS_NOT_CONNECTED);
}
try {
this.producer = session.createProducer(null);
} catch (JMSException jmsex) {
throw new AnzoException(ExceptionConstants.COMBUS.JMS_CREATE_PRODUCER_FAILED, jmsex);
}
}
return producer;
}
/**
* Reset cache the cache of acls
*
* @param session
* session
* @throws AnzoException
*/
public void reset(Session session) throws AnzoException {
synchronized (datasetTrackers) {
datasetTrackers.clear();
}
userRolesCache.clear();
graphRolesCache.clear();
registeredSysadmins.clear();
trackers.clear();
namedGraphTrackers.clear();
try {
// Now send a transaction complete to all users
TextMessage endMessage = session.createTextMessage();
endMessage.setStringProperty(SerializationConstants.operation, SerializationConstants.reset);
endMessage.setIntProperty(SerializationConstants.protocolVersion, Constants.VERSION);
for (Map.Entry<URI, Collection<Destination>> entry : userDestinations.entrySet()) {
Collection<Destination> destinations = entry.getValue();
for (Destination destination : destinations) {
try {
getProducer().send(destination, endMessage);
} catch (JMSException jmex) {
log.debug(LogUtils.COMBUS_MARKER, "Error sending reset message", jmex);
cleanupDestination(entry.getKey(), destination);
}
}
}
} catch (JMSException jmse) {
throw new AnzoException(ExceptionConstants.COMBUS.PROCESS_UPDATE_FAILED, jmse);
}
//currentServerId = serviceContainer.getInstanceURI().toString();
}
/**
* Register a User with its destination and a session
*
* @param userUri
* UserURI for client
* @param destination
* Destination for client
* @param session
* Session for the destination
* @throws AnzoException
*/
void registerClient(AnzoPrincipal principal, Destination destination) throws AnzoException {
if (log.isDebugEnabled()) {
log.debug("Registering client - user:{} dest:{}", (principal != null ? principal.getUserURI() : null), (destination != null ? destination.toString() : null));
}
if (principal == null) {
throw new AnzoException(ExceptionConstants.CORE.NULL_PARAMETER, "principal");
}
userDestinations.put(principal.getUserURI(), destination);
if (principal.isSysadmin()) {
registeredSysadmins.add(principal.getUserURI());
}
for (URI role : principal.getRoles()) {
userRolesCache.put(role, principal.getUserURI());
}
}
/**
* Deregister a destination from a UserId
*
* @param userUri
* UserURI for client
* @param destination
* Destination to deregister
* @throws AnzoException
*/
void deregisterClient(AnzoPrincipal principal, Destination destination) throws AnzoException {
userDestinations.remove(principal.getUserURI(), destination);
trackers.removeDestinationsTrackers(destination);
removeDestination(destination);
if (principal.isSysadmin()) {
registeredSysadmins.remove(principal.getUserURI());
}
}
/**
* Handle an namedgraph update message from the model service
*
* @param context
* context of operation
* @param session
* JMS session message came in over
* @param message
* JMS Message containing update message stream
* @throws AnzoException
*/
public void handleNamedgraphUpdateMessage(IOperationContext context, Session session, TextMessage message) throws AnzoException {
try {
if (trackers.isEmpty() && datasetTrackers.isEmpty() && namedGraphTrackers.isEmpty())
return;
NotificationUpdateHandler handler = new NotificationUpdateHandler(session, null);
NamedGraphUpdate update = MessageUtils.processNamedGraphUpdateMessage(message);
handler.handleNamedGraphUpdate(update);
Collection<DestinationNamedgraphTracker> ngListeners = namedGraphTrackers.get(update.getUUID());
if (ngListeners != null && !ngListeners.isEmpty()) {
for (DestinationNamedgraphTracker ngt : ngListeners) {
try {
getProducer().send(ngt.getDestination(), message);
} catch (JMSException jmex) {
log.warn(LogUtils.COMBUS_MARKER, MessageFormat.format(Messages.getString(ExceptionConstants.COMBUS.SEND_MESSAGE_FAILED), ngt.getUserUri().toString()), jmex);
cleanupDestination(ngt.getUserUri(), ngt.getDestination());
}
}
}
} catch (JMSException jmsex) {
log.error(LogUtils.COMBUS_MARKER, "Error handling named graph update message", jmsex);
}
}
/**
* Handle an namedgraph update message from the model service
*
* @param context
* context of operation
* @param session
* JMS session message came in over
* @param message
* JMS Message containing update message stream
* @throws AnzoException
*/
public void handleNamedgraphUpdateMessage(String operationid, INamedGraphUpdate update, Map<String, Object> updateMessage) throws AnzoException {
try {
if (trackers.isEmpty() && datasetTrackers.isEmpty() && namedGraphTrackers.isEmpty())
return;
NotificationUpdateHandler handler = new NotificationUpdateHandler(session, null);
handler.handleNamedGraphUpdate(update);
Collection<DestinationNamedgraphTracker> ngListeners = namedGraphTrackers.get(update.getUUID());
if (ngListeners != null && !ngListeners.isEmpty()) {
TextMessage textMessage = session.createTextMessage();
textMessage.setIntProperty(SerializationConstants.protocolVersion, Constants.VERSION);
setMessageProperties(textMessage, updateMessage);
for (DestinationNamedgraphTracker ngt : ngListeners) {
try {
getProducer().send(ngt.getDestination(), textMessage);
} catch (JMSException jmex) {
log.warn(LogUtils.COMBUS_MARKER, MessageFormat.format(Messages.getString(ExceptionConstants.COMBUS.SEND_MESSAGE_FAILED), ngt.getUserUri().toString()), jmex);
cleanupDestination(ngt.getUserUri(), ngt.getDestination());
}
}
}
} catch (JMSException jmsex) {
log.error(LogUtils.COMBUS_MARKER, "Error handling named graph update message", jmsex);
}
}
private void setMessageProperties(Message message, Map<String, Object> properties) throws JMSException {
// How can we do this more efficiently?
for (Map.Entry<String, Object> entry : properties.entrySet()) {
String name = entry.getKey();
Object value = entry.getValue();
if (value instanceof String) {
message.setStringProperty(name, (String) value);
} else if (value instanceof Integer) {
message.setIntProperty(name, ((Integer) value).intValue());
} else if (value instanceof Long) {
message.setLongProperty(name, ((Long) value).longValue());
} else if (value instanceof Float) {
message.setFloatProperty(name, ((Float) value).floatValue());
} else if (value instanceof Double) {
message.setDoubleProperty(name, ((Double) value).doubleValue());
} else if (value instanceof Short) {
message.setShortProperty(name, ((Short) value).shortValue());
} else if (value instanceof Byte) {
message.setByteProperty(name, ((Byte) value).byteValue());
} else if (value instanceof Boolean) {
message.setBooleanProperty(name, ((Boolean) value).booleanValue());
}
}
}
/**
* Handler an update message from the model service
*
* @param context
* context of operation
* @param session
* JMS session message came in over
* @param message
* JMS Message containing update message stream
* @throws AnzoException
*/
public void handleTransactionMessage(IOperationContext context, Session session, TextMessage message) throws AnzoException {
try {
// Get the XML data from the message and parse it with the updates parser
String results = message.getText();
String version = message.getStringProperty(SerializationConstants.version);
if (version != null && currentServerId != null) {
if (!version.equals(currentServerId)) {
return;
}
}
if (trackers.size() == 0)
return;
NotificationUpdateHandler handler = new NotificationUpdateHandler(session, version);
synchronized (userDestinations) {
JSONUpdatesReader.parseUpdateTransactions(results, handler);
}
} catch (JMSException jmsex) {
log.error(LogUtils.COMBUS_MARKER, "Error handling transaction message", jmsex);
}
}
class NotificationUpdateHandler implements IUpdatesHandler {
Session updateSession = null;
String version = null;
NotificationUpdateHandler(Session session, String version) {
this.updateSession = session;
this.version = version;
}
boolean checkVersion() {
if (currentServerId != null && (!currentServerId.equals(version))) {
return true;
} else {
return false;
}
}
public void start() throws AnzoException {
}
public void end() throws AnzoException {
handleDocumentEnd();
}
public void handleTransaction(IUpdateTransaction transaction) throws AnzoException {
if ((transaction.getErrors() == null || transaction.getErrors().size() == 0) && !transaction.isEmpty()) {
for (INamedGraphUpdate update : transaction.getNamedGraphUpdates()) {
handleNamedGraphUpdate(update);
}
}
}
public void handleNamedGraphUpdate(INamedGraphUpdate update) throws AnzoException {
handleNamedGraph(update.getNamedGraphURI());
for (Statement stmt : update.getMetaRemovals()) {
if (!handleStatement(false, stmt))
return;
}
for (Statement stmt : update.getMetaAdditions()) {
if (!handleStatement(true, stmt))
return;
}
for (Statement stmt : update.getRemovals()) {
if (!handleStatement(false, stmt))
return;
}
for (Statement stmt : update.getAdditions()) {
if (!handleStatement(true, stmt))
return;
}
}
boolean handleNamedGraph(URI namedGraphUri) throws AnzoException {
log.debug(LogUtils.COMBUS_MARKER, "handleNamedGraph {}", namedGraphUri);
if (checkVersion())
return false;
if (userDestinations.size() == 0 || datasetTrackers.size() == 0)
return false;
Collection<DestinationDatasetTracker> trackers = datasetUrisTrackers.get(namedGraphUri);
if (trackers != null && trackers.size() > 0) {
for (DestinationDatasetTracker tracker : trackers) {
for (URI uri : tracker.namedGraphsUris) {
datasetTrackers.remove(uri, tracker);
}
}
for (URI uri : getDatasetURIs(namedGraphUri)) {
for (DestinationDatasetTracker tracker : trackers) {
datasetTrackers.put(uri, tracker);
tracker.namedGraphsUris.add(namedGraphUri);
}
}
}
MultiHashMap<URI, DestinationDatasetTracker> matchingTrackers = matchingDatasetTrackers(namedGraphUri);
log.debug(LogUtils.COMBUS_MARKER, "Found {} matchingTrackers for graph {}", matchingTrackers.size(), namedGraphUri);
if (matchingTrackers.size() > 0) {
Set<URI> roles = null;
roles = graphRolesCache.get(namedGraphUri);
if (roles == null) {
roles = datasource.getAuthorizationService().getRolesForGraph(context, namedGraphUri, Privilege.READ);
graphRolesCache.put(namedGraphUri, roles);
}
Set<URI> users = getUsersForRoles(roles);
if (users.size() > 0) {
for (Map.Entry<URI, Collection<DestinationDatasetTracker>> entry : matchingTrackers.entrySet()) {
if (users.contains(entry.getKey())) {
MultiMap<Destination, URI> byDestination = byDestination(entry.getValue());
if (log.isDebugEnabled() && byDestination.size() == 0) {
log.debug(LogUtils.COMBUS_MARKER, "Ignoring graph update since there is no matching destination for graph {}", namedGraphUri);
}
for (Map.Entry<Destination, Collection<URI>> subentry : byDestination.entrySet()) {
try {
TextMessage textMessage = updateSession.createTextMessage();
textMessage.setStringProperty(SerializationConstants.operation, SerializationConstants.datasetUpdate);
textMessage.setStringProperty(SerializationConstants.namedGraphUri, namedGraphUri.toString());
if (version != null) {
textMessage.setStringProperty(SerializationConstants.version, version);
}
textMessage.setIntProperty(SerializationConstants.protocolVersion, Constants.VERSION);
//textMessage.setStringProperty(SerializationConstants.transactionURI, transactionURI.toString());
if (log.isDebugEnabled())
log.debug(MessageUtils.prettyPrint(textMessage, "Sending Notification to " + entry.getKey()));
StringBuilder sb = new StringBuilder();
for (URI dsURI : subentry.getValue()) {
sb.append(dsURI.toString());
sb.append(",");
}
textMessage.setStringProperty(SerializationConstants.datasetUri, sb.toString());
getProducer().send(subentry.getKey(), textMessage);
} catch (JMSException jmex) {
log.error(LogUtils.COMBUS_MARKER, MessageFormat.format(Messages.getString(ExceptionConstants.COMBUS.SEND_MESSAGE_FAILED), entry.getKey().toString()), jmex);
cleanupDestination(entry.getKey(), subentry.getKey());
}
}
} else {
log.debug(LogUtils.COMBUS_MARKER, "Ignoring graph update since no users are listening that are allowed to see the graph {}", namedGraphUri);
}
}
} else {
log.debug(LogUtils.COMBUS_MARKER, "Ignoring graph update since no users belong to the roles for graph {}", namedGraphUri);
}
} else {
log.debug(LogUtils.COMBUS_MARKER, "Ignoring graph update since there are no matching trackers for graph {}", namedGraphUri);
}
return true;
}
boolean handleStatement(boolean additions, Statement statement) throws AnzoException {
if (checkVersion())
return false;
if (userDestinations.size() == 0 || trackers.size() == 0)
return false;
Set<URI> roles = null;
Map<Destination, URI> matches = trackers.matchingDestinations(statement.getSubject(), statement.getPredicate(), statement.getObject(), statement.getNamedGraphUri());
if (matches.size() > 0) {
roles = graphRolesCache.get(statement.getNamedGraphUri());
if (roles == null) {
roles = datasource.getAuthorizationService().getRolesForGraph(context, statement.getNamedGraphUri(), Privilege.READ);
graphRolesCache.put(statement.getNamedGraphUri(), roles);
}
Set<URI> users = getUsersForRoles(roles);
if (users.size() > 0) {
TextMessage textMessage = null;
try {
textMessage = updateSession.createTextMessage();
textMessage.setStringProperty(SerializationConstants.operation, SerializationConstants.updateResults);
textMessage.setBooleanProperty(SerializationConstants.method, additions);
textMessage.setStringProperty(SerializationConstants.type, SerializationConstants.statement);
CommonSerializationUtils.setSubjectInMessage(textMessage, statement.getSubject());
textMessage.setStringProperty(SerializationConstants.predicate, statement.getPredicate().toString());
textMessage.setStringProperty(SerializationConstants.namedGraphUri, statement.getNamedGraphUri().toString());
CommonSerializationUtils.setObjectInMessage(textMessage, statement.getObject());
if (version != null) {
textMessage.setStringProperty(SerializationConstants.version, version);
}
textMessage.setIntProperty(SerializationConstants.protocolVersion, Constants.VERSION);
//textMessage.setStringProperty(SerializationConstants.transactionURI, transactionURI.toString());
} catch (JMSException jmse) {
throw new AnzoException(ExceptionConstants.COMBUS.CREATE_MESSAGE_FAILED, jmse);
}
for (Map.Entry<Destination, URI> entry : matches.entrySet()) {
if (users.contains(entry.getValue())) {
try {
if (log.isDebugEnabled())
log.debug(MessageUtils.prettyPrint(textMessage, "Sending Notification to " + entry.getKey()));
getProducer().send(entry.getKey(), textMessage);
} catch (JMSException jmex) {
log.error(LogUtils.COMBUS_MARKER, MessageFormat.format(Messages.getString(ExceptionConstants.COMBUS.SEND_MESSAGE_FAILED), entry.getKey().toString()), jmex);
cleanupDestination(entry.getValue(), entry.getKey());
}
}
}
}
}
return true;
}
boolean handleDocumentEnd() throws AnzoException {
if (checkVersion())
return false;
return true;
}
}
private void cleanupDestination(URI userUri, Destination destination) {
trackers.removeDestinationsTrackers(destination);
userDestinations.remove(userUri, destination);
}
void registerTracker(IOperationContext context, Set<? extends SelectorTracker> selectorTrackers, Set<? extends DatasetTracker> datasetTrackersIn, Set<URI> namedGraphTrackers, Destination destination) throws AnzoException {
URI userURI = context.getOperationPrincipal().getUserURI();
if (log.isDebugEnabled()) {
log.debug(LogUtils.COMBUS_MARKER, "registerTracker for {} selectorTrackers, {} datasetTrackers and " + (namedGraphTrackers == null ? 0 : namedGraphTrackers.size()) + " to destination '" + destination + "' for user " + userURI, (selectorTrackers == null ? 0 : selectorTrackers.size()), (datasetTrackersIn == null ? 0 : datasetTrackersIn.size()));
}
if (selectorTrackers != null) {
for (SelectorTracker trackerObject : selectorTrackers) {
if (trackerObject instanceof DestinationSelectorTracker) {
trackers.addTracker((DestinationSelectorTracker) trackerObject);
} else {
SelectorTracker tracker = trackerObject;
trackers.addTracker(new DestinationSelectorTracker(destination, userURI, tracker.getSubject(), tracker.getPredicate(), tracker.getObject(), tracker.getNamedGraphUri()));
}
}
}
if (datasetTrackersIn != null) {
for (DatasetTracker trackerObject : datasetTrackersIn) {
log.debug(LogUtils.COMBUS_MARKER, "registerTracker got request to add datasetTracker {}", trackerObject.getTrackerURI());
if (trackerObject instanceof DestinationDatasetTracker) {
addDatasetTracker(context, (DestinationDatasetTracker) trackerObject);
} else {
DestinationDatasetTracker ddt = new DestinationDatasetTracker(destination, userURI, trackerObject);
addDatasetTracker(context, ddt);
}
}
}
if (namedGraphTrackers != null) {
for (URI graph : namedGraphTrackers) {
boolean ok = true;
if (!context.getOperationPrincipal().isSysadmin()) {
URI namedGraphUri = datasource.getModelService().getUriForUUID(context, graph);
Set<URI> roles = graphRolesCache.get(graph);
if (roles == null) {
roles = datasource.getAuthorizationService().getRolesForGraph(context, namedGraphUri, Privilege.READ);
graphRolesCache.put(graph, roles);
graphRolesCache.put(namedGraphUri, roles);
}
if (!Collections.memberOf(roles, context.getOperationPrincipal().getRoles())) {
ok = false;
}
}
if (ok) {
this.namedGraphTrackers.put(graph, new DestinationNamedgraphTracker(destination, context.getOperationPrincipal().getUserURI(), graph));
}
}
}
}
private MultiHashMap<URI, DestinationDatasetTracker> matchingDatasetTrackers(URI namedGraphUri) {
MultiHashMap<URI, DestinationDatasetTracker> results = new MultiHashMap<URI, DestinationDatasetTracker>();
Collection<DestinationDatasetTracker> trackers = datasetTrackers.get(namedGraphUri);
if (trackers != null) {
for (DestinationDatasetTracker entry : trackers) {
results.put(entry.userUri, entry);
}
}
return results;
}
private MultiHashMap<Destination, URI> byDestination(Collection<DestinationDatasetTracker> trackers) {
MultiHashMap<Destination, URI> results = new MultiHashMap<Destination, URI>();
for (DestinationDatasetTracker entry : trackers) {
results.put(entry.destination, entry.getTrackerURI());
}
return results;
}
protected Collection<URI> getDatasetURIs(URI namedDataset) throws AnzoException {
Collection<URI> uris = new ArrayList<URI>();
QueryDataset uriSet = datasource.getModelService().resolveNamedDataset(context, namedDataset);
if (uriSet != null) {
if (uriSet.getDefaultGraphURIs() != null) {
for (URI uri : uriSet.getDefaultGraphURIs()) {
uris.add(uri);
}
}
if (uriSet.getNamedGraphURIs() != null) {
for (URI uri : uriSet.getNamedGraphURIs()) {
uris.add(uri);
}
}
}
return uris;
}
private void addDatasetTracker(IOperationContext context, DestinationDatasetTracker tracker) throws AnzoException {
if (log.isDebugEnabled()) {
if (tracker == null) {
log.debug(LogUtils.COMBUS_MARKER, "Trying to add null datasetTracker.");
} else {
log.debug(LogUtils.COMBUS_MARKER, "Adding datasetTracker " + tracker.getTrackerURI() + " with " + (tracker.getDefaultGraphs() == null ? 0 : tracker.getDefaultGraphs().size()) + " default graphs, " + (tracker.getNamedGraphs() == null ? 0 : tracker.getNamedGraphs().size()) + " named graphs, and " + (tracker.getNamedDatasets() == null ? 0 : tracker.getNamedDatasets().size()) + " named datasets for destination '" + tracker.getDestination() + "' for user " + tracker.getUserUri());
}
}
if (tracker == null) {
throw new AnzoException(ExceptionConstants.CORE.NULL_PARAMETER, "tracker");
}
synchronized (datasetTrackers) {
for (URI ngURI : tracker.getDefaultGraphs()) {
datasetTrackers.put(ngURI, tracker);
}
for (URI ngURI : tracker.getNamedGraphs()) {
datasetTrackers.put(ngURI, tracker);
}
if (tracker.getNamedDatasets() != null) {
for (URI dsUri : tracker.getNamedDatasets()) {
datasetUrisTrackers.put(dsUri, tracker);
datasetTrackers.put(dsUri, tracker);
for (URI uri : getDatasetURIs(dsUri)) {
datasetTrackers.put(uri, tracker);
tracker.namedGraphsUris.add(uri);
}
// Save this tracker instance with the expanded namedGraphsUris so that we don't have to re-expand
// when removing the tracker.
datasetExpandedTrackers.put(tracker, tracker);
}
}
if (log.isDebugEnabled()) {
log.debug(LogUtils.COMBUS_MARKER, "After adding datasetTracker, the size of datasetTrackers map is {}.", datasetTrackers.size());
}
}
}
private void removeDatasetTracker(IOperationContext context, DestinationDatasetTracker tracker) throws AnzoException {
log.debug(LogUtils.COMBUS_MARKER, "Removing dataset tracker {}", (tracker != null ? tracker.getTrackerURI() : null));
if (tracker == null) {
throw new AnzoException(ExceptionConstants.CORE.NULL_PARAMETER, "tracker");
}
synchronized (datasetTrackers) {
if (tracker.getDefaultGraphs() != null) {
for (URI ngURI : tracker.getDefaultGraphs()) {
datasetTrackers.remove(ngURI, tracker);
}
}
if (tracker.getNamedGraphs() != null) {
for (URI ngURI : tracker.getNamedGraphs()) {
datasetTrackers.remove(ngURI, tracker);
}
}
if (tracker.getNamedDatasets() != null) {
for (URI dsUri : tracker.getNamedDatasets()) {
datasetTrackers.remove(dsUri, tracker);
datasetUrisTrackers.remove(dsUri, tracker);
// The tracker passed into the this method is likely to not have the namedGraphsUris all filled in, so we lookup
// the expanded version of the tracker to remove things.
DestinationDatasetTracker expandedTracker = datasetExpandedTrackers.get(tracker);
if (expandedTracker != null) {
for (URI uri : expandedTracker.namedGraphsUris) {
datasetTrackers.remove(uri, tracker);
}
}
datasetExpandedTrackers.remove(tracker);
}
}
}
}
private void removeDestination(Destination destination) {
synchronized (datasetTrackers) {
MultiMap<URI, DestinationDatasetTracker> toRemove = new MultiHashMap<URI, DestinationDatasetTracker>();
for (Map.Entry<URI, Collection<DestinationDatasetTracker>> entry : datasetTrackers.entrySet()) {
for (DestinationDatasetTracker tracker : entry.getValue()) {
if (tracker.getDestination().equals(destination)) {
toRemove.put(entry.getKey(), tracker);
}
}
}
for (Map.Entry<URI, Collection<DestinationDatasetTracker>> entry : toRemove.entrySet()) {
for (DestinationDatasetTracker tracker : entry.getValue()) {
datasetTrackers.remove(entry.getKey(), tracker);
}
}
}
synchronized (namedGraphTrackers) {
MultiMap<URI, DestinationNamedgraphTracker> toRemove = new MultiHashMap<URI, DestinationNamedgraphTracker>();
for (Map.Entry<URI, Collection<DestinationNamedgraphTracker>> entry : namedGraphTrackers.entrySet()) {
for (DestinationNamedgraphTracker dest : entry.getValue()) {
if (dest.getDestination().equals(destination)) {
toRemove.put(entry.getKey(), dest);
}
}
}
for (Map.Entry<URI, Collection<DestinationNamedgraphTracker>> entry : toRemove.entrySet()) {
for (DestinationNamedgraphTracker dest : entry.getValue()) {
namedGraphTrackers.remove(entry.getKey(), dest);
}
}
}
}
void unregisterTracker(IOperationContext context, Set<? extends SelectorTracker> selectorTrackers, Set<? extends DatasetTracker> datasetTrackersIn, Set<URI> namedGraphTrackers, Destination destination) throws AnzoException {
URI userURI = context.getOperationPrincipal().getUserURI();
if (selectorTrackers != null) {
for (SelectorTracker trackerObject : selectorTrackers) {
if (trackerObject instanceof DestinationSelectorTracker) {
trackers.removeTracker((DestinationSelectorTracker) trackerObject);
} else {
SelectorTracker tracker = trackerObject;
trackers.removeTracker(new DestinationSelectorTracker(destination, userURI, tracker.getSubject(), tracker.getPredicate(), tracker.getObject(), tracker.getNamedGraphUri()));
}
}
}
if (datasetTrackersIn != null) {
for (DatasetTracker trackerObject : datasetTrackersIn) {
if (trackerObject instanceof DestinationDatasetTracker) {
removeDatasetTracker(context, (DestinationDatasetTracker) trackerObject);
} else {
DestinationDatasetTracker ddt = new DestinationDatasetTracker(destination, userURI, trackerObject);
removeDatasetTracker(context, ddt);
}
}
}
if (namedGraphTrackers != null) {
for (URI graph : namedGraphTrackers) {
this.namedGraphTrackers.remove(graph, new DestinationNamedgraphTracker(destination, context.getOperationPrincipal().getUserURI(), graph));
}
}
}
public TextMessage handleMessage(IOperationContext context, Destination replyTo, String format, String operation, TextMessage request, MessageProducer messageProducer) throws JMSException, AnzoException {
verifyCaller(context);
if (operation != null && SerializationConstants.updateResults.equals(operation)) {
handleTransactionMessage(context, session, request);
return null;
} else if (operation != null && SerializationConstants.namedGraphUpdate.equals(operation)) {
handleNamedgraphUpdateMessage(context, session, request);
return null;
} else if (SerializationConstants.reset.equals(operation)) {
reset(session);
return null;
}
throw new AnzoException(ExceptionConstants.SERVER.UNKNOWN_OPERATION_ERROR, operation);
}
public String getQueueName() {
return COMBUS.NOTIFICATION_UPDATES_QUEUE;
}
private Set<URI> getUsersForRoles(Set<URI> roles) {
HashSet<URI> users = new HashSet<URI>();
for (URI role : roles) {
Collection<URI> u = userRolesCache.get(role);
if (u != null)
users.addAll(u);
}
synchronized (registeredSysadmins) {
users.addAll(registeredSysadmins);
}
return users;
}
}