/******************************************************************************* * 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.FileNotFoundException; import java.io.IOException; import java.io.Reader; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Dictionary; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.concurrent.locks.ReentrantLock; import javax.jms.JMSException; import org.openanzo.client.GraphTable.Type; import org.openanzo.combus.IJmsProvider; import org.openanzo.datasource.IAuthorizationService; import org.openanzo.datasource.IDatasource; import org.openanzo.datasource.IModelService; import org.openanzo.datasource.IUpdateService; import org.openanzo.exceptions.AnzoException; import org.openanzo.exceptions.AnzoRuntimeException; import org.openanzo.exceptions.CompoundAnzoException; import org.openanzo.exceptions.ExceptionConstants; import org.openanzo.exceptions.LogUtils; import org.openanzo.exceptions.Messages; import org.openanzo.glitter.query.QueryResults; import org.openanzo.ontologies.openanzo.AnzoFactory; import org.openanzo.rdf.AnzoGraph; import org.openanzo.rdf.Constants; import org.openanzo.rdf.Dataset; import org.openanzo.rdf.IAnzoGraph; import org.openanzo.rdf.IDataset; import org.openanzo.rdf.INamedGraph; import org.openanzo.rdf.IQuadStore; import org.openanzo.rdf.Literal; import org.openanzo.rdf.MemQuadStore; import org.openanzo.rdf.NamedGraph; 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.GRAPHS; import org.openanzo.rdf.Constants.OPTIONS; import org.openanzo.rdf.utils.IStatementsHandler; import org.openanzo.rdf.utils.ReadWriteUtils; import org.openanzo.rdf.utils.SerializationConstants; import org.openanzo.rdf.utils.UriGenerator; import org.openanzo.rdf.vocabulary.RDF; import org.openanzo.services.AnzoPrincipal; import org.openanzo.services.IAuthenticationService; import org.openanzo.services.IExecutionService; import org.openanzo.services.INotificationConnectionListener; import org.openanzo.services.IOperationContext; import org.openanzo.services.IPrecondition; import org.openanzo.services.IUpdateTransaction; import org.openanzo.services.IUpdates; import org.openanzo.services.IUpdatesProvider; import org.openanzo.services.Privilege; import org.openanzo.services.UpdateServerException; import org.openanzo.services.impl.BaseOperationContext; import org.openanzo.services.impl.Precondition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MDC; /** * The AnzoClient provides remote access to an Anzo server. * * This is the central management point for a single connection to an Anzo server. The AnzoClient provides the methods to get replica and server graphs. The * graphs are bound to the AnzoClient that creates them, and all graph created with a single AnzoClient share a common TransactionQueue, NotificationService and * persistence provider. * * For more details see: http://www.openanzo.org/projects/openanzo/wiki/AnzoJavaProgrammingModel * * @author Joe Betz * @author Ben Szekely ( <a href="mailto:ben@cambridgesemantics.com">ben@cambridgesemantics.com </a>) */ public class AnzoClient { private static final Logger log = LoggerFactory.getLogger(AnzoClient.class); /** INamedGraphInitializer for creating Revisioned NamedGraphs */ public static final INamedGraphInitializer REVISIONED_NAMED_GRAPH = new RevisionedNamedGraphInitializer(true); /** INamedGraphInitializer for creating NonRevisioned NamedGraphs */ public static final INamedGraphInitializer NON_REVISIONED_NAMED_GRAPH = new RevisionedNamedGraphInitializer(false); /** INamedGraphInitializer that adds precondition that the NamedGraph must already exist on the server */ public static final INamedGraphInitializer GRAPH_MUST_EXIST = new GraphExistenceInitializer(true); /** INamedGraphInitializer that adds precondition that the NamedGraph must not already exist on the server */ public static final INamedGraphInitializer GRAPH_MUST_NOT_EXIST = new GraphExistenceInitializer(false); private static final INamedGraphInitializer STATEMENT_STREAM = new StatementStreamInitializer(); protected final AnzoClientDatasource clientDatasource; protected final INotificationConnectionListener reconnectionListener; protected final GraphTable replicaGraphTable; protected final GraphTable serverGraphTable; protected final TransactionProxy replicaGraphTransactionProxy; protected final TransactionProxy serverGraphTransactionProxy; protected final TransactionQueue transactionQueue; protected final Replicator replicator; protected final ReplicaUpdater replicaUpdater; protected final ReplicaUpdater namedGraphUpdater; protected final IQuadStore quadStore; protected final IQuadStoreComponent quadStoreComponent; protected final IQuadStore serverQuadStore; protected final RealtimeUpdateManager realtimeUpdates; final NamedGraphUpdateManager namedGraphUpdateManager; final ReentrantLock clientLock = new ReentrantLock(); private final HashSet<IAnzoClientConnectionListener> connectionListeners = new HashSet<IAnzoClientConnectionListener>(); private final HashSet<ITransactionListener> transactionListeners = new HashSet<ITransactionListener>(); private boolean closed = false; private boolean connected = false; private boolean updateRepositoryOnCommit = false; protected boolean jmsEnabled = true; private final ArrayList<INamedGraphInitializer> defaultGraphInitializers = new ArrayList<INamedGraphInitializer>(); protected final HashMap<URI, StatementChannel> statementChannels = new HashMap<URI, StatementChannel>(); // runas user state /** Service container principal */ protected AnzoPrincipal servicePrincipal = null; /** ThreadLocal property for the userid to run operations as for this thread */ protected final ThreadLocal<AnzoPrincipal> runAsPrincipal = new ThreadLocal<AnzoPrincipal>(); /** ThreadLocal property for the user to run operations as for this thread */ protected final ThreadLocal<String> runAsUser = new ThreadLocal<String>(); protected String userDescription = null; protected Collection<AnzoClientDataset> datasets = new ArrayList<AnzoClientDataset>(); /** * Creates a new AnzoClient. * * Loads the given properties and initializes the various services required to communicate with the anzo server. * * @param configuration * Java properties file. These properties are usually created via calls to * {@link AnzoClientConfigurationFactory#createJMSConfiguration(String, String, String, Integer,Boolean)} * @throws AnzoException */ public AnzoClient(Properties configuration) throws AnzoException { this(new AnzoClientDatasource(configuration)); } /** * Creates a new AnzoClient using the provided implementation of the core services. This method is usually only used on the server. * * @param configuration * These properties are usually passed in from the OSGI host * @param datasource * Instance of the {@link IDatasource} on which this client operates * @param authenticationService * Instance of the {@link IAuthenticationService} on which this client operates * @param executionService * Instance of the {@link IExecutionService} on which this client operates * @param updatesProvider * Instance of the {@link IUpdatesProvider} on which this client operates * @param jmsProvider * Instance of the {@link IJmsProvider} that this client uses to get its jms connections * @param enableJMS * true if this client needs to use jms. Note:the jmsProvider must be provided if this is true * @throws AnzoException */ @SuppressWarnings("unchecked") public AnzoClient(Dictionary configuration, IDatasource datasource, IAuthenticationService authenticationService, IExecutionService executionService, IUpdatesProvider updatesProvider, IJmsProvider jmsProvider, boolean enableJMS) throws AnzoException { this(new AnzoClientDatasource(configuration, datasource, authenticationService, executionService, updatesProvider, jmsProvider, enableJMS)); } private AnzoClient(AnzoClientDatasource datasource) throws AnzoException { this.clientDatasource = datasource; this.clientDatasource.start(); quadStoreComponent = clientDatasource.getQuadStore(); this.quadStore = this.quadStoreComponent.getQuadStore(); this.transactionQueue = quadStoreComponent.getTransactionQueue(); this.serverQuadStore = new ServerQuadStore(this); this.replicaGraphTransactionProxy = new TransactionProxy(quadStore, transactionQueue, this); this.serverGraphTransactionProxy = new TransactionProxy(serverQuadStore, transactionQueue, this); // the replica graph table may be persisted, whereas // the server graph table need not. replicaGraphTable = this.quadStoreComponent.getReplicaGraphTable(this); replicaGraphTable.type = Type.REPLICA; serverGraphTable = new GraphTable(this); serverGraphTable.type = Type.SERVER; this.realtimeUpdates = (clientDatasource.getCombusConnection() != null) ? new RealtimeUpdateManager(this) : null; this.namedGraphUpdateManager = new NamedGraphUpdateManager(this); this.replicaUpdater = new ReplicaUpdater(quadStore, this, ReplicaUpdater.Type.REPLICATION); this.namedGraphUpdater = new ReplicaUpdater(quadStore, this, ReplicaUpdater.Type.NAMED_GRAPH_UPDATE); this.reconnectionListener = new JMSConnectionListener(); this.replicator = new Replicator(this); defaultGraphInitializers.add(new BaseNamedGraphInitializer()); defaultGraphInitializers.add(new RevisionedNamedGraphInitializer(true)); } /** * @return the AnzoClientDatasource for this AnzoClient */ public AnzoClientDatasource getAnzoClientDatasource() { return clientDatasource; } /** * Set whether client will call {@link #updateRepository()} when {@link #commit()} is called * * @param updateRepositoryOnCommit * If true, {@link #updateRepository()} is automatically called. If false, the use must call {@link #updateRepository()} directly. */ public void setUpdateRepositoryOnCommit(boolean updateRepositoryOnCommit) { this.updateRepositoryOnCommit = updateRepositoryOnCommit; } /** * Connects to the anzo server, establishing event subscriptions if notification is enabled. * * @throws AnzoException */ public void connect() throws AnzoException { if (closed) { throw new AnzoException(ExceptionConstants.CLIENT.CLIENT_CLOSED); } try { clientLock.lockInterruptibly(); if (connected) { return; } jmsEnabled = clientDatasource.getCombusConnection() != null; connected = true; if (jmsEnabled) { clientDatasource.getCombusConnection().connect(); } namedGraphUpdateManager.connect(); if (jmsEnabled) { connectToNotification(); } // setup any statement channels for (URI uri : statementChannels.keySet()) { StatementChannel channel = statementChannels.get(uri); channel.connect(); } // connect all unconnected graphs for (URI uri : replicaGraphTable.table.keySet()) { ClientGraph graph = replicaGraphTable.table.get(uri).graph; if (!graph.connected) { getReplicaGraph(uri, graph.namedGraphInitializers); replicaGraphTable.release(uri, false); } } // we must connect server graphs as well. for (URI uri : serverGraphTable.table.keySet()) { ClientGraph graph = serverGraphTable.table.get(uri).graph; if (!graph.connected) { getServerGraph(uri, graph.namedGraphInitializers); serverGraphTable.release(uri, false); } } for (IAnzoClientConnectionListener listener : connectionListeners) { listener.clientConnected(); } } catch (InterruptedException e) { throw new AnzoException(ExceptionConstants.CLIENT.CLIENT_LOCK_ERROR, e); } finally { clientLock.unlock(); } } /** * Disconnects that Anzo Client. The client may later be reconnected if further operations are required. * * @throws AnzoException */ public void disconnect() throws AnzoException { try { clientLock.lock(); boolean wasConnected = connected; connected = false; if (clientDatasource != null) { clientDatasource.disconnect(namedGraphUpdateManager); } namedGraphUpdateManager.disconnect(); if (wasConnected) { disconnectFromNotification(true, true); } for (URI uri : replicaGraphTable.table.keySet()) { ClientGraph graph = replicaGraphTable.table.get(uri).graph; graph.connected = false; } // we must connect server graphs as well. for (URI uri : serverGraphTable.table.keySet()) { ClientGraph graph = serverGraphTable.table.get(uri).graph; graph.connected = false; } for (IAnzoClientConnectionListener listener : connectionListeners) { listener.clientDisconnected(); } } finally { clientLock.unlock(); } } /** * @return true if the client is currently connected */ public boolean isConnected() { return connected; } /** * @return The RealtimeUpdateManager for this client * @throws AnzoException */ public RealtimeUpdateManager getRealtimeUpdates() throws AnzoException { if (!jmsEnabled) { throw new AnzoException(ExceptionConstants.CLIENT.JMS_NOT_ENABLED); } if (!this.realtimeUpdates.connected) { this.realtimeUpdates.connect(); } return this.realtimeUpdates; } private void connectToNotification() throws AnzoException { try { if (!jmsEnabled) { return; } if (!clientDatasource.getCombusConnection().isConnected()) { clientDatasource.getCombusConnection().connect(); } clientDatasource.getCombusConnection().registerConnectionListener(reconnectionListener); clientDatasource.getCombusConnection().startMessageExecutor(); // check if we need to reconnect the realtime updates if (!this.realtimeUpdates.patternToTracker.isEmpty()) { this.realtimeUpdates.connect(); } } catch (JMSException jmsex) { throw new AnzoException(ExceptionConstants.COMBUS.SERVER_CONNECT_EXCEPTION, jmsex); } } private void disconnectFromNotification(boolean enableReconnect, boolean cleanDisconnect) throws AnzoException { ArrayList<AnzoException> exceptions = new ArrayList<AnzoException>(); if (!jmsEnabled) { return; } if (!enableReconnect) { clientDatasource.getCombusConnection().unregisterConnectionListener(reconnectionListener); } if (this.realtimeUpdates.connected) { try { realtimeUpdates.disconnect(enableReconnect, cleanDisconnect); } catch (AnzoException ae) { exceptions.add(ae); } } // this disconnection might be // redundant because it should all // get closed when the container is stopped clientDatasource.getCombusConnection().stopMessageExecutor(); try { clientDatasource.getCombusConnection().disconnect(cleanDisconnect); } catch (AnzoException ae) { exceptions.add(ae); } if (exceptions.size() > 0) { throw new CompoundAnzoException(exceptions); } } /** * Register a {@link IAnzoClientConnectionListener} that is notified when the clients connection state changes. * * @param listener */ public void registerConnectionListener(IAnzoClientConnectionListener listener) { connectionListeners.add(listener); } /** * Unregister a {@link IAnzoClientConnectionListener} . * * @param listener */ public void unregisterConnectionListener(IAnzoClientConnectionListener listener) { connectionListeners.remove(listener); } /** * Register a {@link ITransactionListener} that is notified when client receives transaction events. * * @param listener */ public void registerTransactionListener(ITransactionListener listener) { transactionListeners.add(listener); } /** * Unregister a {@link ITransactionListener} * * @param listener */ public void unregisterTransactionListener(ITransactionListener listener) { transactionListeners.remove(listener); } /** * Gets a replica graph with the given URI and registers the graph for replication, and optionally for notification. To unregister the graph call * graph.close(). * * A replica graph keeps a local copy of the graph and performs all read and write operations directly against this local copy. The replicate method must be * called to synchronize replica graphs with the anzo server. * * Since replica graphs keep a local copy of the graph, they may be used offline, meaning they do not require the anzo client be connected to the anzo * server. * * For details on how replication works, see the replicate method. * * To perform reads and writes directly against the server see the getServerGraph method. * * @param uri * URI of the NamedGraph to get/create * @param namedGraphInitializers * Set of initializers used to create graph if it does not exist. Will use {@link #REVISIONED_NAMED_GRAPH} if none are set. * @return A replica graph for the given URI. * @throws AnzoException */ public ClientGraph getReplicaGraph(URI uri, INamedGraphInitializer... namedGraphInitializers) throws AnzoException { return getReplicaGraphs(Collections.singleton(uri), namedGraphInitializers).get(uri); } /** * Gets a replica graph with the given URI and registers the graph for replication, and optionally for notification. To unregister the graph call * graph.close(). * * A replica graph keeps a local copy of the graph and performs all read and write operations directly against this local copy. The replicate method must be * called to synchronize replica graphs with the anzo server. * * Since replica graphs keep a local copy of the graph, they may be used offline, meaning they do not require the anzo client be connected to the anzo * server. * * For details on how replication works, see the replicate method. * * To perform reads and writes directly against the server see the getServerGraph method. * * @param uris * URI that specifies the name of the graphs. * @param namedGraphInitializers * Set of initializers used to create graph if it does not exist. Will use {@link #REVISIONED_NAMED_GRAPH} if none are set. * @return Map of URIs to their replica graphs. * @throws AnzoException */ public Map<URI, ClientGraph> getReplicaGraphs(Set<URI> uris, INamedGraphInitializer... namedGraphInitializers) throws AnzoException { Map<URI, ClientGraph> replicaGraphs = new HashMap<URI, ClientGraph>(); Set<URI> graphsToGet = new HashSet<URI>(); for (URI uri : uris) { ClientGraph clientGraph = replicaGraphTable.get(uri); if (clientGraph != null && (clientGraph.connected || !this.connected)) { boolean canReadGraph = true; if (runAsUser.get() != null) { canReadGraph = canReadNamedGraph(uri); } if (canReadGraph) { replicaGraphs.put(uri, clientGraph); } } else { if (clientGraph != null) { // we have to decrement the count because it will be // increased again below. We cannot just do a release // because that will remove the graph from the table if // the count is just 1 (and should be 0 as in the persistence case) replicaGraphTable.release(uri, false); } graphsToGet.add(uri); } } if (!connected) { for (URI uri : graphsToGet) { URI metadataGraphUri = UriGenerator.generateMetadataGraphUri(uri); NamedGraph metadataGraph = new MetadataGraph(metadataGraphUri, replicaGraphTransactionProxy, replicaGraphTable); metadataGraph.setNotifyAddRemove(false); ClientGraph clientGraph = new ClientGraph(uri, replicaGraphTransactionProxy, metadataGraph, this, replicaGraphTable, namedGraphInitializers); replicaGraphTable.put(uri, clientGraph); if (!inTransaction()) { begin(); try { initializeNamedGraph(clientGraph, true, namedGraphInitializers); commit(); } catch (AnzoException e) { abort(); throw new AnzoException(ExceptionConstants.CLIENT.FAILED_INITIALIZE_GRAPH, e, uri.toString()); } } else { initializeNamedGraph(clientGraph, true, namedGraphInitializers); } replicaGraphs.put(uri, clientGraph); } return Collections.unmodifiableMap(replicaGraphs); } replicator.replicateToQuadStore(graphsToGet); for (URI uri : graphsToGet) { boolean createNew = false; if (this.quadStore.find(null, null, null, UriGenerator.generateMetadataGraphUri(uri)).isEmpty()) { createNew = true; } ClientGraph graph = getGraph(uri, createNew, replicaGraphTable, replicaGraphTransactionProxy, namedGraphInitializers); replicaGraphs.put(uri, graph); } return replicaGraphs; } /** * Gets a server graph with the given URI and optionally registers it for notification. To unregister the graph call graph.close(). * * A server graph performs all read and write operations directly against the anzo server and requires a connection to the anzo server to operate. * * To perform reads and writes when not connected to the anzo server, see the getReplicaGraph method. * * @param uri * URI that specifies the name of the graph. * @param namedGraphInitializers * Set of initializers used to create graph if it does not exist. Will use {@link #REVISIONED_NAMED_GRAPH} if none are set. * @return A server graph for the given URI. * @throws AnzoException */ public ClientGraph getServerGraph(URI uri, INamedGraphInitializer... namedGraphInitializers) throws AnzoException { if (!connected) { throw new AnzoException(ExceptionConstants.CLIENT.CLIENT_NOT_CONNECTED); } ClientGraph serverGraph = serverGraphTable.get(uri); if (serverGraph != null && serverGraph.connected) { if (runAsUser.get() != null) { if (canReadNamedGraph(uri)) { return serverGraph; } else { return null; } } return serverGraph; } if (serverGraph != null) { // decrement the ref count on the server graph // because it will be increased againg below in // getGraph() serverGraphTable.release(uri, false); } boolean createNew = !clientDatasource.getModelService().containsNamedGraph(createContext("containsNamedGraph"), uri); if (!createNew) { Collection<Statement> stmts = null; Collection<Statement> serverStmts = null; stmts = quadStore.find(uri, org.openanzo.ontologies.openanzo.NamedGraph.uuidProperty, (URI) null, (URI) null); if (stmts.isEmpty()) { URI metadataGraphUri = UriGenerator.generateMetadataGraphUri(uri); serverStmts = serverQuadStore.find(uri, org.openanzo.ontologies.openanzo.NamedGraph.uuidProperty, (URI) null, metadataGraphUri); if (!serverStmts.isEmpty()) { Statement uuidStmt = serverStmts.iterator().next(); quadStore.add(uuidStmt); } } } ClientGraph graph = getGraph(uri, createNew, serverGraphTable, serverGraphTransactionProxy, namedGraphInitializers); return graph; } /** * Pushes all committed transactions to the server. * * @throws AnzoException */ public void updateRepository() throws AnzoException { if (!connected) { throw new AnzoException(ExceptionConstants.CLIENT.CLIENT_NOT_CONNECTED); } replicator.update(); } /** * Replicate changes from the server to the client. This is usually not needed, since replica graphs are kept up-to-date via real time messaging. * * @throws AnzoException */ public void replicate() throws AnzoException { if (!connected) { throw new AnzoException(ExceptionConstants.CLIENT.CLIENT_NOT_CONNECTED); } replicator.replicate(); } /** * Begins a new transaction. If begin is called from within a transaction, a nested transaction is begun. Transactions are isolated per thread. * * @param preconditions * Optional array of preconditions that must pass for the transaction not to fail. */ public void begin(Set<IPrecondition> preconditions) { transactionQueue.begin(preconditions); } /** * Begins a new transaction. If begin is called from within a transaction, a nested transaction is begun. */ public void begin() { transactionQueue.begin(); } /** * Commit the current transaction. If {@link #setUpdateRepositoryOnCommit(boolean)} has not been called with true, then transaction is kept on client untill * {@link #updateRepository()} is called, otherwise transaction is sent to server during this call. */ public void commit() { try { // transaction complete should also return false // if the transaction was empty, and thus not added // to the queue. boolean transactionComplete = transactionQueue.commit(); if (transactionComplete) { this.replicaGraphTable.mergeIsolatedGraphs(); this.serverGraphTable.mergeIsolatedGraphs(); for (AnzoClientDataset dataset : datasets) { dataset.commit(); } } if (transactionComplete && updateRepositoryOnCommit) { updateRepository(); } } catch (AnzoException e) { throw new AnzoRuntimeException(e); } } /** * Abort the current transaction. */ public void abort() { transactionQueue.abort(); for (AnzoClientDataset dataset : datasets) { dataset.abort(); } } /** * Get the {@link INamedGraph} that holds the current threads transaction context * * @return the {@link INamedGraph} that holds the current threads transaction context */ public INamedGraph getTransactionContext() { return transactionQueue.getTransactionContext(); } /** * Checks if current thread is in a transaction. * * @return True if current thread is in a transaction, false otherwise. */ public boolean inTransaction() { return transactionQueue.inTransaction(); } /** * If reset is enabled on the anzo server, removes all existing RDF from the repository and replaces it with the given statements. * * NOTE: This call resets the anzo server, completely removing all data stored on the server and replacing it with the provided statements. This is intended * to be used only for testing, and production anzo servers should be configured with reset disabled. * * @param statements * Array of statements that are added to the repository after it is cleared. * @param checks * A set of checks that the server blocks on until they are all true before this method returns * @throws AnzoException */ public void reset(Collection<Statement> statements, Collection<Statement> checks) throws AnzoException { if (!connected) { throw new AnzoException(ExceptionConstants.CLIENT.CLIENT_NOT_CONNECTED); } try { clientLock.lockInterruptibly(); clientDatasource.getResetService().reset(createContext("reset"), statements, checks); clear(); } catch (InterruptedException e) { throw new AnzoException(ExceptionConstants.CLIENT.CLIENT_LOCK_ERROR, e); } finally { clientLock.unlock(); } } /** * Clears the local replicated data, all graphs, and transactions * * @throws AnzoException */ public void clear() throws AnzoException { try { namedGraphUpdateManager.clear(); clientLock.lockInterruptibly(); quadStore.clear(); quadStoreComponent.reset(); transactionQueue.clear(); replicaGraphTable.clear(); serverGraphTable.clear(); } catch (InterruptedException e) { throw new AnzoException(ExceptionConstants.CLIENT.CLIENT_LOCK_ERROR, e); } finally { clientLock.unlock(); } } /** * Runs a SPARQL query directly against the server. * * @param defaultNamedGraphs * List of named graph URIs that identify the graphs that will be merged to form the default graph in the query's RDF Dataset * @param namedGraphs * List of named graph URIs that identify the named graph components of the query's RDF Dataset * @param namedDatasets * List of presisted dataset URIs that identify set of datasets whose contents will contribute to the defaultNamedGraphs and namedGraphs. * @param query * The SPARQL query that is to be executed. * @param baseUri * The base URI against which relative URI references in the query are resolved * @return The SPARQL query results. * @throws AnzoException */ public QueryResults serverQuery(Set<URI> defaultNamedGraphs, Set<URI> namedGraphs, Set<URI> namedDatasets, String query, URI baseUri) throws AnzoException { Map<String, Object> options = new HashMap<String, Object>(); options.put(OPTIONS.PRIORITY, 4); return serverQuery(defaultNamedGraphs, namedGraphs, namedDatasets, query, baseUri, options); } /** * @param defaultNamedGraphs * List of named graph URIs that identify the graphs that will be merged to form the default graph in the query's RDF Dataset * @param namedGraphs * List of named graph URIs that identify the named graph components of the query's RDF Dataset * @param namedDatasets * List of presisted dataset URIs that identify set of datasets whose contents will contribute to the defaultNamedGraphs and namedGraphs. * @param query * The SPARQL query that is to be executed. * @param baseUri * The base URI against which relative URI references in the query are resolved * @param options * Pass a set of custom options to this call. see {@link OPTIONS#PRIORITY},{@link Constants.COMBUS#TIMEOUT}, {@link OPTIONS#DATASOURCE} * @return The SPARQL query results. * @throws AnzoException */ public QueryResults serverQuery(Set<URI> defaultNamedGraphs, Set<URI> namedGraphs, Set<URI> namedDatasets, String query, URI baseUri, Map<String, Object> options) throws AnzoException { if (!connected) { throw new AnzoException(ExceptionConstants.CLIENT.CLIENT_NOT_CONNECTED); } IOperationContext context = createContext("executeQuery"); if (options != null) { for (String prop : options.keySet()) { context.setAttribute(prop, options.get(prop)); } } return clientDatasource.getQueryService().query(context, defaultNamedGraphs, namedGraphs, namedDatasets, null, query, baseUri); } /** * Runs a SPARQL query directly against the server. * * @param defaultNamedGraphs * List of named graph URIs that identify the graphs that will be merged to form the default graph in the query's RDF Dataset * @param namedGraphs * List of named graph URIs that identify the named graph components of the query's RDF Dataset * @param namedDatasets * List of presisted dataset URIs that identify set of datasets whose contents will contribute to the defaultNamedGraphs and namedGraphs. * @param query * The SPARQL query that is to be executed. * @return The SPARQL query results. * @throws AnzoException */ public QueryResults serverQuery(Set<URI> defaultNamedGraphs, Set<URI> namedGraphs, Set<URI> namedDatasets, String query) throws AnzoException { return serverQuery(defaultNamedGraphs, namedGraphs, namedDatasets, query, null); } /** * Runs a SPARQL query against this anzo client's replica graphs. This method does not connect to the anzo server and may be called offline. * * @param defaultNamedGraphs * List of named graph URIs that identify the graphs that will be merged to form the default graph in the query's RDF Dataset * @param namedGraphs * List of named graph URIs that identify the named graph components of the query's RDF Dataset * @param namedDatasets * List of presisted dataset URIs that identify set of datasets whose contents will contribute to the defaultNamedGraphs and namedGraphs. * @param query * SPARQL query string * @param baseUri * Base URI against which relative URI references in the SPARQL query are resolved * @return The SPARQL query results. * @throws AnzoException */ public QueryResults replicaQuery(Set<URI> defaultNamedGraphs, Set<URI> namedGraphs, Set<URI> namedDatasets, String query, URI baseUri) throws AnzoException { if (namedDatasets != null && namedDatasets.size() > 0) throw new UnsupportedOperationException("Named dataset queries against replica graphs"); return replicaGraphTransactionProxy.executeQuery(defaultNamedGraphs, namedGraphs, namedDatasets, query, baseUri); } /** * Return an collection of statements that match the pattern of subj,prop,obj, namedGraphUri in the local replica graphs * * @param subj * Subject resource to match, or wildcard if null * @param prop * Predicate uri to match, or wildcard if null * @param obj * Object value to match, or wildcard if null * @param namedGraphUri * Named graph values to match, or wildcard if null * @return an collection of all statements that match the pattern of subj,prop,obj,namedGraphUri */ public Collection<Statement> replicaFind(Resource subj, URI prop, Value obj, URI... namedGraphUri) throws AnzoException { if (runAsUser.get() != null) { Collection<URI> graphs = new ArrayList<URI>(); if (namedGraphUri != null && namedGraphUri.length > 0) { for (URI ng : namedGraphUri) { if (canReadNamedGraph(ng)) { graphs.add(ng); } } namedGraphUri = graphs.toArray(new URI[0]); } } Collection<Statement> stmts = replicaGraphTransactionProxy.find(subj, prop, obj, namedGraphUri); if (runAsUser.get() != null) { if (namedGraphUri == null || namedGraphUri.length == 0) { Collection<Statement> filtered = new HashSet<Statement>(); HashSet<URI> allowed = new HashSet<URI>(); HashSet<URI> notAllowed = new HashSet<URI>(); for (Statement stmt : stmts) { if (allowed.contains(stmt.getNamedGraphUri())) { filtered.add(stmt); } else if (notAllowed.contains(stmt.getNamedGraphUri())) { continue; } else { if (canReadNamedGraph(stmt.getNamedGraphUri())) { allowed.add(stmt.getNamedGraphUri()); filtered.add(stmt); } else { notAllowed.add(stmt.getNamedGraphUri()); } } } return filtered; } } return stmts; } /** * Return collection of statements that match the pattern of subj,prop,obj, namedGraphUri on the server * * @param subj * Subject resource to match, or wildcard if null * @param prop * Predicate uri to match, or wildcard if null * @param obj * Object value to match, or wildcard if null * @param namedGraphUri * Named graph values to match, or wildcard if null * @return collection of statements that match the pattern of subj,prop,obj,namedGraphUri * @throws AnzoException */ public Collection<Statement> serverFind(Resource subj, URI prop, Value obj, URI... namedGraphUri) throws AnzoException { if (!connected) { throw new AnzoException(ExceptionConstants.CLIENT.CLIENT_NOT_CONNECTED); } return serverGraphTransactionProxy.find(subj, prop, obj, namedGraphUri); } /** * Gets a read-only copy of a named graph as it existed when at the provided revision. * * @param namedGraphUri * The URI of the named graph to get. * @param revision * The revision number, must be greater than zero and less than or equal to the current named graph revision. * @return A read-only copy of the named graph. * @throws AnzoException */ public IAnzoGraph getNamedGraphRevision(URI namedGraphUri, long revision) throws AnzoException { if (!connected) { throw new AnzoException(ExceptionConstants.CLIENT.CLIENT_NOT_CONNECTED); } return clientDatasource.getModelService().getNamedGraphRevision(createContext(IModelService.GET_NAMED_GRAPH_REVISION), namedGraphUri, revision); } /** * Gets a read-only copy of the named graph as it now exists. * * @param namedGraphUri * The URI of the named graph to get. * @return A read-only copy of the named graph. * @throws AnzoException */ public IAnzoGraph getCurrentNamedGraphRevision(URI namedGraphUri) throws AnzoException { return getNamedGraphRevision(namedGraphUri, -1); } /** * Closes this anzo client. Once called, the client cannot be reconnected */ public void close() { close(true); } /** * Closes this anzo client. Once called, the client cannot be reconnected * * @param clean * If true, try to cleanly close jms, otherwise assume jms is not valid * */ public void close(boolean clean) { try { namedGraphUpdateManager.disconnect(); clientLock.lock(); if (connected) { try { disconnectFromNotification(false, clean); } catch (AnzoException e) { log.error(LogUtils.COMBUS_MARKER, Messages.formatString(ExceptionConstants.COMBUS.ERROR_DISCONNECTING_NOTIFICATIONS), e); } } replicaGraphTable.close(); serverGraphTable.close(); datasets.clear(); connected = false; closed = true; } finally { try { clientDatasource.stop(clean); } catch (AnzoException be) { throw new AnzoRuntimeException(be); } finally { clientLock.unlock(); } } } private ClientGraph getGraph(URI uri, boolean createNew, GraphTable graphTable, IQuadStore store, INamedGraphInitializer... namedGraphInitalizers) throws AnzoException { try { clientLock.lockInterruptibly(); ClientGraph clientGraph = graphTable.get(uri); if (clientGraph == null || !clientGraph.connected) { URI metadataGraphUri = UriGenerator.generateMetadataGraphUri(uri); NamedGraph metadataGraph; if (clientGraph == null) { metadataGraph = new MetadataGraph(metadataGraphUri, store, graphTable); metadataGraph.setNotifyAddRemove(false); clientGraph = new ClientGraph(uri, store, metadataGraph, this, graphTable, namedGraphInitalizers); graphTable.put(uri, clientGraph); } if (!createNew) { Collection<Statement> stmts = null; stmts = quadStore.find(uri, org.openanzo.ontologies.openanzo.NamedGraph.uuidProperty, (URI) null, metadataGraphUri); if (!stmts.isEmpty()) { Statement uuidStmt = stmts.iterator().next(); URI uuid = (URI) uuidStmt.getObject(); namedGraphUpdateManager.addNamedGraphUpdateTopic(uuid); } } if (!createNew) { Collection<Statement> stmts = null; stmts = store.find(uri, org.openanzo.ontologies.openanzo.NamedGraph.revisionProperty, null, metadataGraphUri); if (!stmts.isEmpty()) { Statement revStmt = stmts.iterator().next(); Literal rev = (Literal) revStmt.getObject(); try { clientGraph.setRevision(Long.parseLong(rev.getLabel())); } catch (NumberFormatException nfe) { if (log.isDebugEnabled()) { log.debug(LogUtils.INTERNAL_MARKER, Messages.formatString(ExceptionConstants.CORE.NFE, rev.getLabel()), nfe); } } } } if (!inTransaction()) { begin(); try { if (createNew) { transactionQueue.getOrCreateIsolatedTransaction().currentTransaction.getNamedGraphsToSubscribe().put(uri, metadataGraphUri); if (graphTable == serverGraphTable) { transactionQueue.getOrCreateIsolatedTransaction().currentTransaction.getServerUUIDStoFetch().add(uri); } } initializeNamedGraph(clientGraph, createNew, namedGraphInitalizers); commit(); } catch (AnzoException e) { abort(); throw new AnzoException(ExceptionConstants.CLIENT.FAILED_INITIALIZE_GRAPH, e, uri.toString()); } } else { if (createNew) { transactionQueue.getOrCreateIsolatedTransaction().currentTransaction.getNamedGraphsToSubscribe().put(uri, metadataGraphUri); if (graphTable == serverGraphTable) { transactionQueue.getOrCreateIsolatedTransaction().currentTransaction.getServerUUIDStoFetch().add(uri); } } initializeNamedGraph(clientGraph, createNew, namedGraphInitalizers); } clientGraph.connected = true; } return clientGraph; } catch (InterruptedException e) { throw new AnzoException(ExceptionConstants.CLIENT.CLIENT_LOCK_ERROR, e); } finally { clientLock.unlock(); } } private void initializeNamedGraph(IAnzoGraph namedGraph, boolean createNew, INamedGraphInitializer... namedGraphInitializers) throws AnzoException { for (INamedGraphInitializer initializer : defaultGraphInitializers) { initializer.initializeNamedGraph(namedGraph, createNew); // we will always be in a transactions so this should never be null; Collection<IPrecondition> preconditions = initializer.getPreconditions(); if (preconditions != null && !preconditions.isEmpty()) { transactionQueue.isolatedTransactions.get().currentTransaction.preconditions.addAll(preconditions); } } for (INamedGraphInitializer initializer : namedGraphInitializers) { initializer.initializeNamedGraph(namedGraph, createNew); Collection<IPrecondition> preconditions = initializer.getPreconditions(); if (preconditions != null && !preconditions.isEmpty()) { transactionQueue.isolatedTransactions.get().currentTransaction.preconditions.addAll(preconditions); } } } /** * Create, or retrieve if already exists, a dataset of replica graphs based on the given default and named graphs. * * @param persisted * If true, this dataset's structure is persisted in the server and thus can be used for running queries. * @param datasetUri * URI of the dataset * @param defaultGraphUris * List of named graph URIs that identify the graphs that will be merged to form the default graph for this dataset * @param namedGraphUris * List of named graph URIs that identify the named graph components for this dataset * @param namedGraphInitializers * Set of initializers used to create graph if it does not exist. Will use {@link #REVISIONED_NAMED_GRAPH} if none are set. * @return the dataset */ public IDataset createReplicaDataset(boolean persisted, URI datasetUri, Set<URI> defaultGraphUris, Set<URI> namedGraphUris, INamedGraphInitializer... namedGraphInitializers) { return new AnzoClientDataset(this, datasetUri, AnzoClientDataset.DatasetType.REPLICA, persisted, defaultGraphUris, namedGraphUris, namedGraphInitializers); } /** * Create, or retrieve if already exists, a dataset of server graphs based on the given default and named graphs. * * @param persisted * If true, this dataset's structure is persisted in the server and thus can be used for running queries. * @param datasetUri * URI of the dataset * @param defaultGraphUris * List of named graph URIs that identify the graphs that will be merged to form the default graph for this dataset * @param namedGraphUris * List of named graph URIs that identify the named graph components for this dataset * @param namedGraphInitializers * Set of initializers used to create graph if it does not exist. Will use {@link #REVISIONED_NAMED_GRAPH} if none are set. * @return the dataset */ public IDataset createServerDataset(boolean persisted, URI datasetUri, Set<URI> defaultGraphUris, Set<URI> namedGraphUris, INamedGraphInitializer... namedGraphInitializers) { return new AnzoClientDataset(this, datasetUri, AnzoClientDataset.DatasetType.SERVER, persisted, defaultGraphUris, namedGraphUris, namedGraphInitializers); } /** * Get dataset that represents the set of all open replica graphs. If a service user has been set this call is not permitted. * * @return dataset that represents the set of all open replica graphs */ public IDataset getAllReplicaGraphsDataset() { if (runAsUser.get() != null) { throw new AnzoRuntimeException(ExceptionConstants.CLIENT.OP_NOT_ALLOWED_WITH_SERVICE_USER, "getAllReplicaGraphsDataset"); } return replicaGraphTable.getDataset(); } /** * Get dataset that represents the set of all open replica graphs. If a service user has been set this call is not permitted. * * @return dataset that represents the set of all open server graphs */ public IDataset getAllServerGraphsDataset() { if (runAsUser.get() != null) { throw new AnzoRuntimeException(ExceptionConstants.CLIENT.OP_NOT_ALLOWED_WITH_SERVICE_USER, "getAllServerGraphsDataset"); } return serverGraphTable.getDataset(); } /** * Get the number of currently queued transactions which have not been sent to the server * * @return the number of currently queued transactions which have not been sent to the server */ public long getQueuedTransactionCount() { return transactionQueue.committedTransactions.size(); } /** * Drop all currently queued transactions */ public void dropQueuedTransactions() { transactionQueue.clear(); } /** * Get a {@link IStatementChannel} for the given URI * * @param uri * URI of statement channel * @param initializers * Set of initializers used to create the statement channel's configuration graph if it does not exist. Will use {@link #REVISIONED_NAMED_GRAPH} * if none are set. * @return a {@link IStatementChannel} for the given URI * @throws AnzoException */ public IStatementChannel getStatementChannel(URI uri, INamedGraphInitializer... initializers) throws AnzoException { INamedGraphInitializer[] inits = new INamedGraphInitializer[(initializers != null) ? initializers.length + 1 : 1]; if (initializers != null) System.arraycopy(initializers, 0, inits, 0, initializers.length); inits[inits.length - 1] = STATEMENT_STREAM; AnzoGraph graph = getReplicaGraph(uri, inits); IStatementChannel stream = createStatementChannel(uri, graph); return stream; } /** * Import the set of statements into the server. No events/notification are sent for imported statements. * * @param statements * Set of statements to import into server * @param templateStatements * Template for imported graphs metadata, ie acls * @throws AnzoException */ public void importStatements(Collection<Statement> statements, Collection<Statement> templateStatements) throws AnzoException { IUpdates updates = clientDatasource.getUpdateService().importStatements(createContext(IUpdateService.IMPORT_STATEMENTS), statements, templateStatements); List<IUpdateTransaction> results = updates.getTransactions(); boolean hadErrors = false; @SuppressWarnings("unchecked") ArrayList<AnzoException>[] errors = new ArrayList[results.size()]; int i = 0; for (IUpdateTransaction transaction : results) { if (transaction.getErrors().size() > 0) { hadErrors = true; errors[i] = new ArrayList<AnzoException>(); for (AnzoException err : transaction.getErrors()) { errors[i].add(err); } i++; } } if (hadErrors) { throw new UpdateServerException(updates.getTransactions().toArray(new IUpdateTransaction[0]), errors); } } /** * Import the set of statements into the server. No events/notification are sent for imported statements. * * @param statements * Set of statements to import into server * @param initializers * Set of initializers used to create the imported graphs if it does not exist. Will use {@link #REVISIONED_NAMED_GRAPH} if none are set. * @throws AnzoException */ public void importStatements(Collection<Statement> statements, INamedGraphInitializer... initializers) throws AnzoException { MemQuadStore store = new MemQuadStore(); AnzoGraph graphTemplate = new AnzoGraph(GRAPHS.DEFAULT_GRAPH_TEMPLATE, new NamedGraph(GRAPHS.DEFAULT_METADATA_GRAPH_TEMPLATE, store), store); for (INamedGraphInitializer initializer : initializers) { initializer.initializeNamedGraph(graphTemplate, true); } IUpdates updates = clientDatasource.getUpdateService().importStatements(createContext(IUpdateService.IMPORT_STATEMENTS), statements, store.getStatements()); List<IUpdateTransaction> results = updates.getTransactions(); boolean hadErrors = false; @SuppressWarnings("unchecked") ArrayList<AnzoException>[] errors = new ArrayList[results.size()]; int i = 0; for (IUpdateTransaction transaction : results) { if (transaction.getErrors().size() > 0) { hadErrors = true; errors[i] = new ArrayList<AnzoException>(); for (AnzoException err : transaction.getErrors()) { errors[i].add(err); } i++; } } if (hadErrors) { throw new UpdateServerException(updates.getTransactions().toArray(new IUpdateTransaction[0]), errors); } } /** * Import the set of statements into the server. No events/notification are sent for imported statements. * * @param fileName * Name of file containing statements * @param baseUri * Base URI of uris in the file * @param defaultNamedGraph * Default graph for statements that do not have a namedGraph specified in file * @param initializers * Set of initializers used to create the imported graphs if it does not exist. Will use {@link #REVISIONED_NAMED_GRAPH} if none are set. * @param callbackHandler * Callback handler to show verbose status of import * @throws AnzoException */ public void importStatements(String fileName, String baseUri, URI defaultNamedGraph, int batchSize, final IStatementsHandler callbackHandler, INamedGraphInitializer... initializers) throws AnzoException { try { Reader reader = ReadWriteUtils.createSmartFileReader(fileName); importStatements(reader, RDFFormat.forFileName(fileName), baseUri, defaultNamedGraph, batchSize, callbackHandler, initializers); reader.close(); } catch (FileNotFoundException fnfe) { throw new AnzoException(ExceptionConstants.IO.RDF_PARSER_ERROR, fnfe); } catch (IOException fnfe) { throw new AnzoException(ExceptionConstants.IO.RDF_PARSER_ERROR, fnfe); } } /** * Import the set of statements into the server. No events/notification are sent for imported statements. * * @param reader * reader containing data * @param format * format of data within reader. @see {@link RDFFormat} * @param baseUri * Base URI of uris in the file * @param defaultNamedGraph * Default graph for statements that do not have a namedGraph specified in file * @param callbackHandler * Callback handler to show verbose status of import * @param initializers * Set of initializers used to create the imported graphs if it does not exist. Will use {@link #REVISIONED_NAMED_GRAPH} if none are set. * @throws AnzoException */ public void importStatements(Reader reader, RDFFormat format, String baseUri, URI defaultNamedGraph, int batchSize, final IStatementsHandler callbackHandler, INamedGraphInitializer... initializers) throws AnzoException { final MemQuadStore store = new MemQuadStore(); AnzoGraph graphTemplate = new AnzoGraph(GRAPHS.DEFAULT_GRAPH_TEMPLATE, new NamedGraph(GRAPHS.DEFAULT_METADATA_GRAPH_TEMPLATE, store), store); for (INamedGraphInitializer initializer : initializers) { initializer.initializeNamedGraph(graphTemplate, true); } long start = System.currentTimeMillis(); int totalSize = 0; if (defaultNamedGraph == null && !format.supportsNamedGraphs()) { totalSize = ReadWriteUtils.batchLoadStatementsWithSubjectToNamedGraph(reader, format, baseUri, batchSize, new IStatementsHandler() { public void handleStatements(Collection<Statement> statements) throws AnzoException { IUpdates updates = clientDatasource.getUpdateService().importStatements(createContext(IUpdateService.IMPORT_STATEMENTS), statements, store.getStatements()); List<IUpdateTransaction> results = updates.getTransactions(); @SuppressWarnings("unchecked") ArrayList<AnzoException>[] errors = new ArrayList[results.size()]; int i = 0; boolean hadErrors = false; for (IUpdateTransaction transaction : results) { if (transaction.getErrors().size() > 0) { hadErrors = true; errors[i] = new ArrayList<AnzoException>(); for (AnzoException err : transaction.getErrors()) { errors[i].add(err); } i++; } } if (hadErrors) { throw new UpdateServerException(updates.getTransactions().toArray(new IUpdateTransaction[0]), errors); } else { if (callbackHandler != null) callbackHandler.handleStatements(statements); } } }); } else { totalSize = ReadWriteUtils.batchLoadStatements(reader, format, baseUri, defaultNamedGraph, batchSize, new IStatementsHandler() { public void handleStatements(Collection<Statement> statements) throws AnzoException { IUpdates updates = clientDatasource.getUpdateService().importStatements(createContext(IUpdateService.IMPORT_STATEMENTS), statements, store.getStatements()); List<IUpdateTransaction> results = updates.getTransactions(); @SuppressWarnings("unchecked") ArrayList<AnzoException>[] errors = new ArrayList[results.size()]; int i = 0; boolean hadErrors = false; for (IUpdateTransaction transaction : results) { if (transaction.getErrors().size() > 0) { hadErrors = true; errors[i] = new ArrayList<AnzoException>(); for (AnzoException err : transaction.getErrors()) { errors[i].add(err); } i++; } } if (hadErrors) { throw new UpdateServerException(updates.getTransactions().toArray(new IUpdateTransaction[0]), errors); } else { if (callbackHandler != null) callbackHandler.handleStatements(statements); } } }); } long total = System.currentTimeMillis() - start; if (log.isInfoEnabled()) { log.info(LogUtils.INTERNAL_MARKER, "Imported " + totalSize + " in " + (total / 1000) + " seconds"); } } /** * Import the set of statements into the server. No events/notification are sent for imported statements. * * @param reader * reader containing data * @param format * format of data within reader. @see {@link RDFFormat} * @param baseUri * Base URI of uris in the file * @param defaultNamedGraph * Default graph for statements that do not have a namedGraph specified in file * @param templateStatements * Template for imported graphs metadata, ie acls * @param callbackHandler * Callback handler to show verbose status of import * @throws AnzoException */ public void importStatements(Reader reader, RDFFormat format, String baseUri, URI defaultNamedGraph, int batchSize, final Collection<Statement> templateStatements, final IStatementsHandler callbackHandler) throws AnzoException { long start = System.currentTimeMillis(); int totalSize = ReadWriteUtils.batchLoadStatements(reader, format, baseUri, defaultNamedGraph, batchSize, new IStatementsHandler() { public void handleStatements(Collection<Statement> statements) throws AnzoException { IUpdates updates = clientDatasource.getUpdateService().importStatements(createContext(IUpdateService.IMPORT_STATEMENTS), statements, templateStatements); List<IUpdateTransaction> results = updates.getTransactions(); @SuppressWarnings("unchecked") ArrayList<AnzoException>[] errors = new ArrayList[results.size()]; int i = 0; boolean hadErrors = false; for (IUpdateTransaction transaction : results) { if (transaction.getErrors().size() > 0) { hadErrors = true; errors[i] = new ArrayList<AnzoException>(); for (AnzoException err : transaction.getErrors()) { errors[i].add(err); } i++; } } if (hadErrors) { throw new UpdateServerException(updates.getTransactions().toArray(new IUpdateTransaction[0]), errors); } else { if (callbackHandler != null) callbackHandler.handleStatements(statements); } } }); long total = System.currentTimeMillis() - start; if (log.isInfoEnabled()) { log.info(LogUtils.INTERNAL_MARKER, "Imported " + totalSize + " in " + (total / 1000) + " seconds"); } } private IStatementChannel createStatementChannel(URI uri, AnzoGraph graph) throws AnzoException { if (!jmsEnabled) { throw new AnzoException(ExceptionConstants.CLIENT.JMS_NOT_ENABLED); } StatementChannel channel = statementChannels.get(uri); if (channel == null) { channel = new StatementChannel(clientDatasource.getCombusConnection(), uri, graph, this); statementChannels.put(uri, channel); } return channel; } void closeChannel(IStatementChannel channel) throws AnzoException { statementChannels.remove(channel.getURI()); } /** * Returns true if the NamedGraph is stored on the server * * @param namedGraphUri * URI of NamedGraph to check for on server * @return True if the namedGraph stored on the server * @throws AnzoException */ public boolean namedGraphExists(URI namedGraphUri) throws AnzoException { if (!connected) { throw new AnzoException(ExceptionConstants.CLIENT.CLIENT_NOT_CONNECTED); } if (namedGraphUri == null) { return false; } try { return clientDatasource.getModelService().containsNamedGraph(createContext(IModelService.CONTAINS_NAMED_GRAPH), namedGraphUri); } catch (AnzoException be) { if (be.getErrorCode() == ExceptionConstants.DATASOURCE.NAMEDGRAPH.NOT_FOUND) { return false; } else { throw be; } } } /** * Retrieve the set URIs for the graphs stored on the server, for which the current user has read permission. * * @return set of URIs for the graphs stored on the server, for which the current user has read permission. * @throws AnzoException */ public Set<URI> getNamedGraphs() throws AnzoException { return clientDatasource.getModelService().getStoredNamedGraphs(createContext(IModelService.GET_STORED_NAMED_GRAPHS)); } /** * Exectute a semantic service on the server * * @param serviceUri * URI of the service to call * @param statements * Input statements to the service * @return results from the service * @throws AnzoException */ public Collection<Statement> executeService(URI serviceUri, Collection<Statement> statements) throws AnzoException { IOperationContext context = createContext(IExecutionService.EXECUTE_SERVICE); return clientDatasource.getExecutionService().executeService(context, statements, serviceUri); } protected void handleTransaction(IUpdateTransaction transaction) throws AnzoException { replicator.transactionComplete(transaction.getURI()); notifyTransactionListeners(transaction); } protected void notifyTransactionListeners(IUpdateTransaction transaction) { if (userDescription != null) { MDC.put(SerializationConstants.userDescription, userDescription); } try { if (transactionListeners.size() > 0) { IDataset dataset = new Dataset(); if (transaction.getTransactionContext() != null) { Collection<Statement> stmts = transaction.getTransactionContext(); for (Statement stmt : stmts) { URI namedGraphUri = stmt.getNamedGraphUri(); if (!dataset.containsNamedGraph(namedGraphUri)) { dataset.addNamedGraph(namedGraphUri); } dataset.add(stmt); } } Set<URI> ngUpates = convertUUIDSToNamedGraphURIs(transaction.getUpdatedNamedGraphRevisions().keySet()); for (ITransactionListener listener : transactionListeners) { listener.transactionComplete(transaction.getURI(), transaction.getTransactionTimestamp(), ngUpates, dataset); } } } finally { if (userDescription != null) { MDC.remove(SerializationConstants.userDescription); } } } protected void notifyTransactionListners(URI transactionUri, long timestamp, Set<URI> namedGraphs, Collection<Statement> context) { if (userDescription != null) { MDC.put(SerializationConstants.userDescription, userDescription); } try { if (transactionListeners.size() > 0) { IDataset dataset = new Dataset(); if (context != null) { for (Statement stmt : context) { URI namedGraphUri = stmt.getNamedGraphUri(); if (!dataset.containsNamedGraph(namedGraphUri)) { dataset.addNamedGraph(namedGraphUri); } dataset.add(stmt); } } for (ITransactionListener listener : transactionListeners) { listener.transactionComplete(transactionUri, timestamp, namedGraphs, dataset); } } } finally { if (userDescription != null) { MDC.remove(SerializationConstants.userDescription); } } } protected void notifyTransactionListners(URI transactionUri, Set<URI> namedGraphs, Collection<Statement> context, List<AnzoException> errors) { if (userDescription != null) { MDC.put(SerializationConstants.userDescription, userDescription); } try { if (transactionListeners.size() > 0) { IDataset dataset = new Dataset(); if (context != null) { for (Statement stmt : context) { URI namedGraphUri = stmt.getNamedGraphUri(); if (!dataset.containsNamedGraph(namedGraphUri)) { dataset.addNamedGraph(namedGraphUri); } dataset.add(stmt); } } for (ITransactionListener listener : transactionListeners) { listener.transactionFailed(transactionUri, namedGraphs, dataset, errors); } } } finally { if (userDescription != null) { MDC.remove(SerializationConstants.userDescription); } } } protected Set<URI> convertUUIDSToNamedGraphURIs(Set<URI> uuids) { Set<URI> uris = new HashSet<URI>(); for (URI uuid : uuids) { Iterator<Statement> stmts = null; stmts = quadStore.find(null, org.openanzo.ontologies.openanzo.NamedGraph.uuidProperty, uuid, (URI) null).iterator(); if (stmts.hasNext()) { Statement uuidStmt = stmts.next(); URI namedGraphUri = (URI) uuidStmt.getSubject(); uris.add(namedGraphUri); } } return uris; } /** * Set the userId for the current thread which this service will use to authenticate. The Anzo Client will protect replica data from being read by * non-authorized service users, but will not be able to prevent services users from listenting to realtime updates (for example) that have already been * registered by an authorized user. In general, events are not protected in this manner. * * Multi-user uses of this client (in web-apps for example) should be sure that updates create in the transaction queue or current transaction have been * committed and sent to the server via update repository before either handing control to a new service user, or reverting control back to the primary * user. This is to keep service users from writing statements to graphs they don't have permission to, as well as prevent valid writes from service or * primary users to fail because they could be ultimately sent to the server by an un-authorized service user. A good first step to prevent such situations * is to setUpdateOnCommit(true). * * @param user * userId for the current thread which this service will use to authenticate */ public void setServiceUser(String user) throws AnzoException { if (servicePrincipal == null) { servicePrincipal = getServicePrincipal(); } if (!servicePrincipal.isSysadmin()) { throw new AnzoException(ExceptionConstants.COMBUS.RUNAS_NOT_AUTHORIZED); } if (user == null || user.equals(servicePrincipal.getName())) { runAsUser.remove(); } else { runAsUser.set(user); } runAsPrincipal.remove(); } /** * Get the userId for the current thread which this service will use to authenticate * * @return the userId for the current thread which this service will use to authenticate */ public String getServiceUser() { String ruser = runAsUser.get(); if (ruser != null) { return ruser; } else { return clientDatasource.getServiceUser(); } } /** * Allow end user to provide a description about the user * * @param userDescription * User provided description */ public void setUserDescription(String userDescription) { this.userDescription = userDescription; } /** * Get user provided description of client if available * * @return User provided description */ public String getUserDescription() { return userDescription; } private boolean getGraphPermission(URI namedGraphUri, Privilege privilege, URI metadataPredicate) throws AnzoException { // not all data sources will provide access control info in the metadata graph but for those // that do, we can short circuit a call to the server if we have the graph in the replica. URI metadataGraphUri = UriGenerator.generateMetadataGraphUri(namedGraphUri); Collection<Statement> roleStmts = quadStore.find(namedGraphUri, metadataPredicate, null, metadataGraphUri); Set<URI> graphRoles = new HashSet<URI>(); for (Statement stmt : roleStmts) { graphRoles.add((URI) stmt.getObject()); } if (org.openanzo.rdf.utils.Collections.memberOf(graphRoles, getServicePrincipal().getRoles())) { return true; } // we could potentially avoid some of the following calls to the server if (!namedGraphExists(namedGraphUri)) { return false; } if (getServicePrincipal().isSysadmin()) { return true; } return org.openanzo.rdf.utils.Collections.memberOf(clientDatasource.getAuthorizationService().getRolesForGraph(createContext(IAuthorizationService.GET_ROLES_FOR_GRAPH), namedGraphUri, privilege), getServicePrincipal().getRoles()); } /** * Return true if the user has permission to read data from the given grap * * @param namedGraphUri * URI of graph for which to determine read permission * @return true if the user has permission to read data from the given graph * @throws AnzoException */ public boolean canReadNamedGraph(URI namedGraphUri) throws AnzoException { return getGraphPermission(namedGraphUri, Privilege.READ, org.openanzo.ontologies.openanzo.NamedGraph.canBeReadByProperty); } /** * Return true if the user has permission to add data from the given graph * * @param namedGraphUri * URI of graph for which to determine add permission * @return true if the user has permission to add data from the given graph * @throws AnzoException */ public boolean canAddToNamedGraph(URI namedGraphUri) throws AnzoException { return getGraphPermission(namedGraphUri, Privilege.ADD, org.openanzo.ontologies.openanzo.NamedGraph.canBeAddedToByProperty); } /** * Return true if the user has permission to remove data from the given graph * * @param namedGraphUri * URI of graph for which to determine remove permission * @return true if the user has permission to remove data from the given graph * @throws AnzoException */ public boolean canRemoveFromNamedGraph(URI namedGraphUri) throws AnzoException { return getGraphPermission(namedGraphUri, Privilege.REMOVE, org.openanzo.ontologies.openanzo.NamedGraph.canBeRemovedFromByProperty); } /** * Get the AnzoPrincipal object for the the currently connected user * * @return AnzoPrincipal object for the the currently connected user * @throws AnzoException */ public AnzoPrincipal getServicePrincipal() throws AnzoException { String user = runAsUser.get(); if (user != null) { AnzoPrincipal runAsPrincipal = this.runAsPrincipal.get(); if (runAsPrincipal != null) { return runAsPrincipal; } IOperationContext context = new BaseOperationContext("getUserPrincipal", BaseOperationContext.generateOperationId(), null); runAsPrincipal = clientDatasource.authenticationService.getUserPrincipal(context, user); this.runAsPrincipal.set(runAsPrincipal); return runAsPrincipal; } else { if (servicePrincipal == null) { IOperationContext context = new BaseOperationContext("getUserPrincipal", BaseOperationContext.generateOperationId(), null); servicePrincipal = clientDatasource.authenticationService.getUserPrincipal(context, getServiceUser()); return servicePrincipal; } return servicePrincipal; } } protected IOperationContext createContext(String name) throws AnzoException { return new BaseOperationContext(name, BaseOperationContext.generateOperationId(), getServicePrincipal()); } class JMSConnectionListener implements INotificationConnectionListener { public void connectionStateChanged(int state) { // react to auto-reconnection from the underlying // JMS connection if (state == INotificationConnectionListener.CONNECTED) { if (!connected) { try { connect(); } catch (AnzoException e) { log.error(LogUtils.COMBUS_MARKER, Messages.formatString(ExceptionConstants.COMBUS.JMS_CONNECT_FAILED), e); } } // react to disconnection from the underlying JMS connection } else if (state == INotificationConnectionListener.DISCONNECTED || state == INotificationConnectionListener.CONNECTIONFAILED) { if (connected) { try { disconnect(); } catch (AnzoException e) { log.error(LogUtils.COMBUS_MARKER, Messages.formatString(ExceptionConstants.COMBUS.ERROR_DISCONNECTING_NOTIFICATIONS)); } } } } public void notificationException(AnzoException exception) { } } private static class BaseNamedGraphInitializer implements INamedGraphInitializer { public void initializeNamedGraph(IAnzoGraph namedGraph, boolean createNew) throws AnzoException { if (createNew) { INamedGraph metadataGraph = namedGraph.getMetadataGraph(); URI uri = namedGraph.getNamedGraphUri(); URI metadataGraphUri = metadataGraph.getNamedGraphUri(); if (!metadataGraph.contains(uri, org.openanzo.ontologies.openanzo.NamedGraph.hasMetadataGraphProperty, metadataGraphUri)) { metadataGraph.add(uri, org.openanzo.ontologies.openanzo.NamedGraph.hasMetadataGraphProperty, metadataGraphUri); metadataGraph.add(uri, RDF.TYPE, org.openanzo.ontologies.openanzo.NamedGraph.TYPE); } } } public Collection<IPrecondition> getPreconditions() { return null; } } private static class RevisionedNamedGraphInitializer implements INamedGraphInitializer { boolean revisioned = true; public RevisionedNamedGraphInitializer(boolean revisioned) { this.revisioned = revisioned; } public void initializeNamedGraph(IAnzoGraph namedGraph, boolean createNew) throws AnzoException { if (createNew) { INamedGraph metadataGraph = namedGraph.getMetadataGraph(); org.openanzo.ontologies.openanzo.NamedGraph graph = AnzoFactory.getNamedGraph(namedGraph.getNamedGraphUri(), metadataGraph); //if (graph.getRevisioned() == null) { graph.setRevisioned(revisioned); //} } } public Collection<IPrecondition> getPreconditions() { return null; } } private static class StatementStreamInitializer implements INamedGraphInitializer { public StatementStreamInitializer() { } public void initializeNamedGraph(IAnzoGraph namedGraph, boolean createNew) throws AnzoException { if (createNew) { INamedGraph metadataGraph = namedGraph.getMetadataGraph(); Statement type = Constants.valueFactory.createStatement(namedGraph.getNamedGraphUri(), RDF.TYPE, org.openanzo.ontologies.openanzo.StatementStream.TYPE, metadataGraph.getNamedGraphUri()); if (!metadataGraph.contains(type)) { metadataGraph.add(type); } } } public Collection<IPrecondition> getPreconditions() { return null; } } private static class GraphExistenceInitializer implements INamedGraphInitializer { IAnzoGraph graph = null; boolean mustExist = true; GraphExistenceInitializer(boolean mustExist) { this.mustExist = mustExist; } public void initializeNamedGraph(IAnzoGraph namedGraph, boolean createNew) throws AnzoException { this.graph = namedGraph; } public Collection<IPrecondition> getPreconditions() { IPrecondition precondition = new Precondition(); String queryString = "ASK { <" + graph.getNamedGraphUri() + "> <" + RDF.TYPE + "> <" + org.openanzo.ontologies.openanzo.NamedGraph.TYPE + ">}"; precondition.setQuery(queryString); precondition.setDefaultGraphUris(Collections.singleton(UriGenerator.generateMetadataGraphUri(graph.getNamedGraphUri()))); precondition.setResult(mustExist); return Collections.singleton(precondition); } } }