/******************************************************************************* * 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; } }