/*******************************************************************************
* Copyright (c) 2007 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
*
* Contributors:
* Cambridge Semantics Incorporated
*******************************************************************************/
package org.openanzo.client;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import org.apache.commons.collections15.CollectionUtils;
import org.openanzo.combus.CombusConnection;
import org.openanzo.combus.MessageUtils;
import org.openanzo.exceptions.AnzoException;
import org.openanzo.exceptions.ExceptionConstants;
import org.openanzo.exceptions.LogUtils;
import org.openanzo.exceptions.Messages;
import org.openanzo.rdf.Constants;
import org.openanzo.rdf.Dataset;
import org.openanzo.rdf.IDataset;
import org.openanzo.rdf.IDatasetListener;
import org.openanzo.rdf.IStatementListener;
import org.openanzo.rdf.MemQuadStore;
import org.openanzo.rdf.MemURI;
import org.openanzo.rdf.RDFFormat;
import org.openanzo.rdf.Resource;
import org.openanzo.rdf.Statement;
import org.openanzo.rdf.URI;
import org.openanzo.rdf.Value;
import org.openanzo.rdf.Constants.COMBUS;
import org.openanzo.rdf.utils.ReadWriteUtils;
import org.openanzo.rdf.utils.SerializationConstants;
import org.openanzo.services.INotificationRegistrationService;
import org.openanzo.services.IOperationContext;
import org.openanzo.services.ITracker;
import org.openanzo.services.IUpdateTransaction;
import org.openanzo.services.impl.BaseOperationContext;
import org.openanzo.services.impl.DatasetTracker;
import org.openanzo.services.impl.SelectorTracker;
import org.openanzo.services.impl.UpdateTransaction;
import org.openanzo.services.serialization.CommonSerializationUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Manager that manages the realtime event trackers
*
* @author Ben Szekely ( <a href="mailto:ben@cambridgesemantics.com">ben@cambridgesemantics.com </a>)
*
*/
public class RealtimeUpdateManager implements MessageListener {
private static final Logger log = LoggerFactory.getLogger(RealtimeUpdateManager.class);
protected boolean connected = false;
private final Set<ITransactionListener> transactionListeners = Collections.synchronizedSet(new HashSet<ITransactionListener>());
protected final AnzoClient anzoClient;
protected final CombusConnection combusConnection;
protected final Map<Statement, SelectorTracker> patternToTracker;
protected final Map<URI, DatasetTracker> datasetToTrackers;
protected final MemQuadStore quadStore;
private final ReentrantReadWriteLock trackerLock = new ReentrantReadWriteLock();
private final ReentrantReadWriteLock transactionLock = new ReentrantReadWriteLock();
protected RealtimeUpdateManager(AnzoClient client) {
this.anzoClient = client;
this.combusConnection = client.clientDatasource.getCombusConnection();
quadStore = new MemQuadStore();
patternToTracker = new HashMap<Statement, SelectorTracker>();
datasetToTrackers = new HashMap<URI, DatasetTracker>();
}
/**
* Checks if a tracker is registered for the provided pattern.
*
* @param subject
* The subject or null for wildcard.
* @param predicate
* The predicate or null for wildcard.
* @param object
* The object or null for wildcard.
* @param namedGraphUri
* The namedGraphUri or null for wildcard.
* @return true if there is a tracker that matches the pattern
*/
boolean containsTracker(Resource subject, URI predicate, Value object, URI namedGraphUri) {
return patternToTracker.containsKey(Constants.valueFactory.createMatchStatement(subject, predicate, object, namedGraphUri));
}
/**
* Add an {@link ITransactionListener} that is notified when ever a transaction event occurs on the server
*
* @param listener
* lister to be notified
* @throws AnzoException
*/
public void addTransactionListener(ITransactionListener listener) throws AnzoException {
transactionListeners.add(listener);
if (transactionListeners.size() == 1 && connected) {
try {
combusConnection.registerTopicListener(COMBUS.TRANSACTIONS_TOPIC, this);
} catch (AnzoException ae) {
transactionListeners.remove(listener);
}
}
}
/**
* Unregister an {@link ITransactionListener}
*
* @param listener
* listener to unregister
* @throws AnzoException
*/
public void removeTransactionListener(ITransactionListener listener) throws AnzoException {
transactionListeners.remove(listener);
if (transactionListeners.size() == 0 && connected) {
try {
combusConnection.unregisterTopicListener(COMBUS.TRANSACTIONS_TOPIC);
} catch (AnzoException ae) {
transactionListeners.remove(listener);
}
}
}
void notifyTransactionListeners(IUpdateTransaction update) throws AnzoException {
IDataset dataset = new Dataset();
if (update.getTransactionContext() != null) {
for (Statement stmt : update.getTransactionContext()) {
URI namedGraphUri = stmt.getNamedGraphUri();
if (!dataset.containsNamedGraph(namedGraphUri)) {
dataset.addNamedGraph(namedGraphUri);
}
dataset.add(stmt);
}
}
synchronized (transactionListeners) {
for (ITransactionListener listener : transactionListeners) {
listener.transactionComplete(update.getURI(), update.getTransactionTimestamp(), anzoClient.convertUUIDSToNamedGraphURIs(update.getUpdatedNamedGraphRevisions().keySet()), dataset);
}
}
}
/**
* Adds a tracker for the provided pattern (subject, predicate, object and namedGraphUri). Registers the provided listener to receive events about quads
* matching the pattern.
*
* @param subject
* The subject of the quad, or null to match all subjects.
* @param predicate
* The predicate of the quad, or null to match all preciates.
* @param object
* The object of the quad, or null to match all objects.
* @param namedGraphUri
* The namedGraphUri of the quad, or null to match all namedGraphUris.
* @param listener
* if not null, this listener is registered to receive events about quads matching the pattern of tracker.
* @throws AnzoException
*/
public void addTracker(Resource subject, URI predicate, Value object, URI namedGraphUri, IStatementListener<ITracker> listener) throws AnzoException {
try {
trackerLock.writeLock().lockInterruptibly();
Statement statement = Constants.valueFactory.createMatchStatement(subject, predicate, object, namedGraphUri);
SelectorTracker tracker = patternToTracker.get(statement);
if (tracker == null) {
tracker = new SelectorTracker(subject, predicate, object, namedGraphUri);
patternToTracker.put(statement, tracker);
setupTrackerNotification(tracker);
quadStore.add(statement);
}
tracker.addListener(listener);
} catch (InterruptedException e) {
throw new AnzoException(ExceptionConstants.CLIENT.CLIENT_LOCK_ERROR, e);
} finally {
trackerLock.writeLock().unlock();
}
}
/**
* Removes all trackers matching the provided pattern.
*
* @param subject
* The subject of the quad, or null to match all subjects.
* @param predicate
* The predicate of the quad, or null to match all preciates.
* @param object
* The object of the quad, or null to match all objects.
* @param namedGraphUri
* The namedGraphUri of the quad, or null to match all namedGraphUris.
* @throws AnzoException
*/
public void removeTracker(Resource subject, URI predicate, Value object, URI namedGraphUri) throws AnzoException {
Statement pattern = Constants.valueFactory.createMatchStatement(subject, predicate, object, namedGraphUri);
SelectorTracker tracker = patternToTracker.get(pattern);
if (tracker != null) {
IOperationContext context = new BaseOperationContext(INotificationRegistrationService.REGISTER_TRACKERS, BaseOperationContext.generateOperationId(), null);
try {
boolean regOk = anzoClient.clientDatasource.getNotificationRegistrationService().unregisterTrackers(context, Collections.<SelectorTracker> singleton(tracker), Collections.<DatasetTracker> emptySet(),Collections.<URI>emptySet(), null);
if (!regOk) {
throw new AnzoException(ExceptionConstants.COMBUS.JMS_REGISTER_SELECTOR_ERROR);
}
} finally {
context.clearMDC();
}
patternToTracker.remove(pattern);
quadStore.remove(pattern);
}
}
/**
* Add a dataset tracker
*
* @param trackerUri
* URI for this tracker
* @param defaultGraphs
* set of default graphs to monitor in this tracker
* @param namedGraphs
* set of named graphs to monitor in this tracker
* @param namedDatasets
* named datasets whose graphs are to be monitored
* @param listener
* Listener that is notified when one of the graphs changes
* @throws AnzoException
*/
public void addTracker(URI trackerUri, Set<URI> defaultGraphs, Set<URI> namedGraphs, Set<URI> namedDatasets, IDatasetListener listener) throws AnzoException {
try {
trackerLock.writeLock().lockInterruptibly();
DatasetTracker tracker = datasetToTrackers.get(trackerUri);
if (tracker == null) {
tracker = new DatasetTracker(trackerUri, defaultGraphs, namedGraphs, namedDatasets);
datasetToTrackers.put(trackerUri, tracker);
setupTrackerNotification(tracker);
}
tracker.addListener(listener);
} catch (InterruptedException e) {
throw new AnzoException(ExceptionConstants.CLIENT.CLIENT_LOCK_ERROR, e);
} finally {
trackerLock.writeLock().unlock();
}
}
/**
* Remove the tracker based on its URI
*
* @param trackerUri
* URI of dataset tracker to remove
* @throws AnzoException
*/
public void removeTracker(URI trackerUri) throws AnzoException {
DatasetTracker tracker = datasetToTrackers.remove(trackerUri);
if (tracker != null) {
IOperationContext context = new BaseOperationContext(INotificationRegistrationService.REGISTER_TRACKERS, BaseOperationContext.generateOperationId(), null);
try {
boolean regOk = anzoClient.clientDatasource.getNotificationRegistrationService().unregisterTrackers(context, Collections.<SelectorTracker> emptySet(), Collections.<DatasetTracker> singleton(tracker),Collections.<URI>emptySet(), null);
if (!regOk) {
throw new AnzoException(ExceptionConstants.COMBUS.JMS_REGISTER_SELECTOR_ERROR);
}
} finally {
context.clearMDC();
}
}
}
/**
* Removes the given listener from the tracker matching the given pattern
*
* @param subject
* The subject of the quad, or null to match all subjects.
* @param predicate
* The predicate of the quad, or null to match all preciates.
* @param object
* The object of the quad, or null to match all objects.
* @param namedGraphUri
* The namedGraphUri of the quad, or null to match all namedGraphUris.
* @param listener
* Listener to unregister from the given tracker pattern
* @throws AnzoException
*/
public void removeTrackerListener(Resource subject, URI predicate, Value object, URI namedGraphUri, IStatementListener<ITracker> listener) throws AnzoException {
SelectorTracker tracker = patternToTracker.get(Constants.valueFactory.createMatchStatement(subject, predicate, object, namedGraphUri));
if (tracker != null) {
tracker.removeListener(listener);
if (tracker.getListeners().isEmpty()) {
removeTracker(subject, predicate, object, namedGraphUri);
}
}
}
/**
* Remove a listener for the given dataset tracker
*
* @param trackerUri
* URI of the dataset tracker from which to unregister
* @param listener
* listener to unregister
* @throws AnzoException
*/
public void removeTrackerListener(URI trackerUri, IDatasetListener listener) throws AnzoException {
DatasetTracker tracker = datasetToTrackers.get(trackerUri);
if (tracker != null) {
tracker.removeListener(listener);
if (tracker.getListeners().isEmpty()) {
removeTracker(trackerUri);
}
}
}
/**
* Registers with the server to receive notifications of quads matching this tracker's pattern if notification is enabled.
*
* @param tracker
* The tracker to register.
* @throws AnzoException
*/
private void setupTrackerNotification(SelectorTracker tracker) throws AnzoException {
if (anzoClient.isConnected()) {
IOperationContext context = new BaseOperationContext(INotificationRegistrationService.REGISTER_TRACKERS, BaseOperationContext.generateOperationId(), anzoClient.getServicePrincipal());
try {
boolean regOk = anzoClient.clientDatasource.getNotificationRegistrationService().registerTrackers(context, Collections.<SelectorTracker> singleton(tracker), Collections.<DatasetTracker> emptySet(), Collections.<URI> emptySet(), null);
if (!regOk) {
throw new AnzoException(ExceptionConstants.COMBUS.JMS_REGISTER_SELECTOR_ERROR);
}
} finally {
context.clearMDC();
}
}
}
private void setupTrackerNotification(DatasetTracker tracker) throws AnzoException {
if (anzoClient.isConnected()) {
IOperationContext context = new BaseOperationContext(INotificationRegistrationService.REGISTER_TRACKERS, BaseOperationContext.generateOperationId(), null);
try {
boolean regOk = anzoClient.clientDatasource.getNotificationRegistrationService().registerTrackers(context, Collections.<SelectorTracker> emptySet(), Collections.<DatasetTracker> singleton(tracker), Collections.<URI> emptySet(), null);
if (!regOk) {
throw new AnzoException(ExceptionConstants.COMBUS.JMS_REGISTER_SELECTOR_ERROR);
}
} finally {
context.clearMDC();
}
}
}
/**
* Notifies the trackers of statement changes.
*
* @param addition
* If true then the event is for addition of a new statement, if false then deletion of a statement.
* @param statement
* The statement that was added or removed.
*/
private void notifyTrackers(boolean addition, Statement statement) {
Collection<Statement> result = this.findMatchingPatterns(statement);
for (Statement stmt : result) {
SelectorTracker tracker = patternToTracker.get(stmt);
if (tracker != null) {
tracker.notifyListeners(addition, statement);
}
}
}
private void notifyTrackers(URI namedGraphUri, Collection<URI> datasetUris) {
for (URI datasetURI : datasetUris) {
DatasetTracker tracker = datasetToTrackers.get(datasetURI);
if (tracker != null) {
tracker.notifyListeners(namedGraphUri);
}
}
}
/**
* Finds all the tracker patterns that match the provided statements.
*
* Implementation:
*
* Trackers are stored as quads in a quad store, allowing the find operation to be use to find the matching trackers.
*
* Nodes in a tracker pattern may be the special "ANY_URI", indicating the node in the quad is a wildcard.
*
* The matching trackers are found as follows:
*
* <pre>
* (INTERSECTION
* (UNION trackers-with-matching-subject trackers-with-wildcard-subject)
* (UNION trackers-with-matching-predicate trackers-with-wildcard-predicate)
* (UNION trackers-with-matching-object trackers-with-wildcard-object)
* (UNION trackers-with-matching-namedGraphUri trackers-with-wildcard-namedGraphUri))
* </pre>
*
* @param statement
* The statement find matching tracker patterns for.
* @return The tracker patterns matching the provided statement.
*/
protected Collection<Statement> findMatchingPatterns(Statement statement) {
Collection<Statement> subjectExactMatches = quadStore.find(statement.getSubject(), null, null, (URI[]) null);
Collection<Statement> subjectWildcardMatches = quadStore.find(Constants.ANY_URI, null, null, (URI[]) null);
// UNION exact and wildcard matches of the statements subject
Collection<Statement> subjectMatches = CollectionUtils.union(subjectExactMatches, subjectWildcardMatches);
Collection<Statement> predicateExactMatches = quadStore.find(null, statement.getPredicate(), null, (URI[]) null);
Collection<Statement> predicateWildcardMatches = quadStore.find(null, Constants.ANY_URI, null, (URI[]) null);
// UNION exact and wildcard matches of the statements predicate
Collection<Statement> predicateMatches = CollectionUtils.union(predicateExactMatches, predicateWildcardMatches);
Collection<Statement> objectExactMatches = quadStore.find(null, null, statement.getObject(), (URI[]) null);
Collection<Statement> objectWildcardMatches = quadStore.find(null, null, Constants.ANY_URI, (URI[]) null);
// UNION exact and wildcard matches of the statements object
Collection<Statement> objectMatches = CollectionUtils.union(objectExactMatches, objectWildcardMatches);
Collection<Statement> namedGraphExactMatches = quadStore.find(null, null, null, statement.getNamedGraphUri());
Collection<Statement> namedGraphWildcardMatches = quadStore.find(null, null, null, Constants.ANY_URI);
// UNION exact and wildcard matches of the statements namedGraphUri
Collection<Statement> namedGraphMatches = CollectionUtils.union(namedGraphExactMatches, namedGraphWildcardMatches);
// INTERSECTION of the unions
Collection<Statement> intersection = CollectionUtils.intersection(CollectionUtils.intersection(CollectionUtils.intersection(subjectMatches, predicateMatches), objectMatches), namedGraphMatches);
return intersection;
}
protected void connect() throws AnzoException {
IOperationContext context = null;
try {
context = new BaseOperationContext(INotificationRegistrationService.REGISTER_SUBSCRIBER, BaseOperationContext.generateOperationId(), anzoClient.getServicePrincipal());
boolean regOk = anzoClient.clientDatasource.getNotificationRegistrationService().registerSubscriber(context, null);
if (!regOk) {
throw new AnzoException(ExceptionConstants.COMBUS.SERVER_CONNECT_EXCEPTION);
}
this.combusConnection.registerMessageListener(this);
for (Statement pattern : patternToTracker.keySet()) {
SelectorTracker tracker = patternToTracker.get(pattern);
setupTrackerNotification(tracker);
}
if (transactionListeners.size() > 0) {
combusConnection.registerTopicListener(COMBUS.TRANSACTIONS_TOPIC, this);
}
connected = true;
} finally {
if (context != null) {
context.clearMDC();
}
}
}
protected void disconnect(boolean enableReconnect, boolean clean) throws AnzoException {
IOperationContext context = null;
try {
connected = false;
if (clean && combusConnection.isConnected()) {
context = new BaseOperationContext(INotificationRegistrationService.UNREGISTER_SUBSCRIBER, BaseOperationContext.generateOperationId(), null);
anzoClient.clientDatasource.getNotificationRegistrationService().unregisterSubscriber(context, null);
if (transactionListeners.size() > 0) {
combusConnection.unregisterTopicListener(COMBUS.TRANSACTIONS_TOPIC);
}
this.combusConnection.unregisterMessageListener(this);
}
if (!enableReconnect) {
quadStore.clear();
patternToTracker.clear();
}
} finally {
if (context != null) {
context.clearMDC();
}
}
}
public void onMessage(final Message message) {
if (message == null) {
log.error(LogUtils.COMBUS_MARKER, Messages.formatString(ExceptionConstants.COMBUS.ERROR_PROCESSING_MESSGE, "null message"), new AnzoException(ExceptionConstants.COMBUS.JMS_MESSAGE_PARSING));
return;
}
try {
if (log.isTraceEnabled()) {
log.trace(LogUtils.COMBUS_MARKER, MessageUtils.prettyPrint(message, "Notification Recieved: "));
}
String operation = message.getStringProperty(SerializationConstants.operation);
if (operation != null) {
if (operation.equals(SerializationConstants.transactionComplete)) {
handleTransactionMessage(message);
} else if (SerializationConstants.updateResults.equals(operation)) {
handleUpdateMessage(message);
} else if (SerializationConstants.datasetUpdate.equals(operation)) {
handleDatasetUpdateMessage(message);
}
}
} catch (JMSException jmsex) {
log.error(LogUtils.INTERNAL_MARKER, Messages.formatString(ExceptionConstants.COMBUS.ERROR_PROCESSING_MESSGE, "realtime update message"), jmsex);
} catch (AnzoException e) {
log.error(LogUtils.INTERNAL_MARKER, Messages.formatString(ExceptionConstants.COMBUS.ERROR_PROCESSING_MESSGE, "realtime update message"), e);
}
}
// Normal update message. Iterate through those who have permission.
protected void handleUpdateMessage(Message message) throws AnzoException {
try {
if (!message.propertyExists(SerializationConstants.method)) {
throw new AnzoException(ExceptionConstants.COMBUS.JMS_MISSING_MESSAGE_PARAMETER);
}
if (!message.propertyExists(SerializationConstants.subject)) {
throw new AnzoException(ExceptionConstants.COMBUS.JMS_MISSING_MESSAGE_PARAMETER);
}
if (!message.propertyExists(SerializationConstants.predicate)) {
throw new AnzoException(ExceptionConstants.COMBUS.JMS_MISSING_MESSAGE_PARAMETER);
}
if (!message.propertyExists(SerializationConstants.namedGraphUri)) {
throw new AnzoException(ExceptionConstants.COMBUS.JMS_MISSING_MESSAGE_PARAMETER);
}
if (!message.propertyExists(SerializationConstants.object)) {
throw new AnzoException(ExceptionConstants.COMBUS.JMS_MISSING_MESSAGE_PARAMETER);
}
boolean method = message.getBooleanProperty(SerializationConstants.method);
// Extract statement info from message.
String predicateUri = message.getStringProperty(SerializationConstants.predicate);
String namedGraph = message.getStringProperty(SerializationConstants.namedGraphUri);
// Construct statement.
Value object = CommonSerializationUtils.getObjectFromMessage(message);
if (object == null) {
throw new AnzoException(ExceptionConstants.COMBUS.JMS_MISSING_MESSAGE_PARAMETER);
}
Resource subject = CommonSerializationUtils.getSubjectFromMessage(message);
if (subject == null) {
throw new AnzoException(ExceptionConstants.COMBUS.JMS_MISSING_MESSAGE_PARAMETER);
}
URI predicate = Constants.valueFactory.createURI(predicateUri);
URI namedGraphUri = Constants.valueFactory.createURI(namedGraph);
URI graphURI = namedGraphUri;
Statement stmt = Constants.valueFactory.createStatement(subject, predicate, object, graphURI);
notifyTrackers(method, stmt);
} catch (JMSException jmsex) {
throw new AnzoException(ExceptionConstants.COMBUS.JMS_MESSAGE_PARSING, jmsex);
}
}
// Normal update message. Iterate through those who have permission.
protected void handleDatasetUpdateMessage(Message message) throws AnzoException {
try {
if (!message.propertyExists(SerializationConstants.datasetUri)) {
throw new AnzoException(ExceptionConstants.COMBUS.JMS_MISSING_MESSAGE_PARAMETER);
}
if (!message.propertyExists(SerializationConstants.namedGraphUri)) {
throw new AnzoException(ExceptionConstants.COMBUS.JMS_MISSING_MESSAGE_PARAMETER);
}
String namedGraph = message.getStringProperty(SerializationConstants.namedGraphUri);
String datasetUris = message.getStringProperty(SerializationConstants.datasetUri);
Collection<URI> uris = new ArrayList<URI>();
StringTokenizer st = new StringTokenizer(datasetUris, ",");
while (st.hasMoreTokens()) {
uris.add(Constants.valueFactory.createURI(st.nextToken()));
}
notifyTrackers(Constants.valueFactory.createURI(namedGraph), uris);
} catch (JMSException jmsex) {
throw new AnzoException(ExceptionConstants.COMBUS.JMS_MESSAGE_PARSING, jmsex);
}
}
// End of transaction.
protected void handleTransactionMessage(Message message) throws AnzoException {
// Extract method and transactionId.
String transactionContextString = null;
try {
transactionContextString = message.propertyExists(SerializationConstants.transactionContext) ? message.getStringProperty(SerializationConstants.transactionContext) : null;
} catch (JMSException e) {
throw new AnzoException(ExceptionConstants.COMBUS.JMS_MESSAGE_PARSING, e);
}
String updatedNamedGraphs = null;
try {
updatedNamedGraphs = message.propertyExists(SerializationConstants.namedGraphUpdates) ? message.getStringProperty(SerializationConstants.namedGraphUpdates) : null;
} catch (JMSException e) {
throw new AnzoException(ExceptionConstants.COMBUS.JMS_MESSAGE_PARSING, e);
}
long timestamp = -1;
try {
timestamp = Long.valueOf(message.getLongProperty(SerializationConstants.transactionTimestamp));
} catch (JMSException e) {
throw new AnzoException(ExceptionConstants.COMBUS.JMS_MESSAGE_PARSING, e);
}
String transactionUri = null;
try {
transactionUri = message.propertyExists(SerializationConstants.transactionURI) ? message.getStringProperty(SerializationConstants.transactionURI) : null;
} catch (JMSException e) {
throw new AnzoException(ExceptionConstants.COMBUS.JMS_MESSAGE_PARSING, e);
}
URI transactionURI = (transactionUri != null) ? MemURI.create(transactionUri) : null;
transactionLock.writeLock().lock();
try {
Collection<Statement> context = null;
if (transactionContextString != null) {
context = ReadWriteUtils.readStatements(transactionContextString, RDFFormat.JSON);
}
Map<URI, Long> updatedGraphs = null;
if (updatedNamedGraphs != null) {
updatedGraphs = CommonSerializationUtils.readNamedGraphRevisions(updatedNamedGraphs);
}
UpdateTransaction transaction = new UpdateTransaction(transactionURI, timestamp, context, updatedGraphs);
notifyTransactionListeners(transaction);
} finally {
transactionLock.writeLock().unlock();
}
}
}