/******************************************************************************* * 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 * * File: $Source$ * Created by: Ben Szekely ( <a href="mailto:ben@cambridgesemantics.com">ben@cambridgesemantics.com </a>) * Created on: Dec 6, 2007 * Revision: $Id$ * * Contributors: * Cambridge Semantics Incorporated - initial API and implementation *******************************************************************************/ package org.openanzo.client; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.openanzo.ontologies.openanzo.Dataset; import org.openanzo.rdf.Constants; import org.openanzo.rdf.DatasetBase; import org.openanzo.rdf.INamedGraph; import org.openanzo.rdf.IStatementListener; import org.openanzo.rdf.NamedGraph; import org.openanzo.rdf.URI; import org.openanzo.rdf.utils.UriGenerator; /** * * Stores reference counts for AnzoGraph instances. * * This class is thread safe. * * @author Joe Betz <jpbetz@cambridgesemantics.com> * @author Ben Szekely (<a href="mailto:ben@cambridgesemantics.com">ben@cambridgesemantics.com</a>) * */ public class GraphTable { ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); public static final URI REPLICA_GRAPH_TABLE_DATASET_URI = Constants.valueFactory.createURI("http://openanzo.org/datasets/replicaGraphTable"); public enum ReleaseResult { OPEN, CLOSE_INSTANCE, CLOSE_ALL } public enum Type { REPLICA, SERVER } Type type; /** * Holds a graph and it's reference count. * * @param */ protected static class TableEntry { ClientGraph graph; long count; public TableEntry(ClientGraph graph) { this.graph = graph; count = 1; } } protected final HashMap<URI, TableEntry> table = new HashMap<URI, TableEntry>(); private final ThreadLocal<HashMap<URI, TableEntry>> isolatedTable = new ThreadLocal<HashMap<URI, TableEntry>>(); private final ThreadLocal<GraphTableDataset> dataset = new ThreadLocal<GraphTableDataset>(); private final List<HashMap<URI, TableEntry>> isolatedTables = new ArrayList<HashMap<URI, TableEntry>>(); protected final HashMap<URI, Set<IStatementListener<INamedGraph>>> listeners = new HashMap<URI, Set<IStatementListener<INamedGraph>>>(); protected final HashMap<URI, Set<IStatementListener<INamedGraph>>> metaListeners = new HashMap<URI, Set<IStatementListener<INamedGraph>>>(); private final AnzoClient anzoClient; private final INamedGraph persistedDatasetGraph; public GraphTable(AnzoClient anzoClient) { this(anzoClient, null); } /** * This version of the constructor is used for persistence * * @param f * @param datasetGraph */ public GraphTable(AnzoClient anzoClient, INamedGraph persistedDatasetGraph) { this.anzoClient = anzoClient; this.persistedDatasetGraph = persistedDatasetGraph; // the resulting set of calls that go and create all the replica graphs // in the case of persistence is a bit complicated, but it works. // The GraphTableDataset constructor uses the persistedDatasetGraph // to load all the replica graphs dataset.set(new GraphTableDataset()); } void close() { // if we are in persistence, // we could have loaded up replica // graphs on startup that never got // touched. If so, we close them // note that such graphs never can // exist in the isolated table because // the loading of such graphs does not // occur inside a transaction Set<ClientGraph> graphsToClose = new HashSet<ClientGraph>(); if (persistedDatasetGraph != null) { for (URI uri : table.keySet()) { TableEntry entry = table.get(uri); if (entry.count == 0) { // this close call, in a roundabout way, // will come back and remove the graph // from the table and modify the dataset graphsToClose.add(entry.graph); //entry.graph.close(); } } } for (ClientGraph graph : graphsToClose) { graph.close(); } table.clear(); if (dataset.get() != null) { dataset.get().close(); } } public void clear() { table.clear(); // do we need to do this for all the thread-locals ? dataset.remove(); for (HashMap<URI, TableEntry> t : isolatedTables) { t.clear(); } listeners.clear(); metaListeners.clear(); } /** * Sets or replaces the entry in the graph table for the provided URI and graph. The reference count will be 1 after this call returns. * * @param namedGraphUri * The URI of the graph. * @param graph * The graph. */ public void put(URI namedGraphUri, ClientGraph graph) { lock.writeLock().lock(); try { if (anzoClient.inTransaction() && isolatedTable.get() == null) { isolatedTable.set(new HashMap<URI, TableEntry>()); isolatedTables.add(isolatedTable.get()); } TableEntry tableEntry = new TableEntry(graph); tableEntry.count = 1; if (anzoClient.inTransaction()) { isolatedTable.get().put(namedGraphUri, tableEntry); } else { table.put(namedGraphUri, tableEntry); } if (dataset.get() == null) { getDataset(); } dataset.get().addGraph(graph); } finally { lock.writeLock().unlock(); } } /** * Increments the reference count for the graph matching the URI if it is already in the table and returns the graph, otherwise returns null. * * @param namedGraphUri * A named graph URI. * @return A graph matching the URI */ public ClientGraph get(URI namedGraphUri) { ClientGraph result = null; lock.writeLock().lock(); try { TableEntry tableEntry = null; if (isolatedTable.get() != null) { tableEntry = isolatedTable.get().get(namedGraphUri); } if (tableEntry == null) { tableEntry = table.get(namedGraphUri); } if (tableEntry != null) { tableEntry.count++; result = tableEntry.graph; } } finally { lock.writeLock().unlock(); } return result; } /** * Returns whether or not this URI is contained in the graph table..includes isolated tables as well. * * @param namedGraphUri * @return true if contains the graph based on uri */ boolean contains(URI namedGraphUri) { lock.readLock().lock(); try { TableEntry tableEntry = null; if (isolatedTable.get() != null) { tableEntry = isolatedTable.get().get(namedGraphUri); } if (tableEntry == null) { tableEntry = table.get(namedGraphUri); } if (tableEntry != null) { return true; } else { return false; } } finally { lock.readLock().unlock(); } } /** * Decrements the reference count for the graph with the provided URI and removes it from the table if the reference count reaches zero. * * @param namedGraphUri * The graph URI to release. * @return True if the reference count reached zero or was already zero. */ ReleaseResult release(URI namedGraphUri) { return release(namedGraphUri, true); } /** * Decrements the reference count for the graph with the provided URI and removes it from the table if the reference count reaches zero. * * @param namedGraphUri * The graph URI to release. * @param removeIfZeroReference * Remove te graph if this is the last reference * @return True if the reference count reached zero or was already zero. */ ReleaseResult release(URI namedGraphUri, boolean removeIfZeroReference) { ReleaseResult result = ReleaseResult.OPEN; lock.writeLock().lock(); try { TableEntry tableEntry = null; if (isolatedTable.get() != null) { tableEntry = isolatedTable.get().get(namedGraphUri); } boolean isolated = false; if (tableEntry == null) { tableEntry = table.get(namedGraphUri); } else { isolated = true; } if (tableEntry == null) { if (graphExistsInSomeIsolatedTable(namedGraphUri)) { result = ReleaseResult.CLOSE_INSTANCE; } else { result = ReleaseResult.CLOSE_ALL; } } else { tableEntry.count--; if (tableEntry.count <= 0 && removeIfZeroReference) { if (isolated) { isolatedTable.get().remove(namedGraphUri); } else { table.remove(namedGraphUri); } if (dataset.get() == null) { getDataset(); } dataset.get().removeGraph(namedGraphUri); if (graphExistsInSomeIsolatedTable(namedGraphUri)) { result = ReleaseResult.CLOSE_INSTANCE; } else { listeners.remove(namedGraphUri); metaListeners.remove(namedGraphUri); result = ReleaseResult.CLOSE_ALL; } } } } finally { lock.writeLock().unlock(); } return result; } private boolean graphExistsInSomeIsolatedTable(URI uri) { for (HashMap<URI, TableEntry> table : isolatedTables) { if (table.containsKey(uri)) { return true; } } return table.containsKey(uri); } GraphTableDataset getDataset() { if (dataset.get() == null) { GraphTableDataset ds = new GraphTableDataset(); for (ClientGraph graph : listAll()) { ds.addGraph(graph); } dataset.set(ds); } return dataset.get(); } /** * Returns a set of all the graphs in the table. * * @return Set containing all the graphs in the table. */ Set<ClientGraph> listAll() { Set<ClientGraph> results = new HashSet<ClientGraph>(); lock.readLock().lock(); try { if (isolatedTable.get() != null) { for (TableEntry entry : isolatedTable.get().values()) { results.add(entry.graph); } } for (TableEntry entry : table.values()) { results.add(entry.graph); } } finally { lock.readLock().unlock(); } return results; } void mergeIsolatedGraphs() { lock.writeLock().lock(); try { if (isolatedTable.get() != null) { Set<URI> toRemove = new HashSet<URI>(); for (URI uri : isolatedTable.get().keySet()) { TableEntry isolatedEntry = isolatedTable.get().get(uri); TableEntry entry = table.get(uri); if (entry == null) { table.put(uri, isolatedEntry); toRemove.add(uri); } } for (URI uri : toRemove) { isolatedTable.get().remove(uri); } if (isolatedTable.get().isEmpty()) { isolatedTables.remove(isolatedTable.get()); isolatedTable.remove(); } } } finally { lock.writeLock().unlock(); } } class GraphTableDataset extends DatasetBase { private ReentrantLock lock = new ReentrantLock(); GraphTableDataset() { super(REPLICA_GRAPH_TABLE_DATASET_URI); } @Override protected ReentrantLock getLock() { return lock; } @Override protected INamedGraph createNamedGraph(URI namedGraphUri) { if (persistedDatasetGraph != null) { if (isolatedTable.get() != null && isolatedTable.get().get(namedGraphUri) != null) { return isolatedTable.get().get(namedGraphUri).graph; } else if (table.get(namedGraphUri) != null) { return table.get(namedGraphUri).graph; } URI metadataGraphUri = UriGenerator.generateMetadataGraphUri(namedGraphUri); NamedGraph metadataGraph = new MetadataGraph(metadataGraphUri, anzoClient.replicaGraphTransactionProxy, GraphTable.this); metadataGraph.setNotifyAddRemove(false); ClientGraph clientGraph = new ClientGraph(namedGraphUri, anzoClient.replicaGraphTransactionProxy, metadataGraph, anzoClient, GraphTable.this); // any initializers from persisted graphs will have already been run. TableEntry entry = new TableEntry(clientGraph); entry.count = 0; entry.graph = clientGraph; table.put(namedGraphUri, entry); return clientGraph; } throw new UnsupportedOperationException("Cannot create graphs inside GraphTableDataset"); } @Override protected INamedGraph createDatasetGraph() { if (persistedDatasetGraph != null) { return persistedDatasetGraph; } else { return new NamedGraph(datasetUri); } } private void addGraph(ClientGraph graph) { synchronized (graphs) { graphs.put(graph.getNamedGraphUri(), graph); } addDefaultGraph(graph.getNamedGraphUri()); addNamedGraph(graph.getNamedGraphUri()); } private void removeGraph(URI namedGraphUri) { synchronized (graphs) { graphs.remove(namedGraphUri); } defaultGraphUris.remove(namedGraphUri); if (datasetGraph != null) { datasetGraph.remove(datasetUri, Dataset.defaultGraphProperty, namedGraphUri); } namedGraphUris.remove(namedGraphUri); if (datasetGraph != null) { datasetGraph.remove(datasetUri, Dataset.namedGraphProperty, namedGraphUri); datasetGraph.remove(datasetUri, Dataset.defaultNamedGraphProperty, namedGraphUri); } } @Override public INamedGraph addNamedGraph(URI namedGraphUri) { if (!graphs.containsKey(namedGraphUri)) { throw new UnsupportedOperationException("Cannot create graphs inside GraphTableDataset"); } return super.addNamedGraph(namedGraphUri); } @Override public INamedGraph addDefaultGraph(URI namedGraphUri) { if (!graphs.containsKey(namedGraphUri)) { throw new UnsupportedOperationException("Cannot create graphs inside GraphTableDataset"); } return super.addDefaultGraph(namedGraphUri); } @Override public void setDefaultGraphs(Set<URI> namedGraphUris) { throw new UnsupportedOperationException(); } @Override public void setNamedGraphs(Set<URI> namedGraphUris) { throw new UnsupportedOperationException(); } @Override public void removeDefaultGraph(URI namedGraphUri) { throw new UnsupportedOperationException(); } @Override public void removeNamedGraph(URI namedGraphUri) { throw new UnsupportedOperationException(); } @Override public void close() { synchronized (graphs) { graphs.clear(); } defaultGraphUris.clear(); namedGraphUris.clear(); dataset.remove(); } } }