/******************************************************************************* * Copyright (c) 2004, 2007 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.common/src/com/ibm/adtech/boca/services/impl/JMSNotificationService.java,v $ * Created by: Matthew Roy ( <a href="mailto:mroy@us.ibm.com">mroy@us.ibm.com </a>) * Created on: 5/20/2006 * Revision: $Id: JMSNotificationService.java 178 2007-07-31 14:22:33Z mroy $ * * Contributors: * IBM Corporation - initial API and implementation * Cambridge Semantics Incorporated - Fork to Anzo *******************************************************************************/ package org.openanzo.client; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.MessageListener; 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.ontologies.openanzo.NamedGraph; import org.openanzo.rdf.Resource; import org.openanzo.rdf.Statement; import org.openanzo.rdf.URI; import org.openanzo.rdf.utils.SerializationConstants; import org.openanzo.rdf.utils.SerializationUtils; import org.openanzo.services.INamedGraphUpdate; import org.openanzo.services.IOperationContext; import org.openanzo.services.IUpdateResultListener; import org.openanzo.services.IUpdateTransaction; import org.openanzo.services.IUpdates; import org.openanzo.services.impl.UpdateTransaction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Core class that process updates to and from the server * * @author Matthew Roy ( <a href="mailto:mroy@cambridgesemantics.com">mroy@cambridgesemantics.com </a>) * @author Ben Szekely ( <a href="mailto:ben@cambridgesemantics.com">ben@cambridgesemantics.com </a>) */ class NamedGraphUpdateManager implements MessageListener, IUpdateResultListener { private static final Logger log = LoggerFactory.getLogger(NamedGraphUpdateManager.class); /** Registered listeners for named graph updates */ private final HashSet<URI> namedGraphTopics = new HashSet<URI>(); private NamedGraphUpdateMessageProcessor namedGraphUpdateProcessor; private final AnzoClient anzoClient; /** * Create new JMSNotificationService with configuration properties provided in properties object */ NamedGraphUpdateManager(AnzoClient client) { this.anzoClient = client; } void addNamedGraphUpdateTopic(URI namedGraphUUIDUri) throws AnzoException { if (namedGraphTopics.contains(namedGraphUUIDUri)) { return; } else { anzoClient.clientDatasource.registerTopicListener(this, namedGraphUUIDUri); namedGraphTopics.add(namedGraphUUIDUri); } } void removeNamedGraphUpdateTopic(URI namedGraphUri) throws AnzoException { if (namedGraphTopics.remove(namedGraphUri)) { anzoClient.clientDatasource.unregisterTopicListener(this, namedGraphUri); } } void clear() { if (this.namedGraphUpdateProcessor != null) { this.namedGraphUpdateProcessor.interrupt(); connect(); } } void connect() { this.namedGraphUpdateProcessor = new NamedGraphUpdateMessageProcessor(anzoClient); this.namedGraphUpdateProcessor.start(); } void disconnect() { if (namedGraphUpdateProcessor != null) { namedGraphUpdateProcessor.disconnect(); namedGraphUpdateProcessor = null; } this.namedGraphTopics.clear(); } public void updateComplete(IOperationContext context, IUpdates updates) throws AnzoException { if (namedGraphUpdateProcessor != null) { namedGraphUpdateProcessor.lock.lock(); try { for (IUpdateTransaction transaction : updates.getTransactions()) { if (transaction.getErrors().size() == 0) { boolean found = false; if (namedGraphUpdateProcessor != null && namedGraphUpdateProcessor.updateMessages != null) { synchronized (namedGraphUpdateProcessor.updateMessages) { for (INamedGraphUpdate update : transaction.getNamedGraphUpdates()) { if (namedGraphTopics.contains(update.getUUID())) { namedGraphUpdateProcessor.updateMessages.add(update); found = true; } } } } if (found) { namedGraphUpdateProcessor.newUpdateMessage.signalAll(); } } } } finally { if (namedGraphUpdateProcessor != null) { namedGraphUpdateProcessor.lock.unlock(); } } } } public void onMessage(Message topicMessage) { if (namedGraphUpdateProcessor != null && !namedGraphUpdateProcessor.disconnect && !namedGraphUpdateProcessor.disconnected) { namedGraphUpdateProcessor.lock.lock(); try { String method = topicMessage.getStringProperty(SerializationConstants.type); if (method != null) { if (log.isTraceEnabled()) { log.trace(LogUtils.COMBUS_MARKER, MessageUtils.prettyPrint(topicMessage, "Topic Message Recieved: ")); } if (method.equals(SerializationConstants.namedGraphUpdate)) { synchronized (namedGraphUpdateProcessor.updateMessages) { namedGraphUpdateProcessor.updateMessages.add(MessageUtils.processNamedGraphUpdateMessage(topicMessage)); } namedGraphUpdateProcessor.newUpdateMessage.signalAll(); } } } catch (JMSException jmsex) { log.error(LogUtils.COMBUS_MARKER, Messages.formatString(ExceptionConstants.COMBUS.ERROR_PROCESSING_MESSGE, "namedgraph update"), jmsex); } catch (AnzoException jmsex) { log.error(LogUtils.COMBUS_MARKER, Messages.formatString(ExceptionConstants.COMBUS.ERROR_PROCESSING_MESSGE, "namedgraph update"), jmsex); } finally { if (namedGraphUpdateProcessor != null) { namedGraphUpdateProcessor.lock.unlock(); } } } } private static class NamedGraphUpdates extends UpdateTransaction { long firstMessageTimestamp = System.currentTimeMillis(); HashMap<URI, Long> expectedGraphs = new HashMap<URI, Long>(); NamedGraphUpdates(URI transactionUri, long transactionTimestamp, Collection<Statement> transactionContext, Map<URI, Long> updatedNamedGraphs) { super(transactionUri, transactionTimestamp, transactionContext, updatedNamedGraphs); } } private static class NamedGraphUpdateMessageProcessor extends Thread { // transactionId > NamedGraphUpdates HashMap<URI, NamedGraphUpdates> updates = new HashMap<URI, NamedGraphUpdates>(); // list of processed transactions so we know that if we see a message for transaction that has // already been processed and removed from the updates map, we don't setup an entry for it there. // for now, we keep this list indefinitely. Since it is just a set of URI, this should not be a // concern. HashSet<URI> processedTransactions = new HashSet<URI>(); List<INamedGraphUpdate> updateMessages = new LinkedList<INamedGraphUpdate>(); final Lock lock = new ReentrantLock(); final Condition newUpdateMessage = lock.newCondition(); final Condition finished = lock.newCondition(); long interval = 1500; long transactionTimeout = 2000; final AnzoClient client; boolean disconnect = false; boolean disconnected = false; NamedGraphUpdateMessageProcessor(AnzoClient client) { super("NamedGraphUpdateMessageProcessor:" + client.userDescription); setDaemon(true); this.client = client; } public void disconnect() { disconnect = true; lock.lock(); try { newUpdateMessage.signalAll(); if (!disconnected) { try { finished.await(); } catch (InterruptedException ie) { } } } finally { lock.unlock(); } updateMessages.clear(); updates.clear(); disconnect = false; disconnected = false; } @Override public void run() { try { // the update message processor runs throughout the life of the client while (!disconnect) { try { // every interval period, wakeup to check if we have any transactions that we haven't // received all the named graph updates for, and that we expect we never will lock.lock(); ArrayList<INamedGraphUpdate> _updates = null; try { newUpdateMessage.await(interval, TimeUnit.MILLISECONDS); if (disconnect) { return; } synchronized (updateMessages) { _updates = new ArrayList<INamedGraphUpdate>(updateMessages); updateMessages.removeAll(_updates); } } catch (InterruptedException ie) { return; } finally { lock.unlock(); } if (_updates.size() > 0 || updates.size() > 0) { client.clientLock.lock(); try { processUpdates(_updates); } finally { client.clientLock.unlock(); } } } catch (Exception e) { log.error(LogUtils.INTERNAL_MARKER, Messages.formatString(ExceptionConstants.COMBUS.ERROR_PROCESSING_MESSGE, "namedgraph update"), e); } } } finally { lock.lock(); try { disconnected = true; finished.signal(); } finally { lock.unlock(); } } } private void processUpdates(Collection<INamedGraphUpdate> updateMessages) throws AnzoException { long timestamp = System.currentTimeMillis(); boolean needsReplicate = false; // if we were notified of one or more update messages, process them. // process every update message received so far. if (updateMessages != null) { for (INamedGraphUpdate updateMessage : updateMessages) { needsReplicate |= processNamedGraphUpdateMessage(updateMessage); if (disconnect) return; } } // check if we have been waiting on a particular transaction for more than the alloted transactionTimeout // if so, we know we need to replicate. if (!needsReplicate) { for (URI transactionURI : updates.keySet()) { NamedGraphUpdates namedGraphUpdates = updates.get(transactionURI); if (timestamp - namedGraphUpdates.firstMessageTimestamp > transactionTimeout) { if (log.isDebugEnabled()) { log.debug(LogUtils.INTERNAL_MARKER, Messages.formatString(ExceptionConstants.CLIENT.EXTRA_REPLICATION_NEEDED, SerializationUtils.convertToList(namedGraphUpdates.getNamedGraphs(), SerializationConstants.MIMETYPE_CSV))); } needsReplicate = true; break; } } } // if either we have timed-out transactions, or our replica is out of date, then we must perform a replicate. // Once we have replicated, there is no longer a need to wait for any more named graph updates for transactions // we already know about so we notify the transaction handle and mark the transaction as processed. if (disconnect) return; if (needsReplicate) { HashSet<URI> graphsToReplicate = new HashSet<URI>(); for (URI transactionURI : updates.keySet()) { NamedGraphUpdates namedGraphUpdates = updates.get(transactionURI); graphsToReplicate.addAll(namedGraphUpdates.expectedGraphs.keySet()); } if (log.isDebugEnabled()) log.debug(LogUtils.INTERNAL_MARKER, Messages.formatString(ExceptionConstants.CLIENT.EXTRA_REPLICATION, SerializationUtils.convertToList(graphsToReplicate, SerializationConstants.MIMETYPE_CSV))); if (disconnect) return; client.replicator.replicate(graphsToReplicate); for (URI transactionURI : updates.keySet()) { NamedGraphUpdates namedGraphUpdates = updates.get(transactionURI); client.handleTransaction(new UpdateTransaction(transactionURI, namedGraphUpdates.getTransactionTimestamp(), namedGraphUpdates.getTransactionContext(), namedGraphUpdates.expectedGraphs)); processedTransactions.add(transactionURI); } updates.clear(); } } /** * * @param namedGraphUpdateMessage * @return whether or a not a replication is required after processing this message * @throws AnzoException */ boolean processNamedGraphUpdateMessage(INamedGraphUpdate namedGraphUpdateMessage) throws AnzoException { if (processedTransactions.contains(namedGraphUpdateMessage.getTransaction().getURI())) { return false; } NamedGraphUpdates namedGraphUpdates = updates.get(namedGraphUpdateMessage.getTransaction().getURI()); if (namedGraphUpdates == null) { namedGraphUpdates = new NamedGraphUpdates(namedGraphUpdateMessage.getTransaction().getURI(), transactionTimeout, namedGraphUpdateMessage.getTransaction().getTransactionContext(), namedGraphUpdateMessage.getTransaction().getUpdatedNamedGraphRevisions()); updates.put(namedGraphUpdateMessage.getTransaction().getURI(), namedGraphUpdates); for (Map.Entry<URI, Long> entry : namedGraphUpdateMessage.getTransaction().getUpdatedNamedGraphRevisions().entrySet()) { Collection<Statement> stmts = null; stmts = client.quadStore.find((Resource) null, NamedGraph.uuidProperty, entry.getKey(), (URI) null); if (!stmts.isEmpty()) { Statement stmt = stmts.iterator().next(); URI ngUri = (URI) stmt.getSubject(); namedGraphUpdates.expectedGraphs.put(ngUri, entry.getValue()); } else { log.debug(LogUtils.INTERNAL_MARKER, Messages.formatString(ExceptionConstants.DATASOURCE.NAMEDGRAPH.GRAPH_NOT_VALID, entry.getKey().toString())); } } } Long rev = namedGraphUpdates.expectedGraphs.get(namedGraphUpdateMessage.getNamedGraphURI()); // we received a namedgraph update in a transaction that we were not expecting.... // this should not happen. if (rev == null) { if (log.isDebugEnabled()) { log.debug(LogUtils.INTERNAL_MARKER, Messages.formatString(ExceptionConstants.CLIENT.NOT_EXPECTED, namedGraphUpdateMessage.getNamedGraphURI().toString(), SerializationUtils.convertToList(namedGraphUpdates.expectedGraphs.keySet(), SerializationConstants.MIMETYPE_CSV))); } return true; } if (log.isDebugEnabled()) log.debug(LogUtils.INTERNAL_MARKER, Messages.formatString(ExceptionConstants.CLIENT.EXPECTED, namedGraphUpdateMessage.getTransaction().getURI().toString(), namedGraphUpdateMessage.getNamedGraphURI().toString(), SerializationUtils.convertToList(namedGraphUpdates.expectedGraphs.keySet(), SerializationConstants.MIMETYPE_CSV))); namedGraphUpdates.addNamedGraphUpdate(namedGraphUpdateMessage); if (namedGraphUpdates.getNamedGraphs().containsAll(namedGraphUpdates.expectedGraphs.keySet())) { boolean replicaInSync = true; for (INamedGraphUpdate entry : namedGraphUpdates.getNamedGraphUpdates()) { boolean inSync = client.namedGraphUpdater.handleNamedGraphUpdate(entry); if (!inSync) { if (log.isDebugEnabled()) { log.debug(LogUtils.INTERNAL_MARKER, Messages.formatString(ExceptionConstants.CLIENT.NOT_IN_SYNCH, namedGraphUpdateMessage.getNamedGraphURI().toString())); } replicaInSync = false; } } if (replicaInSync) { client.handleTransaction(namedGraphUpdateMessage.getTransaction()); this.updates.remove(namedGraphUpdateMessage.getTransaction().getURI()); this.processedTransactions.add(namedGraphUpdateMessage.getTransaction().getURI()); return false; } else { return true; } } return false; } } }