/******************************************************************************* * 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.io.StringWriter; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.openanzo.client.Transaction.MapFunction; import org.openanzo.exceptions.AnzoException; import org.openanzo.exceptions.AnzoRuntimeException; import org.openanzo.ontologies.persistence.ClientPersistenceFactory; import org.openanzo.ontologies.persistence.ClientPersistenceRoot; import org.openanzo.ontologies.persistence.PersistedPrecondition; import org.openanzo.ontologies.persistence.PersistedTransaction; import org.openanzo.ontologies.persistence.ReferencedNamedGraph; import org.openanzo.ontologies.persistence.ReferencedQuadStore; import org.openanzo.rdf.Constants; import org.openanzo.rdf.IQuadStore; import org.openanzo.rdf.MemURI; import org.openanzo.rdf.NamedGraph; import org.openanzo.rdf.RDFFormat; import org.openanzo.rdf.Statement; import org.openanzo.rdf.URI; import org.openanzo.rdf.jastor.Thing; import org.openanzo.rdf.utils.ReadWriteUtils; import org.openanzo.rdf.utils.UriGenerator; import org.openanzo.services.IPrecondition; import org.openanzo.services.impl.AskResult; import org.openanzo.services.impl.Precondition; /** * Manages persistence of the AnzoClient's trackers, transactions and replication markers. * * @author Joe Betz * */ public class TransactionPersistence { // root node of persisted client state static final URI persistenceUri = MemURI.create("http://openanzo.org/reserved/localPersistence"); protected ClientPersistenceRoot clientStore; protected IQuadStore quadStore; public TransactionPersistence(IQuadStore quadStore) { this.quadStore = quadStore; this.clientStore = ClientPersistenceFactory.createClientPersistenceRoot(persistenceUri, new NamedGraph(persistenceUri, quadStore)); } void clear() { quadStore.clear(); } /** * Loads all the transactions in the store. */ List<Transaction> loadTransactions() { PersistedTransaction current = clientStore.getCommittedTransactions(); List<Transaction> transactions = new ArrayList<Transaction>(); Map<URI, Transaction> map = new HashMap<URI, Transaction>(); LoadTransactionFunction function = new LoadTransactionFunction(this, map); while (current != null) { walkSavedTransactionTree(function, current); Transaction transaction = map.get(current.resource()); transactions.add(transaction); current = current.getNext(); } return transactions; } /** * Removes all transactions from the store. */ void deleteTransactions() { quadStore.clear(); } /** * Saves the provided transaction to the end of the list of transactions in the store. */ void saveTransaction(Transaction transaction) { Map<Transaction, URI> map = new HashMap<Transaction, URI>(); SaveTransactionFunction mapFunction = new SaveTransactionFunction(this, map); transaction.walkTransactionTree(mapFunction); URI uri = map.get(transaction); if (uri == null) { //uri = UriGenerator.generateTransactionURI(); uri = transaction.transactionUri; } PersistedTransaction current = clientStore.getCommittedTransactions(); if (current == null) { clientStore.setCommittedTransactions(uri); } else { PersistedTransaction previous = null; while (current != null) { previous = current; current = current.getNext(); } if (previous != null) previous.setNext(uri); } } void removeTransaction(Transaction transaction) { removeTransaction(transaction.transactionUri); } private void removeTransaction(URI transactionUri) { PersistedTransaction tran = ClientPersistenceFactory.getPersistedTransaction(transactionUri, new NamedGraph(persistenceUri, quadStore)); PersistedTransaction nextTran = tran.getNext(); Collection<Statement> stmts = quadStore.find(null, PersistedTransaction.nextProperty, transactionUri, persistenceUri); if (!stmts.isEmpty()) { URI prevUri = (URI) stmts.iterator().next().getObject(); PersistedTransaction prevTran = ClientPersistenceFactory.getPersistedTransaction(prevUri, new NamedGraph(persistenceUri, quadStore)); if (nextTran != null) { prevTran.setNext(nextTran); } else { prevTran.setNext((PersistedTransaction) null); } } else { if (clientStore.getCommittedTransactions().resource().equals(transactionUri)) { if (nextTran != null) { clientStore.setCommittedTransactions(nextTran); } else { clientStore.setCommittedTransactions((PersistedTransaction) null); } } } Iterable<PersistedPrecondition> preconditions = tran.getPreconditions(); for (PersistedPrecondition precondition : preconditions) { precondition.removeStatements(); } if (tran.getAdditionsStore() != null) { removeQuadStore(tran.getAdditionsStore(), quadStore); } if (tran.getDeletionsStore() != null) { removeQuadStore(tran.getDeletionsStore(), quadStore); } if (tran.getChildTransaction() != null) { removeTransaction((URI) tran.getChildTransaction().resource()); } if (tran.getNextTransaction() != null) { removeTransaction((URI) tran.getNextTransaction().resource()); } tran.removeStatements(); } private static class SaveTransactionFunction implements MapFunction { Map<Transaction, URI> map; TransactionPersistence store; public SaveTransactionFunction(TransactionPersistence store, Map<Transaction, URI> map) { this.map = map; this.store = store; } public void call(Transaction transaction) { store.saveTransactionNode(transaction, map); } } private static class LoadTransactionFunction implements LoadPersistedMapFunction { Map<URI, Transaction> map; TransactionPersistence store; public LoadTransactionFunction(TransactionPersistence store, Map<URI, Transaction> map) { this.map = map; this.store = store; } public void call(PersistedTransaction transaction) { store.loadTransaction(transaction, map); } } /** * persists the properties of the transaction object, using the provided map to dereference other transaction objects. * */ private URI saveTransactionNode(Transaction transaction, Map<Transaction, URI> map) { URI uri = map.get(transaction); if (uri == null) { //uri = UriGenerator.generateTransactionURI(); uri = transaction.transactionUri; map.put(transaction, uri); } PersistedTransaction current = ClientPersistenceFactory.createPersistedTransaction(uri, new NamedGraph(persistenceUri, quadStore)); for (IPrecondition precondition : transaction.getPreconditions()) { PersistedPrecondition storedPrecondition = current.addPreconditions(); if (precondition.getDefaultGraphUris() != null) { for (URI graphUri : precondition.getDefaultGraphUris()) { storedPrecondition.addPreconditionDefaultUri(graphUri); } } if (precondition.getNamedGraphUris() != null) { for (URI graphUri : precondition.getNamedGraphUris()) { storedPrecondition.addPreconditionNamedGraphUri(graphUri); } } storedPrecondition.setQueryString(precondition.getQuery()); AskResult result = (AskResult) precondition.getResult(); if (result != null) { storedPrecondition.setAskResult(result.getResultValue()); } } if (transaction.previousTransaction != null) { URI previousTransactionUri = map.get(transaction.previousTransaction); if (previousTransactionUri == null) { previousTransactionUri = UriGenerator.generateTransactionURI(); map.put(transaction.previousTransaction, previousTransactionUri); } current.setPreviousTransaction(previousTransactionUri); } if (transaction.nextTransaction != null) { URI nextTransactionUri = map.get(transaction.nextTransaction); if (nextTransactionUri == null) { nextTransactionUri = UriGenerator.generateTransactionURI(); map.put(transaction.nextTransaction, nextTransactionUri); } current.setNextTransaction(nextTransactionUri); } if (transaction.parentTransaction != null) { URI parentTransactionUri = map.get(transaction.parentTransaction); if (parentTransactionUri == null) { parentTransactionUri = UriGenerator.generateTransactionURI(); map.put(transaction.parentTransaction, parentTransactionUri); } current.setParentTransaction(parentTransactionUri); } if (transaction.childTransaction != null) { URI childTransactionUri = map.get(transaction.childTransaction); if (childTransactionUri == null) { childTransactionUri = UriGenerator.generateTransactionURI(); map.put(transaction.childTransaction, childTransactionUri); } current.setChildTransaction(childTransactionUri); } StringWriter writer = new StringWriter(); try { ReadWriteUtils.writeStatements(transaction.getContext().getStatements(), writer, RDFFormat.TRIG, null, false); } catch (AnzoException e) { throw new AnzoRuntimeException(e); } current.setTransactionContext(writer.toString()); current.setTransactionUri(transaction.transactionUri); saveQuadStore(transaction.getAdditions(), current.setAdditionsStore(Constants.valueFactory.createBNode()), quadStore); saveQuadStore(transaction.getDeletions(), current.setDeletionsStore(Constants.valueFactory.createBNode()), quadStore); return uri; } private Transaction loadTransaction(PersistedTransaction current, Map<URI, Transaction> map) { if (map.containsKey(current.resource())) { return map.get(current.resource()); } // load the preconditions Set<IPrecondition> preconditions = new HashSet<IPrecondition>(); Iterable<PersistedPrecondition> currentPreconditions = current.getPreconditions(); for (PersistedPrecondition currentPrecondition : currentPreconditions) { Precondition precondition = new Precondition(); precondition.setResult(currentPrecondition.getAskResult()); precondition.setQuery(currentPrecondition.getQueryString()); Set<URI> defaultUris = new HashSet<URI>(); Iterable<Thing> defaultUri = currentPrecondition.getPreconditionDefaultUri(); for (Thing uri : defaultUri) { defaultUris.add((URI) uri.resource()); } precondition.setDefaultGraphUris(defaultUris); Set<URI> namedGraphUris = new HashSet<URI>(); Iterable<Thing> namedGraphUri = currentPrecondition.getPreconditionNamedGraphUri(); for (Thing uri : namedGraphUri) { namedGraphUris.add((URI) uri.resource()); } precondition.setNamedGraphUris(namedGraphUris); preconditions.add(precondition); } Collection<Statement> adds = loadQuadStore(current.getAdditionsStore(), quadStore); Collection<Statement> deletes = loadQuadStore(current.getDeletionsStore(), quadStore); Transaction transaction = new Transaction(preconditions, (URI) current.getTransactionUri().resource()); transaction.additions.add(adds); transaction.deletions.add(deletes); map.put((URI) current.resource(), transaction); PersistedTransaction previous = current.getPreviousTransaction(); if (previous != null) { Transaction previousTransaction = map.get(previous.resource()); if (previousTransaction == null) { previousTransaction = loadTransaction(previous, map); map.put((URI) previous.resource(), previousTransaction); } transaction.previousTransaction = previousTransaction; } PersistedTransaction next = current.getNextTransaction(); if (next != null) { Transaction nextTransaction = map.get(next.resource()); if (nextTransaction == null) { nextTransaction = loadTransaction(next, map); map.put((URI) next.resource(), nextTransaction); } transaction.nextTransaction = nextTransaction; } PersistedTransaction parent = current.getParentTransaction(); if (parent != null) { Transaction parentTransaction = map.get(parent.resource()); if (parentTransaction == null) { parentTransaction = loadTransaction(parent, map); map.put((URI) parent.resource(), parentTransaction); } transaction.parentTransaction = parentTransaction; } PersistedTransaction child = current.getChildTransaction(); if (child != null) { Transaction childTransaction = map.get(child.resource()); if (childTransaction == null) { childTransaction = loadTransaction(child, map); map.put((URI) child.resource(), childTransaction); } transaction.childTransaction = childTransaction; } String contextString = current.getTransactionContext(); try { Collection<Statement> contextStmts = ReadWriteUtils.readStatements(contextString, RDFFormat.TRIG); for (Statement stmt : contextStmts) { transaction.getContext().add(stmt); } } catch (AnzoException e) { throw new AnzoRuntimeException(e); } return transaction; } /** * HACK: Stores a QuadStore within another QuadStore as a bunch of named graphs. Multiple quad stores may be saved to a single quad store this way even if * they share common named graph uris. */ private static void saveQuadStore(Collection<Statement> statements, ReferencedQuadStore referencedStore, IQuadStore destination) { Map<URI, NamedGraph> graphs = new HashMap<URI, NamedGraph>(); for (Statement stmt : statements) { URI context = stmt.getNamedGraphUri(); NamedGraph graph = graphs.get(context); if (graph == null) { URI graphUri = UriGenerator.generateTransactionURI(); ReferencedNamedGraph referencedGraph = referencedStore.addNamedGraph(graphUri); referencedGraph.setReferenceUri(graphUri); referencedGraph.setGraphUri(context); graph = new NamedGraph(graphUri, destination); graphs.put(context, graph); } graph.add(stmt); } } /** * HACK: Loads a quad store stored within another quad store. See saveQuadStore for details. */ private static Collection<Statement> loadQuadStore(ReferencedQuadStore referencedStore, IQuadStore source) { Iterable<ReferencedNamedGraph> addGraphs = referencedStore.getNamedGraph(); List<Statement> stmts = new ArrayList<Statement>(); for (ReferencedNamedGraph referencedGraph : addGraphs) { URI namedGraphUri = (URI) referencedGraph.getGraphUri().resource(); Collection<Statement> iterator = source.find(null, null, null, (URI) referencedGraph.getReferenceUri().resource()); for (Statement stmt : iterator) { stmts.add(Constants.valueFactory.createStatement(stmt.getSubject(), stmt.getPredicate(), stmt.getObject(), namedGraphUri)); } } return stmts; } private static void removeQuadStore(ReferencedQuadStore referencedStore, IQuadStore source) { Iterable<ReferencedNamedGraph> addGraphs = referencedStore.getNamedGraph(); for (ReferencedNamedGraph referencedGraph : addGraphs) { URI namedGraphUri = (URI) referencedGraph.getGraphUri().resource(); source.remove(null, null, null, namedGraphUri); } referencedStore.removeStatements(); } private interface LoadPersistedMapFunction { void call(PersistedTransaction transaction); } private static void walkSavedTransactionTree(LoadPersistedMapFunction mapFunction, PersistedTransaction root) { mapFunction.call(root); if (root.getChildTransaction() != null) { walkSavedTransactionTree(mapFunction, root.getChildTransaction()); } if (root.getNextTransaction() != null) { walkSavedTransactionTree(mapFunction, root.getNextTransaction()); } } }