Modified: jena/Experimental/jena-client/src/main/java/org/apache/jena/client/graph/DatasetGraphConnection.java URL: http://svn.apache.org/viewvc/jena/Experimental/jena-client/src/main/java/org/apache/jena/client/graph/DatasetGraphConnection.java?rev=1672230&r1=1672229&r2=1672230&view=diff ============================================================================== --- jena/Experimental/jena-client/src/main/java/org/apache/jena/client/graph/DatasetGraphConnection.java (original) +++ jena/Experimental/jena-client/src/main/java/org/apache/jena/client/graph/DatasetGraphConnection.java Thu Apr 9 02:22:47 2015 @@ -18,121 +18,107 @@ package org.apache.jena.client.graph; -import org.apache.jena.client.ConnectionBase; -import org.apache.jena.client.QueryStatement; -import org.apache.jena.client.UpdateStatement; -import org.apache.jena.client.Updater; - -import com.hp.hpl.jena.query.Query; -import com.hp.hpl.jena.query.ReadWrite; -import com.hp.hpl.jena.sparql.core.DatasetGraph; -import com.hp.hpl.jena.sparql.core.Transactional; -import com.hp.hpl.jena.update.Update; +import org.apache.jena.atlas.iterator.Action ; +import org.apache.jena.client.AbstractConnection ; +import org.apache.jena.client.QueryStatement ; +import org.apache.jena.client.UpdateStatement ; +import org.apache.jena.client.Updater ; + +import com.hp.hpl.jena.query.Query ; +import com.hp.hpl.jena.query.ReadWrite ; +import com.hp.hpl.jena.sparql.core.DatasetGraph ; +import com.hp.hpl.jena.sparql.core.Transactional ; +import com.hp.hpl.jena.update.GraphStoreFactory ; +import com.hp.hpl.jena.update.Update ; -public class DatasetGraphConnection extends ConnectionBase +public class DatasetGraphConnection extends AbstractConnection { private final DatasetGraph dsg; + private final Transactional tx; private boolean autoCommitMode = true; - DatasetGraphConnection(DatasetGraphRepository repo, DatasetGraph dsg) + DatasetGraphConnection(DatasetGraphDatabaseClient repo, DatasetGraph dsg, Transactional tx) { super(repo); this.dsg = dsg; - } - - @Override - protected Updater doCreateStreamingUpdater() - { - DatasetGraphUpdater updater = new DatasetGraphUpdater(dsg, autoCommitMode); - updater.open(); - return updater; + this.tx = tx; } @Override public QueryStatement doCreateQueryStatement(String queryString) { - return new DatasetGraphQueryStatement(queryString, dsg, this); + return new DatasetGraphQueryStatement(queryString, dsg, tx, autoCommitMode, this); } @Override public QueryStatement doCreateQueryStatement(Query query) { - return new DatasetGraphQueryStatement(query, dsg, this); + return new DatasetGraphQueryStatement(query, dsg, tx, autoCommitMode, this); } @Override public UpdateStatement doCreateUpdateStatement(String updateString) { - return new DatasetGraphUpdateStatement(updateString, this); + return new DatasetGraphUpdateStatement(updateString, GraphStoreFactory.create(dsg), autoCommitMode, this); } @Override public UpdateStatement doCreateUpdateStatement(Update update) { - return new DatasetGraphUpdateStatement(update, this); + return new DatasetGraphUpdateStatement(update, GraphStoreFactory.create(dsg), autoCommitMode, this); } @Override public UpdateStatement doCreateUpdateStatement(Iterable<? extends Update> updates) { - return new DatasetGraphUpdateStatement(updates, this); + return new DatasetGraphUpdateStatement(updates, GraphStoreFactory.create(dsg), autoCommitMode, this); + } + + @Override + protected UpdateStatement doCreateUpdateStatement(Action<Updater> action) + { + return new DatasetGraphUpdateStatement(action, GraphStoreFactory.create(dsg), autoCommitMode, this); } @Override public boolean supportsTransactions() { - return dsg instanceof Transactional; + return (null != tx); } @Override public void begin(ReadWrite readWrite) { autoCommitMode = false; - if (supportsTransactions()) - { - ((Transactional)dsg).begin(readWrite); - } + if (supportsTransactions()) tx.begin(readWrite); } @Override public void commit() { autoCommitMode = true; - if (supportsTransactions()) - { - ((Transactional)dsg).commit(); - } + if (supportsTransactions()) tx.commit(); } @Override public void abort() { autoCommitMode = true; - if (supportsTransactions()) - { - ((Transactional)dsg).abort(); - } + if (supportsTransactions()) tx.abort(); } @Override public void end() { autoCommitMode = true; - if (supportsTransactions()) - { - ((Transactional)dsg).end(); - } + if (supportsTransactions()) tx.end(); } @Override public boolean isInTransaction() { - if (supportsTransactions()) - { - return ((Transactional)dsg).isInTransaction(); - } - return false; + return supportsTransactions() && tx.isInTransaction(); } - + }
Added: jena/Experimental/jena-client/src/main/java/org/apache/jena/client/graph/DatasetGraphDatabaseClient.java URL: http://svn.apache.org/viewvc/jena/Experimental/jena-client/src/main/java/org/apache/jena/client/graph/DatasetGraphDatabaseClient.java?rev=1672230&view=auto ============================================================================== --- jena/Experimental/jena-client/src/main/java/org/apache/jena/client/graph/DatasetGraphDatabaseClient.java (added) +++ jena/Experimental/jena-client/src/main/java/org/apache/jena/client/graph/DatasetGraphDatabaseClient.java Thu Apr 9 02:22:47 2015 @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jena.client.graph; + +import org.apache.jena.client.Connection ; +import org.apache.jena.client.DatabaseClient ; + +import com.hp.hpl.jena.sparql.core.DatasetGraph ; +import com.hp.hpl.jena.sparql.core.Transactional ; + +/** + * A DatabaseClient wrapped around a local DatasetGraph. + */ +public class DatasetGraphDatabaseClient implements DatabaseClient +{ + private final DatasetGraph dsg; + private final Transactional tx; + + /** + * Construct a DatasetGraphDatabaseClient over the specified DatasetGraph. + * + * @param dsg the DatasetGraph on which to base this DatabaseClient + * @param tx the Transactional object to use for transactions; or null if no transactions desired + */ + public DatasetGraphDatabaseClient(DatasetGraph dsg, Transactional tx) + { + this.dsg = dsg; + this.tx = tx; + } + + @Override + public Connection getConnection() + { + return new DatasetGraphConnection(this, dsg, tx); + } + +} Modified: jena/Experimental/jena-client/src/main/java/org/apache/jena/client/graph/DatasetGraphQueryStatement.java URL: http://svn.apache.org/viewvc/jena/Experimental/jena-client/src/main/java/org/apache/jena/client/graph/DatasetGraphQueryStatement.java?rev=1672230&r1=1672229&r2=1672230&view=diff ============================================================================== --- jena/Experimental/jena-client/src/main/java/org/apache/jena/client/graph/DatasetGraphQueryStatement.java (original) +++ jena/Experimental/jena-client/src/main/java/org/apache/jena/client/graph/DatasetGraphQueryStatement.java Thu Apr 9 02:22:47 2015 @@ -17,38 +17,47 @@ */ package org.apache.jena.client.graph; -import org.apache.jena.client.QueryStatement; -import org.apache.jena.client.QueryStatementBase; +import org.apache.jena.client.QueryStatement ; +import org.apache.jena.client.AbstractQueryStatement ; -import com.hp.hpl.jena.query.Query; -import com.hp.hpl.jena.query.QueryExecution; -import com.hp.hpl.jena.query.QueryExecutionFactory; -import com.hp.hpl.jena.sparql.core.DatasetGraph; -import com.hp.hpl.jena.sparql.core.DatasetImpl; +import com.hp.hpl.jena.query.Query ; +import com.hp.hpl.jena.query.QueryExecution ; +import com.hp.hpl.jena.query.QueryExecutionFactory ; +import com.hp.hpl.jena.sparql.core.DatasetGraph ; +import com.hp.hpl.jena.sparql.core.DatasetImpl ; +import com.hp.hpl.jena.sparql.core.Transactional ; -class DatasetGraphQueryStatement extends QueryStatementBase<QueryExecution> implements QueryStatement +class DatasetGraphQueryStatement extends AbstractQueryStatement<QueryExecution> implements QueryStatement { private final DatasetGraph dsg; + private final Transactional tx; + private final boolean autoCommitMode; - DatasetGraphQueryStatement(String queryString, DatasetGraph dsg, DatasetGraphConnection connection) + DatasetGraphQueryStatement(String queryString, DatasetGraph dsg, Transactional tx, boolean autoCommitMode, DatasetGraphConnection connection) { super(queryString, connection); this.dsg = dsg; + this.tx = tx; + this.autoCommitMode = autoCommitMode; } - DatasetGraphQueryStatement(Query query, DatasetGraph dsg, DatasetGraphConnection connection) + DatasetGraphQueryStatement(Query query, DatasetGraph dsg, Transactional tx, boolean autoCommitMode, DatasetGraphConnection connection) { super(query, connection); this.dsg = dsg; + this.tx = tx; + this.autoCommitMode = autoCommitMode; } @Override + @SuppressWarnings("resource") // Suppress the warning that AutoCommitQueryExection is not used within a try-with-resources block protected QueryExecution createQueryExecution() { - QueryExecution qe = (null != queryString) ? + final QueryExecution qe = (null != queryString) ? QueryExecutionFactory.create(queryString, DatasetImpl.wrap(dsg)) : QueryExecutionFactory.create(queryObject, DatasetImpl.wrap(dsg)); - return qe; + + return (autoCommitMode && (null != tx)) ? new AutoCommitQueryExecution(qe, tx) : qe; } } Modified: jena/Experimental/jena-client/src/main/java/org/apache/jena/client/graph/DatasetGraphUpdateStatement.java URL: http://svn.apache.org/viewvc/jena/Experimental/jena-client/src/main/java/org/apache/jena/client/graph/DatasetGraphUpdateStatement.java?rev=1672230&r1=1672229&r2=1672230&view=diff ============================================================================== --- jena/Experimental/jena-client/src/main/java/org/apache/jena/client/graph/DatasetGraphUpdateStatement.java (original) +++ jena/Experimental/jena-client/src/main/java/org/apache/jena/client/graph/DatasetGraphUpdateStatement.java Thu Apr 9 02:22:47 2015 @@ -17,24 +17,92 @@ */ package org.apache.jena.client.graph; -import org.apache.jena.client.UpdateStatementBase; +import org.apache.jena.atlas.iterator.Action ; +import org.apache.jena.client.AbstractUpdateStatement ; +import org.apache.jena.client.UpdateExecution ; +import org.apache.jena.client.Updater ; -import com.hp.hpl.jena.update.Update; +import com.hp.hpl.jena.query.ARQ ; +import com.hp.hpl.jena.query.ReadWrite ; +import com.hp.hpl.jena.sparql.core.Transactional ; +import com.hp.hpl.jena.sparql.modify.UpdateEngine ; +import com.hp.hpl.jena.sparql.modify.UpdateEngineFactory ; +import com.hp.hpl.jena.sparql.modify.UpdateEngineRegistry ; +import com.hp.hpl.jena.sparql.util.Context ; +import com.hp.hpl.jena.update.GraphStore ; +import com.hp.hpl.jena.update.Update ; -class DatasetGraphUpdateStatement extends UpdateStatementBase +class DatasetGraphUpdateStatement extends AbstractUpdateStatement { - DatasetGraphUpdateStatement(String updateString, DatasetGraphConnection connection) + private final GraphStore graphStore; + private final boolean autoCommitMode; + + DatasetGraphUpdateStatement(String updateString, GraphStore graphStore, boolean autoCommitMode, DatasetGraphConnection connection) { super(updateString, connection); + this.graphStore = graphStore; + this.autoCommitMode = autoCommitMode; } - DatasetGraphUpdateStatement(Update update, DatasetGraphConnection connection) + DatasetGraphUpdateStatement(Update update, GraphStore graphStore, boolean autoCommitMode, DatasetGraphConnection connection) { super(update, connection); + this.graphStore = graphStore; + this.autoCommitMode = autoCommitMode; } - DatasetGraphUpdateStatement(Iterable<? extends Update> updates, DatasetGraphConnection connection) + DatasetGraphUpdateStatement(Iterable<? extends Update> updates, GraphStore graphStore, boolean autoCommitMode, DatasetGraphConnection connection) { super(updates, connection); + this.graphStore = graphStore; + this.autoCommitMode = autoCommitMode; + } + + DatasetGraphUpdateStatement(Action<Updater> action, GraphStore graphStore, boolean autoCommitMode, DatasetGraphConnection connection) + { + super(action, connection); + this.graphStore = graphStore; + this.autoCommitMode = autoCommitMode; + } + + @Override + protected UpdateExecution createUpdateExecution() + { + // Copy the global context to freeze it. + final Context context = new Context(ARQ.getContext()); + + return new UpdateExecution() + { + @Override + public void execUpdate(Action<Updater> action) + { + final UpdateEngineFactory factory = UpdateEngineRegistry.findFactory(graphStore, context); + final UpdateEngine updateEngine = factory.create(graphStore, null, context); + + final boolean performTransaction = autoCommitMode && graphStore instanceof Transactional; + final Transactional tx = performTransaction ? (Transactional)graphStore : null; + if (performTransaction) tx.begin(ReadWrite.WRITE); + try + { + try (UpdateEngineUpdater updater = new UpdateEngineUpdater(updateEngine)) + { + updater.open(); + action.apply(updater); + } + if (performTransaction) tx.commit(); + } + finally + { + if (performTransaction) tx.end(); + } + + } + }; + } + + @Override + public void cancel() + { + // do nothing } } Added: jena/Experimental/jena-client/src/main/java/org/apache/jena/client/graph/UpdateEngineUpdater.java URL: http://svn.apache.org/viewvc/jena/Experimental/jena-client/src/main/java/org/apache/jena/client/graph/UpdateEngineUpdater.java?rev=1672230&view=auto ============================================================================== --- jena/Experimental/jena-client/src/main/java/org/apache/jena/client/graph/UpdateEngineUpdater.java (added) +++ jena/Experimental/jena-client/src/main/java/org/apache/jena/client/graph/UpdateEngineUpdater.java Thu Apr 9 02:22:47 2015 @@ -0,0 +1,121 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.client.graph; + +import java.util.Iterator ; + +import org.apache.jena.client.AbstractUpdater ; + +import com.hp.hpl.jena.sparql.core.Quad ; +import com.hp.hpl.jena.sparql.modify.UpdateEngine ; +import com.hp.hpl.jena.sparql.modify.UpdateSink ; +import com.hp.hpl.jena.sparql.modify.request.QuadDataAccSink ; +import com.hp.hpl.jena.update.Update ; + +class UpdateEngineUpdater extends AbstractUpdater implements AutoCloseable +{ + private final UpdateEngine updateEngine; + + public UpdateEngineUpdater(UpdateEngine updateEngine) + { + this.updateEngine = updateEngine; + } + + public void open() + { + updateEngine.startRequest(); + } + + @Override + public void close() + { + updateEngine.finishRequest(); + } + + @Override + protected void doInsert(Iterator<? extends Quad> quadIter) + { + UpdateSink sink = updateEngine.getUpdateSink(); + try + { + QuadDataAccSink quadSink = sink.createInsertDataSink(); + try + { + while (quadIter.hasNext()) + { + Quad quad = quadIter.next(); + quadSink.addQuad(quad); + } + } + finally + { + quadSink.close(); + } + } + finally + { + sink.close(); + } + } + + @Override + protected void doDelete(Iterator<? extends Quad> quadIter) + { + UpdateSink sink = updateEngine.getUpdateSink(); + try + { + QuadDataAccSink quadSink = sink.createDeleteDataSink(); + try + { + while (quadIter.hasNext()) + { + Quad quad = quadIter.next(); + quadSink.addQuad(quad); + } + } + finally + { + quadSink.close(); + } + } + finally + { + sink.close(); + } + } + + @Override + protected void doUpdate(Iterator<? extends Update> updateIter) + { + UpdateSink sink = updateEngine.getUpdateSink(); + try + { + while (updateIter.hasNext()) + { + Update update = updateIter.next(); + sink.send(update); + } + } + finally + { + sink.close(); + } + } + +} Modified: jena/Experimental/jena-client/src/main/java/org/apache/jena/client/http/HttpConnection.java URL: http://svn.apache.org/viewvc/jena/Experimental/jena-client/src/main/java/org/apache/jena/client/http/HttpConnection.java?rev=1672230&r1=1672229&r2=1672230&view=diff ============================================================================== --- jena/Experimental/jena-client/src/main/java/org/apache/jena/client/http/HttpConnection.java (original) +++ jena/Experimental/jena-client/src/main/java/org/apache/jena/client/http/HttpConnection.java Thu Apr 9 02:22:47 2015 @@ -17,46 +17,34 @@ */ package org.apache.jena.client.http; -import org.apache.jena.client.ConnectionBase; -import org.apache.jena.client.QueryStatement; -import org.apache.jena.client.UpdateStatement; -import org.apache.jena.client.Updater; -import org.apache.jena.riot.WebContent ; +import org.apache.jena.atlas.iterator.Action ; +import org.apache.jena.atlas.web.auth.HttpAuthenticator ; +import org.apache.jena.client.AbstractConnection ; +import org.apache.jena.client.QueryStatement ; +import org.apache.jena.client.UpdateStatement ; +import org.apache.jena.client.Updater ; -import com.hp.hpl.jena.query.Query; -import com.hp.hpl.jena.query.ReadWrite; -import com.hp.hpl.jena.update.Update; +import com.hp.hpl.jena.query.Query ; +import com.hp.hpl.jena.query.ReadWrite ; +import com.hp.hpl.jena.update.Update ; -public class HttpConnection extends ConnectionBase +public class HttpConnection extends AbstractConnection { private final String queryEndpoint; private final String updateEndpoint; - - private boolean useContentTypeSPARQLUpdate = true; + private final HttpAuthenticator authenticator; private String selectContentType = null; private String modelContentType = null; private String askContentType = null; - private String updateContentType = null; - HttpConnection(HttpRepository repo, String queryEndpoint, String updateEndpoint) + HttpConnection(HttpDatabaseClient dc, final String queryEndpoint, final String updateEndpoint, final HttpAuthenticator authenticator) { - super(repo); + super(dc); this.queryEndpoint = queryEndpoint; this.updateEndpoint = updateEndpoint; - } - - /** - * Whether or not to use <code>"application/sparql-update"</code> as the content type for SPARQL Update. Defaults to true. - * If set to false then <code>"application/x-www-form-urlencoded"</code> is used instead (however, be warned that urlencoded - * is not streaming and will buffer the entire update into memory. - * - * @param useContentTypeSPARQLUpdate true if <code>"application/sparql-update"</code> is desired; true is the default - */ - public void useContentTypeSPARQLUpdate(boolean useContentTypeSPARQLUpdate) - { - this.updateContentType = useContentTypeSPARQLUpdate ? WebContent.contentTypeSPARQLUpdate : WebContent.contentTypeHTMLForm; + this.authenticator = authenticator; } /** @@ -89,54 +77,60 @@ public class HttpConnection extends Conn this.askContentType = contentType; } - @Override - protected Updater doCreateStreamingUpdater() - { - if (null == updateEndpoint) - throw new IllegalStateException("Cannot create an Updater because the update endpoint URL has not been set"); - HttpUpdater updater = new HttpUpdater(updateEndpoint, updateContentType); - updater.open(); - return updater; + /** + * Sets the Content Type for SPARQL Update queries. The two supported types are: <code>"application/sparql-update"</code> + * and <code>"application/x-www-form-urlencoded"</code>. Setting this to <code>null</code> will use the default, + * which is <code>"application/sparql-update"</code> + * <p> + * <strong>Warning:</strong> If <code>"application/x-www-form-urlencoded"</code> is used then the update is not streaming + * and must be buffered entirely into memory before being submitted to the server. + * + * @param contentType the Content Type to use for SPARQL Update queries + */ + public void setUpdateContentType(String contentType) { + this.updateContentType = contentType; } @Override - public QueryStatement doCreateQueryStatement(String queryString) + protected QueryStatement doCreateQueryStatement(String queryString) { - if (null == queryEndpoint) - throw new IllegalStateException("Cannot create a QueryStatement because the query endpoint URL has not been set"); - return new HttpQueryStatement(queryString, queryEndpoint, this, selectContentType, modelContentType, askContentType); + if (null == queryEndpoint) throw new IllegalStateException("Cannot create a QueryStatement because the query endpoint URL has not been set"); + return new HttpQueryStatement(queryString, queryEndpoint, authenticator, this, selectContentType, modelContentType, askContentType); } @Override - public QueryStatement doCreateQueryStatement(Query query) + protected QueryStatement doCreateQueryStatement(Query query) { - if (null == queryEndpoint) - throw new IllegalStateException("Cannot create a QueryStatement because the query endpoint URL has not been set"); - return new HttpQueryStatement(query, queryEndpoint, this, selectContentType, modelContentType, askContentType); + if (null == queryEndpoint) throw new IllegalStateException("Cannot create a QueryStatement because the query endpoint URL has not been set"); + return new HttpQueryStatement(query, queryEndpoint, authenticator, this, selectContentType, modelContentType, askContentType); } @Override - public UpdateStatement doCreateUpdateStatement(String updateString) + protected UpdateStatement doCreateUpdateStatement(String updateString) { - if (null == updateEndpoint) - throw new IllegalStateException("Cannot create an UpdateStatement because the update endpoint URL has not been set"); - return new HttpUpdateStatement(updateString, this); + if (null == updateEndpoint) throw new IllegalStateException("Cannot create an UpdateStatement because the update endpoint URL has not been set"); + return new HttpUpdateStatement(updateString, updateEndpoint, authenticator, this, updateContentType); } @Override - public UpdateStatement doCreateUpdateStatement(Update update) + protected UpdateStatement doCreateUpdateStatement(Update update) + { + if (null == updateEndpoint) throw new IllegalStateException("Cannot create an UpdateStatement because the update endpoint URL has not been set"); + return new HttpUpdateStatement(update, updateEndpoint, authenticator, this, updateContentType); + } + + @Override + protected UpdateStatement doCreateUpdateStatement(Iterable<? extends Update> updates) { - if (null == updateEndpoint) - throw new IllegalStateException("Cannot create an UpdateStatement because the update endpoint URL has not been set"); - return new HttpUpdateStatement(update, this); + if (null == updateEndpoint) throw new IllegalStateException("Cannot create an UpdateStatement because the update endpoint URL has not been set"); + return new HttpUpdateStatement(updates, updateEndpoint, authenticator, this, updateContentType); } @Override - public UpdateStatement doCreateUpdateStatement(Iterable<? extends Update> updates) + protected UpdateStatement doCreateUpdateStatement(Action<Updater> action) { - if (null == updateEndpoint) - throw new IllegalStateException("Cannot create an UpdateStatement because the update endpoint URL has not been set"); - return new HttpUpdateStatement(updates, this); + if (null == updateEndpoint) throw new IllegalStateException("Cannot create an UpdateStatement because the update endpoint URL has not been set"); + return new HttpUpdateStatement(action, updateEndpoint, authenticator, this, updateContentType); } @Override Added: jena/Experimental/jena-client/src/main/java/org/apache/jena/client/http/HttpDatabaseClient.java URL: http://svn.apache.org/viewvc/jena/Experimental/jena-client/src/main/java/org/apache/jena/client/http/HttpDatabaseClient.java?rev=1672230&view=auto ============================================================================== --- jena/Experimental/jena-client/src/main/java/org/apache/jena/client/http/HttpDatabaseClient.java (added) +++ jena/Experimental/jena-client/src/main/java/org/apache/jena/client/http/HttpDatabaseClient.java Thu Apr 9 02:22:47 2015 @@ -0,0 +1,137 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jena.client.http; + +import org.apache.jena.atlas.web.auth.HttpAuthenticator ; +import org.apache.jena.client.Connection; +import org.apache.jena.client.DatabaseClient; + +/** + * A DatabaseClient wrapped around a remote HTTP SPARQL endpoint. + * <p> + * <strong>Note:</strong> Databases other than Jena Fuseki may be severely limited on the type and size + * of SPARQL 1.1 Update queries. For example, Sesame (and Sesame-based endpoints) have two main limitations: + * <ol> + * <li> + * Sesame only supports the {@code "application/x-www-form-urlencoded"} content-type. By default DatabaseClient + * will use the {@code "application/sparql-update"} content-type, but it can be changed to use + * {@code "application/x-www-form-urlencoded"} by setting the appropriate type on the {@link HttpDatabaseClient} object. + * <code> + * <pre> + * HttpDatabaseClient client = DatabaseClientFactory.createRemote(queryEndpoint, updateEndpoint); + * client.setUpdateContentType(WebContent.contentTypeHTMLForm);</pre> + * </code> + * <strong>Warning:</strong> When <code>"application/x-www-form-urlencoded"</code> is used then the update request will not be + * streaming and will be buffered entirely into memory before being submitted to the server. + * </li> + * <p><br> + * <li> + * Sesame parses SPARQL Update request into an in-memory Abstract Syntax Tree (AST) completely before executing the + * query. This can cause two issues: 1) an update request with a large number of operations may cause a an OutOfMemoryException; + * and 2) INSERT DATA and DELETE DATA operations are not streamed into the underlying repository, and large such operations + * may cause either an OutOfMemoryException or a StackOverflowException. + * <p><br> + * <strong>Note:</strong> Sesame may not actually report the true exception, rather it seems to report + * {@code org.openrdf.http.server.ClientHTTPException: Unsupported MIME type: application/x-www-form-urlencoded} when + * a large update requst is submitted. + * <p><br> + * The work-around for this issue is to break your update query into smaller chunks and submit them as separate update requests. + * </li> + * </ol> + */ +public class HttpDatabaseClient implements DatabaseClient +{ + private final String queryEndpoint; + private final String updateEndpoint; + private final HttpAuthenticator authenticator; + + private String selectContentType = null; + private String modelContentType = null; + private String askContentType = null; + + private String updateContentType = null; + + /** + * Construct a HttpDatabaseClient over the specified HTTP SPARQL endpoint. + * + * @param queryEndpoint the SPARQL query endpoint URL; if null, then you may not execute queries against the endpoint + * @param updateEndpoint the SPARQL update endpoint URL; if null, then you may not execute updates against the endpoint + * @param authenticator the HTTP authenticator to use; if null, then no authenticator is used + */ + public HttpDatabaseClient(final String queryEndpoint, final String updateEndpoint, final HttpAuthenticator authenticator) + { + this.queryEndpoint = queryEndpoint; + this.updateEndpoint = updateEndpoint; + this.authenticator = authenticator; + } + + @Override + public Connection getConnection() + { + HttpConnection conn = new HttpConnection(this, queryEndpoint, updateEndpoint, authenticator); + conn.setSelectContentType(selectContentType); + conn.setModelContentType(modelContentType); + conn.setAskContentType(askContentType); + conn.setUpdateContentType(updateContentType); + return conn; + } + + /** + * Sets the Content Type for SELECT queries provided that the format is supported. + * Setting this to <code>null</code> will use Jena's default. + * + * @param contentType the Content Type to use for SELECT queries + */ + public void setSelectContentType(String contentType) { + this.selectContentType = contentType; + } + + /** + * Sets the Content Type for CONSTRUCT/DESCRIBE queries provided that the format is supported. + * Setting this to <code>null</code> will use Jena's default. + * + * @param contentType the Content Type to use for CONSTRUCT/DESCRIBE queries + */ + public void setModelContentType(String contentType) { + this.modelContentType = contentType; + } + + /** + * Sets the Content Type for ASK queries provided that the format is supported. + * Setting this to <code>null</code> will use Jena's default. + * + * @param contentType the Content Type to use for ASK queries + */ + public void setAskContentType(String contentType) { + this.askContentType = contentType; + } + + /** + * Sets the Content Type for SPARQL Update requests. The two supported types are: <code>"application/sparql-update"</code> + * and <code>"application/x-www-form-urlencoded"</code>. Setting this to <code>null</code> will use the default, + * which is <code>"application/sparql-update"</code> + * <p> + * <strong>Warning:</strong> If <code>"application/x-www-form-urlencoded"</code> is used then the update request is not streaming + * and will be buffered entirely into memory before being submitted to the server. + * + * @param contentType the Content Type to use for SPARQL Update queries + */ + public void setUpdateContentType(String contentType) { + this.updateContentType = contentType; + } +} Modified: jena/Experimental/jena-client/src/main/java/org/apache/jena/client/http/HttpQueryStatement.java URL: http://svn.apache.org/viewvc/jena/Experimental/jena-client/src/main/java/org/apache/jena/client/http/HttpQueryStatement.java?rev=1672230&r1=1672229&r2=1672230&view=diff ============================================================================== --- jena/Experimental/jena-client/src/main/java/org/apache/jena/client/http/HttpQueryStatement.java (original) +++ jena/Experimental/jena-client/src/main/java/org/apache/jena/client/http/HttpQueryStatement.java Thu Apr 9 02:22:47 2015 @@ -17,42 +17,49 @@ */ package org.apache.jena.client.http; -import org.apache.jena.client.QueryStatement; -import org.apache.jena.client.QueryStatementBase; +import org.apache.jena.atlas.web.auth.HttpAuthenticator ; +import org.apache.jena.client.QueryStatement ; +import org.apache.jena.client.AbstractQueryStatement ; -import com.hp.hpl.jena.query.Query; -import com.hp.hpl.jena.sparql.engine.http.QueryEngineHTTP; +import com.hp.hpl.jena.query.Query ; +import com.hp.hpl.jena.sparql.engine.http.QueryEngineHTTP ; -class HttpQueryStatement extends QueryStatementBase<QueryEngineHTTP> implements QueryStatement +class HttpQueryStatement extends AbstractQueryStatement<QueryEngineHTTP> implements QueryStatement { private final String queryEndpoint; + private final HttpAuthenticator authenticator; private final String selectContentType; private final String modelContentType; private final String askContentType; - HttpQueryStatement(String queryString, String queryEndpoint, HttpConnection connection, String selectContentType, String modelContentType, String askContentType) + HttpQueryStatement(String queryString, String queryEndpoint, HttpAuthenticator authenticator, HttpConnection connection, String selectContentType, String modelContentType, String askContentType) { super(queryString, connection); this.queryEndpoint = queryEndpoint; + this.authenticator = authenticator; this.selectContentType = selectContentType; this.modelContentType = modelContentType; this.askContentType = askContentType; } - HttpQueryStatement(Query query, String queryEndpoint, HttpConnection connection, String selectContentType, String modelContentType, String askContentType) + HttpQueryStatement(Query query, String queryEndpoint, HttpAuthenticator authenticator, HttpConnection connection, String selectContentType, String modelContentType, String askContentType) { super(query, connection); this.queryEndpoint = queryEndpoint; + this.authenticator = authenticator; this.selectContentType = selectContentType; this.modelContentType = modelContentType; this.askContentType = askContentType; } + @Override protected QueryEngineHTTP createQueryExecution() { QueryEngineHTTP qe = (null != queryString) ? - new QueryEngineHTTP(queryEndpoint, queryString) : - new QueryEngineHTTP(queryEndpoint, queryObject); + new QueryEngineHTTP(queryEndpoint, queryString, authenticator) : + new QueryEngineHTTP(queryEndpoint, queryObject, authenticator); + + qe.setTimeout(timeout1, timeout2); if (null != selectContentType) qe.setSelectContentType(selectContentType); if (null != modelContentType) qe.setModelContentType(modelContentType); Added: jena/Experimental/jena-client/src/main/java/org/apache/jena/client/http/HttpUpdateExecution.java URL: http://svn.apache.org/viewvc/jena/Experimental/jena-client/src/main/java/org/apache/jena/client/http/HttpUpdateExecution.java?rev=1672230&view=auto ============================================================================== --- jena/Experimental/jena-client/src/main/java/org/apache/jena/client/http/HttpUpdateExecution.java (added) +++ jena/Experimental/jena-client/src/main/java/org/apache/jena/client/http/HttpUpdateExecution.java Thu Apr 9 02:22:47 2015 @@ -0,0 +1,212 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jena.client.http; + +import java.io.ByteArrayOutputStream ; +import java.io.UnsupportedEncodingException ; +import java.nio.charset.UnsupportedCharsetException ; +import java.util.ArrayList ; +import java.util.List ; +import java.util.Map ; + +import org.apache.http.Consts ; +import org.apache.http.HttpEntity ; +import org.apache.http.NameValuePair ; +import org.apache.http.client.entity.UrlEncodedFormEntity ; +import org.apache.http.message.BasicNameValuePair ; +import org.apache.http.protocol.HttpContext ; +import org.apache.jena.atlas.io.IndentedWriter ; +import org.apache.jena.atlas.iterator.Action ; +import org.apache.jena.atlas.web.auth.HttpAuthenticator ; +import org.apache.jena.atlas.web.auth.SimpleAuthenticator ; +import org.apache.jena.client.UpdateExecution ; +import org.apache.jena.client.Updater ; +import org.apache.jena.riot.WebContent ; +import org.apache.jena.riot.web.HttpOp ; +import org.apache.jena.riot.web.HttpResponseLib ; +import org.slf4j.Logger ; +import org.slf4j.LoggerFactory ; + +import com.hp.hpl.jena.query.ARQ ; +import com.hp.hpl.jena.sparql.engine.http.QueryEngineHTTP ; +import com.hp.hpl.jena.sparql.engine.http.Service ; +import com.hp.hpl.jena.sparql.modify.request.UpdateWriter ; +import com.hp.hpl.jena.sparql.serializer.SerializationContext ; +import com.hp.hpl.jena.sparql.util.Context ; +import com.hp.hpl.jena.sparql.util.Symbol ; + +class HttpUpdateExecution implements UpdateExecution +{ + private static Logger log = LoggerFactory.getLogger(HttpUpdateExecution.class); + + /** + * Symbol used to set a {@link HttpContext} which will be used for HTTP requests + */ + public static final Symbol HTTP_CONTEXT = Symbol.create("httpContext"); + + private final String updateEndpoint; + private final SerializationContext sCxt; + private final String contentType; + private final Context context; + private HttpAuthenticator authenticator; + + protected long connectTimeout = -1; + protected long readTimeout = -1; + + public HttpUpdateExecution(final String updateEndpoint, final HttpAuthenticator authenticator, String contentType, final SerializationContext sCxt) + { + if (null == updateEndpoint) throw new NullPointerException("updateEndpoint"); + if (null == contentType) contentType = WebContent.contentTypeSPARQLUpdate; + + if (!WebContent.contentTypeSPARQLUpdate.equals(contentType) && !WebContent.contentTypeHTMLForm.equals(contentType)) + { + throw new IllegalArgumentException("Content-Type must be " + WebContent.contentTypeSPARQLUpdate + " or " + WebContent.contentTypeHTMLForm + ". Was:" + contentType); + } + + this.updateEndpoint = updateEndpoint; + this.contentType = contentType; + this.sCxt = sCxt; + + // Copy the global context to freeze it. + this.context = new Context(ARQ.getContext()); + + // Apply service configuration if relevant + HttpUpdateExecution.applyServiceConfig(updateEndpoint, this); + + // Don't want to overwrite credentials we may have picked up from + // service context in the parent constructor if the specified + // authenticator is null + if (authenticator != null) { + this.authenticator = authenticator; + } + } + + /** + * <p> + * Helper method which applies configuration from the Context to the query + * engine if a service context exists for the given URI + * </p> + * <p> + * Based off proposed patch for JENA-405 but modified to apply all relevant + * configuration, this is in part also based off of the private + * {@code configureQuery()} method of the {@link Service} class though it + * omits parameter merging since that will be done automatically whenever + * the {@link QueryEngineHTTP} instance makes a query for remote submission. + * </p> + * + * @param serviceURI + * Service URI + */ + private static void applyServiceConfig(String serviceURI, HttpUpdateExecution engine) { + @SuppressWarnings("unchecked") + Map<String, Context> serviceContextMap = (Map<String, Context>) engine.context.get(Service.serviceContext); + if (serviceContextMap != null && serviceContextMap.containsKey(serviceURI)) { + Context serviceContext = serviceContextMap.get(serviceURI); + if (log.isDebugEnabled()) + log.debug("Endpoint URI {} has SERVICE Context: {} ", serviceURI, serviceContext); + + // Apply authentication settings + String user = serviceContext.getAsString(Service.queryAuthUser); + String pwd = serviceContext.getAsString(Service.queryAuthPwd); + + if (user != null || pwd != null) { + user = user == null ? "" : user; + pwd = pwd == null ? "" : pwd; + if (log.isDebugEnabled()) + log.debug("Setting basic HTTP authentication for endpoint URI {} with username: {} ", serviceURI, user); + +// engine.setAuthentication(user, pwd.toCharArray()); + engine.authenticator = new SimpleAuthenticator(user, pwd.toCharArray()); + } + } + } + + /** + * Set time, in milliseconds + */ + public void setTimeout(long connectTimeout, long readTimeout) + { + this.connectTimeout = connectTimeout; + this.readTimeout = readTimeout; + } + + @Override + public void execUpdate(Action<Updater> action) + { + HttpEntity entity; + if (WebContent.contentTypeSPARQLUpdate.equals(contentType)) + { + entity = new SparqlUpdateEntity(action, sCxt); + } + else if (WebContent.contentTypeHTMLForm.equals(contentType)) + { + // There is no streaming if "application/x-www-form-urlencoded" is used. Instead everything will be buffered into memory. + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + IndentedWriter iw = new IndentedWriter(baos); + try + { + UpdateWriter writer = new UpdateWriter(iw, sCxt); + writer.open(); + try + { + action.apply(new UpdateWriterUpdater(writer)); + } + finally + { + writer.close(); + } + } + finally + { + iw.close(); + } + + String updateString ; + try + { + updateString = baos.toString(Consts.UTF_8.name()) ; + } + catch (UnsupportedEncodingException e) + { + // should never happen + throw new UnsupportedCharsetException(Consts.UTF_8.name()); + } + baos = null; + + List<NameValuePair> formparams = new ArrayList<>(); + formparams.add(new BasicNameValuePair("update", updateString)); + updateString = null; + + entity = new UrlEncodedFormEntity(formparams, Consts.UTF_8); + } + else { + throw new IllegalArgumentException("Content-Type must be " + WebContent.contentTypeSPARQLUpdate + " or " + WebContent.contentTypeHTMLForm + ". Was:" + contentType); + } + + // TODO Apply timeouts + if (connectTimeout > 0) { + + } + if (readTimeout > 0) { + + } + + HttpOp.execHttpPost(updateEndpoint, entity, null, HttpResponseLib.nullResponse, null, (HttpContext)context.get(HTTP_CONTEXT), authenticator) ; + } + +} Modified: jena/Experimental/jena-client/src/main/java/org/apache/jena/client/http/HttpUpdateStatement.java URL: http://svn.apache.org/viewvc/jena/Experimental/jena-client/src/main/java/org/apache/jena/client/http/HttpUpdateStatement.java?rev=1672230&r1=1672229&r2=1672230&view=diff ============================================================================== --- jena/Experimental/jena-client/src/main/java/org/apache/jena/client/http/HttpUpdateStatement.java (original) +++ jena/Experimental/jena-client/src/main/java/org/apache/jena/client/http/HttpUpdateStatement.java Thu Apr 9 02:22:47 2015 @@ -17,24 +17,63 @@ */ package org.apache.jena.client.http; -import org.apache.jena.client.UpdateStatementBase; +import org.apache.jena.atlas.iterator.Action ; +import org.apache.jena.atlas.web.auth.HttpAuthenticator ; +import org.apache.jena.client.UpdateExecution ; +import org.apache.jena.client.AbstractUpdateStatement ; +import org.apache.jena.client.Updater ; -import com.hp.hpl.jena.update.Update; +import com.hp.hpl.jena.update.Update ; -class HttpUpdateStatement extends UpdateStatementBase +class HttpUpdateStatement extends AbstractUpdateStatement { - HttpUpdateStatement(String updateString, HttpConnection connection) + private final String updateEndpoint; + private final HttpAuthenticator authenticator; + private final String updateContentType; + + HttpUpdateStatement(String updateString, String updateEndpoint, HttpAuthenticator authenticator, HttpConnection connection, String updateContentType) { super(updateString, connection); + this.updateEndpoint = updateEndpoint; + this.authenticator = authenticator; + this.updateContentType = updateContentType; } - HttpUpdateStatement(Update update, HttpConnection connection) + HttpUpdateStatement(Update update, String updateEndpoint, HttpAuthenticator authenticator, HttpConnection connection, String updateContentType) { super(update, connection); + this.updateEndpoint = updateEndpoint; + this.authenticator = authenticator; + this.updateContentType = updateContentType; } - HttpUpdateStatement(Iterable<? extends Update> updates, HttpConnection connection) + HttpUpdateStatement(Iterable<? extends Update> updates, String updateEndpoint, HttpAuthenticator authenticator, HttpConnection connection, String updateContentType) { super(updates, connection); + this.updateEndpoint = updateEndpoint; + this.authenticator = authenticator; + this.updateContentType = updateContentType; + } + + HttpUpdateStatement(Action<Updater> action, String updateEndpoint, HttpAuthenticator authenticator, HttpConnection connection, String updateContentType) + { + super(action, connection); + this.updateEndpoint = updateEndpoint; + this.authenticator = authenticator; + this.updateContentType = updateContentType; + } + + @Override + protected UpdateExecution createUpdateExecution() + { + HttpUpdateExecution ue = new HttpUpdateExecution(updateEndpoint, authenticator, updateContentType, null); + ue.setTimeout(timeout1, timeout2); + return ue; + } + + @Override + public void cancel() + { + // do nothing } } Added: jena/Experimental/jena-client/src/main/java/org/apache/jena/client/http/SparqlUpdateEntity.java URL: http://svn.apache.org/viewvc/jena/Experimental/jena-client/src/main/java/org/apache/jena/client/http/SparqlUpdateEntity.java?rev=1672230&view=auto ============================================================================== --- jena/Experimental/jena-client/src/main/java/org/apache/jena/client/http/SparqlUpdateEntity.java (added) +++ jena/Experimental/jena-client/src/main/java/org/apache/jena/client/http/SparqlUpdateEntity.java Thu Apr 9 02:22:47 2015 @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.client.http; + +import java.io.IOException ; +import java.io.InputStream ; +import java.io.OutputStream ; + +import org.apache.http.entity.AbstractHttpEntity ; +import org.apache.jena.atlas.io.IndentedWriter ; +import org.apache.jena.atlas.iterator.Action ; +import org.apache.jena.client.Updater ; +import org.apache.jena.riot.WebContent ; + +import com.hp.hpl.jena.sparql.modify.request.UpdateWriter ; +import com.hp.hpl.jena.sparql.serializer.SerializationContext ; + +class SparqlUpdateEntity extends AbstractHttpEntity +{ + private final Action<Updater> action; + private final SerializationContext sCxt; + + SparqlUpdateEntity(Action<Updater> action, SerializationContext sCxt) { + this.action = action; + this.sCxt = sCxt; + this.setContentType(WebContent.contentTypeSPARQLUpdate); + } + + + @Override + public boolean isRepeatable() + { + return false ; + } + + @Override + public long getContentLength() + { + return -1 ; + } + + @Override + public boolean isStreaming() + { + return true ; + } + + @Override + public InputStream getContent() throws IOException, IllegalStateException + { + throw new IllegalStateException("SparqlUpdateEntity is only meant to be used as a Request entity."); + } + + @Override + public void writeTo(OutputStream out) throws IOException + { + IndentedWriter iw = new IndentedWriter(out); + UpdateWriter writer = new UpdateWriter(iw, sCxt); + writer.open(); + try + { + action.apply(new UpdateWriterUpdater(writer)); + } + finally + { + writer.close(); + } + iw.flush(); + } +} Added: jena/Experimental/jena-client/src/main/java/org/apache/jena/client/http/UpdateWriterUpdater.java URL: http://svn.apache.org/viewvc/jena/Experimental/jena-client/src/main/java/org/apache/jena/client/http/UpdateWriterUpdater.java?rev=1672230&view=auto ============================================================================== --- jena/Experimental/jena-client/src/main/java/org/apache/jena/client/http/UpdateWriterUpdater.java (added) +++ jena/Experimental/jena-client/src/main/java/org/apache/jena/client/http/UpdateWriterUpdater.java Thu Apr 9 02:22:47 2015 @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.client.http; + +import java.util.Iterator ; + +import org.apache.jena.client.AbstractUpdater ; + +import com.hp.hpl.jena.sparql.core.Quad ; +import com.hp.hpl.jena.sparql.modify.request.UpdateWriter ; +import com.hp.hpl.jena.update.Update ; + +class UpdateWriterUpdater extends AbstractUpdater implements AutoCloseable +{ + private final UpdateWriter writer; + + public UpdateWriterUpdater(UpdateWriter writer) + { + this.writer = writer; + } + + @Override + protected void doInsert(Iterator<? extends Quad> quadIter) + { + writer.insert(quadIter); + } + + @Override + protected void doDelete(Iterator<? extends Quad> quadIter) + { + writer.delete(quadIter); + } + + @Override + protected void doUpdate(Iterator<? extends Update> updateIter) + { + writer.update(updateIter); + } + + @Override + public void close() + { + writer.close(); + } +} Added: jena/Experimental/jena-client/src/test/java/org/apache/jena/client/graph/AutoCommitQueryExecutionTest.java URL: http://svn.apache.org/viewvc/jena/Experimental/jena-client/src/test/java/org/apache/jena/client/graph/AutoCommitQueryExecutionTest.java?rev=1672230&view=auto ============================================================================== --- jena/Experimental/jena-client/src/test/java/org/apache/jena/client/graph/AutoCommitQueryExecutionTest.java (added) +++ jena/Experimental/jena-client/src/test/java/org/apache/jena/client/graph/AutoCommitQueryExecutionTest.java Thu Apr 9 02:22:47 2015 @@ -0,0 +1,122 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.client.graph; + +import org.junit.Before ; +import org.junit.Test ; + +import com.hp.hpl.jena.query.QueryExecution ; +import com.hp.hpl.jena.query.ReadWrite ; +import com.hp.hpl.jena.sparql.core.Transactional ; +import com.hp.hpl.jena.sparql.core.TransactionalNull ; + +import static org.fest.assertions.Assertions.* ; +import static org.mockito.Mockito.* ; + +public class AutoCommitQueryExecutionTest +{ + private Transactional tx; + private QueryExecution qe; + + @Before + public void setup() + { + tx = spy(new TransactionalNull()); + qe = mock(QueryExecution.class); + } + + + @Test + public void itShouldPerformTransactionOnSelect() + { + try(QueryExecution acqe = new AutoCommitQueryExecution(qe, tx)) + { + assertThat(tx.isInTransaction()).isFalse(); + acqe.execSelect(); + verify(tx).begin(ReadWrite.READ); + } + verify(tx).end(); + assertThat(tx.isInTransaction()).isFalse(); + } + + @Test + public void itShouldPerformTransactionOnAsk() + { + try(QueryExecution acqe = new AutoCommitQueryExecution(qe, tx)) + { + assertThat(tx.isInTransaction()).isFalse(); + acqe.execAsk(); + verify(tx).begin(ReadWrite.READ); + } + verify(tx).end(); + assertThat(tx.isInTransaction()).isFalse(); + } + + @Test + public void itShouldPerformTransactionOnConstruct() + { + try(QueryExecution acqe = new AutoCommitQueryExecution(qe, tx)) + { + assertThat(tx.isInTransaction()).isFalse(); + acqe.execConstruct(); + verify(tx).begin(ReadWrite.READ); + } + verify(tx).end(); + assertThat(tx.isInTransaction()).isFalse(); + } + + @Test + public void itShouldPerformTransactionOnConstructTriples() + { + try(QueryExecution acqe = new AutoCommitQueryExecution(qe, tx)) + { + assertThat(tx.isInTransaction()).isFalse(); + acqe.execConstructTriples(); + verify(tx).begin(ReadWrite.READ); + } + verify(tx).end(); + assertThat(tx.isInTransaction()).isFalse(); + } + + @Test + public void itShouldPerformTransactionOnDescribe() + { + try(QueryExecution acqe = new AutoCommitQueryExecution(qe, tx)) + { + assertThat(tx.isInTransaction()).isFalse(); + acqe.execDescribe(); + verify(tx).begin(ReadWrite.READ); + } + verify(tx).end(); + assertThat(tx.isInTransaction()).isFalse(); + } + + @Test + public void itShouldPerformTransactionOnDescribeTriples() + { + try(QueryExecution acqe = new AutoCommitQueryExecution(qe, tx)) + { + assertThat(tx.isInTransaction()).isFalse(); + acqe.execDescribeTriples(); + verify(tx).begin(ReadWrite.READ); + } + verify(tx).end(); + assertThat(tx.isInTransaction()).isFalse(); + } +} Added: jena/Experimental/jena-client/src/test/java/org/apache/jena/client/graph/DatasetGraphTest.java URL: http://svn.apache.org/viewvc/jena/Experimental/jena-client/src/test/java/org/apache/jena/client/graph/DatasetGraphTest.java?rev=1672230&view=auto ============================================================================== --- jena/Experimental/jena-client/src/test/java/org/apache/jena/client/graph/DatasetGraphTest.java (added) +++ jena/Experimental/jena-client/src/test/java/org/apache/jena/client/graph/DatasetGraphTest.java Thu Apr 9 02:22:47 2015 @@ -0,0 +1,507 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.client.graph; + +import java.io.ByteArrayInputStream ; +import java.util.Iterator ; + +import org.apache.jena.atlas.iterator.Action ; +import org.apache.jena.client.Connection ; +import org.apache.jena.client.DatabaseClient ; +import org.apache.jena.client.DatabaseClientFactory ; +import org.apache.jena.client.QueryStatement ; +import org.apache.jena.client.Updater ; +import org.apache.jena.riot.Lang ; +import org.apache.log4j.ConsoleAppender ; +import org.apache.log4j.Level ; +import org.apache.log4j.Logger ; +import org.apache.log4j.PatternLayout ; +import org.junit.Before ; +import org.junit.Test ; + +import com.hp.hpl.jena.query.Dataset ; +import com.hp.hpl.jena.query.DatasetFactory ; +import com.hp.hpl.jena.query.QueryExecException ; +import com.hp.hpl.jena.query.QueryParseException ; +import com.hp.hpl.jena.query.QuerySolution ; +import com.hp.hpl.jena.query.ReadWrite ; +import com.hp.hpl.jena.query.ResultSet ; +import com.hp.hpl.jena.rdf.model.Model ; +import com.hp.hpl.jena.sparql.ARQException ; +import com.hp.hpl.jena.sparql.JenaTransactionException ; +import com.hp.hpl.jena.sparql.core.DatasetGraph ; +import com.hp.hpl.jena.sparql.core.DatasetGraphFactory ; +import com.hp.hpl.jena.sparql.core.DatasetGraphTrackActive ; +import com.hp.hpl.jena.sparql.core.Quad ; +import com.hp.hpl.jena.sparql.util.Context ; +import com.hp.hpl.jena.update.GraphStore ; + +import static org.fest.assertions.Assertions.* ; +import static org.mockito.Mockito.* ; + +public class DatasetGraphTest +{ + // Set up logging so it doesn't complain + static + { + final Logger rootLogger = Logger.getRootLogger(); + rootLogger.setLevel(Level.WARN); + rootLogger.addAppender(new ConsoleAppender(new PatternLayout("%-6r [%p] %c - %m%n"))); + } + + private CopyOnTxGraphStore dsg; + private DatabaseClient client; + + @Before + public void setup() + { + dsg = spy(new CopyOnTxGraphStore(DatasetGraphFactory.createMem())); + client = DatabaseClientFactory.createLocal(dsg); + } + + private void loadData(final String data) + { + try(Connection conn = client.getConnection()) + { + conn.createUpdateStatement(new Action<Updater>() { + @Override + public void apply(Updater up) + { + up.insert(null, new ByteArrayInputStream(data.getBytes()), Lang.TURTLE); + } + }).execUpdate(); + } + } + + + + @Test(expected = JenaTransactionException.class) + public void itShouldQueryWithNoTransaction() + { + try(Connection conn = DatabaseClientFactory.createLocal(dsg, null).getConnection()) + { + try(QueryStatement qs = conn.createQueryStatement("select * where { ?s ?p ?o }")) + { + qs.execSelect(); + } + } + } + + @Test + public void itShouldDoAutoCommitTransaction() + { + try(Connection conn = client.getConnection()) + { + try(QueryStatement qs = conn.createQueryStatement("select * where { ?s ?p ?o }")) + { + qs.execSelect(); + } + } + verify(dsg).begin(ReadWrite.READ); + verify(dsg).end(); + } + + @Test(expected = ARQException.class) + public void itShouldThrowIfAccessedAfterQueryStatementIsClosed() + { + try(Connection conn = client.getConnection()) + { + ResultSet rs; + try(QueryStatement qs = conn.createQueryStatement("select * where { ?s ?p ?o }")) + { + rs = qs.execSelect(); + } + rs.hasNext(); // bad! + } + } + + @Test + public void itShouldHandleManualReadTransaction() + { + try(Connection conn = client.getConnection()) + { + conn.begin(ReadWrite.READ); + try + { + try(QueryStatement qs = conn.createQueryStatement("select * where { ?s ?p ?o }")) + { + qs.execSelect(); + } + conn.commit(); + } + finally + { + conn.end(); + } + } + verify(dsg).begin(ReadWrite.READ); + verify(dsg).commit(); + verify(dsg).end(); + } + + @Test + public void itShouldHandleManualWriteTransaction() + { + try(Connection conn = client.getConnection()) + { + conn.begin(ReadWrite.WRITE); + try + { + try(QueryStatement qs = conn.createQueryStatement("select * where { ?s ?p ?o }")) + { + qs.execSelect(); + } + conn.commit(); + } + finally + { + conn.end(); + } + } + verify(dsg).begin(ReadWrite.WRITE); + verify(dsg).commit(); + verify(dsg).end(); + } + + @Test + public void itShouldHandleTransactionAbort() + { + try(Connection conn = client.getConnection()) + { + conn.begin(ReadWrite.WRITE); + try + { + try(QueryStatement qs = conn.createQueryStatement("select * where { ?s ?p ?o }")) + { + qs.execSelect(); + } + conn.abort(); + } + finally + { + conn.end(); + } + } + verify(dsg).begin(ReadWrite.WRITE); + verify(dsg).abort(); + verify(dsg).end(); + } + + @Test(expected = QueryExecException.class) + public void itShouldRequireTheCorrectQueryType() + { + try(Connection conn = client.getConnection()) + { + try(QueryStatement qs = conn.createQueryStatement("ask { ?s ?p ?o }")) + { + qs.execSelect(); + } + } + } + + @Test + public void itShouldGetSelectResults() + { + loadData( + "@prefix : <http://example.org/>\n" + + ":a :b :c ." + ); + + try(Connection conn = client.getConnection()) + { + try(QueryStatement qs = conn.createQueryStatement("select * where { ?s ?p ?o }")) + { + ResultSet rs = qs.execSelect(); + assertThat(rs.hasNext()); + QuerySolution row = rs.next(); + assertThat(row.getResource("s").getURI()).isEqualTo("http://example.org/a"); + assertThat(row.getResource("p").getURI()).isEqualTo("http://example.org/b"); + assertThat(row.getResource("o").getURI()).isEqualTo("http://example.org/c"); + } + } + } + + @Test + public void itShouldGetAskResults() + { + loadData( + "@prefix : <http://example.org/>\n" + + ":a :b :c ." + ); + + try(Connection conn = client.getConnection()) + { + try(QueryStatement qs = conn.createQueryStatement("ask { ?s ?p ?o }")) + { + assertThat(qs.execAsk()).isTrue(); + } + } + } + + @Test + public void itShouldGetConstructResults() + { + loadData( + "@prefix : <http://example.org/>\n" + + ":a :b :c ." + ); + + try(Connection conn = client.getConnection()) + { + try(QueryStatement qs = conn.createQueryStatement("construct where { ?s ?p ?o }")) + { + Model m = qs.execConstruct(); + assertThat(m.size()).isEqualTo(1); + } + } + } + + @Test + public void itShouldGetDescribeResults() + { + loadData( + "@prefix : <http://example.org/>\n" + + ":a :b :c ." + ); + + try(Connection conn = client.getConnection()) + { + try(QueryStatement qs = conn.createQueryStatement("describe <http://example.org/a>")) + { + Model m = qs.execDescribe(); + assertThat(m.size()).isEqualTo(1); + } + } + } + + @Test(expected = IllegalStateException.class) + public void itShouldOnlyAllowOneOperationAtATime() + { + try(Connection conn = client.getConnection()) + { + conn.begin(ReadWrite.WRITE); + try + { + conn.createQueryStatement("select * where { ?s ?p ?o }"); + conn.createUpdateStatement("prefix : <http://example.org/>\n insert data { :a :b :c }"); + conn.commit(); + } + finally + { + conn.end(); + } + } + } + + + // Update tests + + @Test + public void itShouldCommit() + { + try(Connection conn = client.getConnection()) + { + conn.begin(ReadWrite.WRITE); + try + { + assertThat(conn.createQueryStatement("ask { ?s ?p ?o }").execAsk()).isFalse(); + conn.createUpdateStatement("prefix : <http://example.org/>\n insert data { :a :b :c }").execUpdate();; + assertThat(conn.createQueryStatement("ask { ?s ?p ?o }").execAsk()).isTrue(); + conn.commit(); + } + finally + { + conn.end(); + } + assertThat(conn.createQueryStatement("ask { ?s ?p ?o }").execAsk()).isTrue(); + } + } + + @Test + public void itShouldRollbackOnAbort() + { + try(Connection conn = client.getConnection()) + { + conn.begin(ReadWrite.WRITE); + try + { + assertThat(conn.createQueryStatement("ask { ?s ?p ?o }").execAsk()).isFalse(); + conn.createUpdateStatement("prefix : <http://example.org/>\n insert data { :a :b :c }").execUpdate();; + assertThat(conn.createQueryStatement("ask { ?s ?p ?o }").execAsk()).isTrue(); + conn.abort(); + } + finally + { + conn.end(); + } + assertThat(conn.createQueryStatement("ask { ?s ?p ?o }").execAsk()).isFalse(); + } + verify(dsg).abort(); + } + + @Test + public void itShouldRollbackOnException() + { + try(Connection conn = client.getConnection()) + { + conn.begin(ReadWrite.WRITE); + try + { + assertThat(conn.createQueryStatement("ask { ?s ?p ?o }").execAsk()).isFalse(); + conn.createUpdateStatement("prefix : <http://example.org/>\n insert data { :a :b :c }").execUpdate();; + assertThat(conn.createQueryStatement("ask { ?s ?p ?o }").execAsk()).isTrue(); + + conn.createQueryStatement("monkey pod").execAsk(); + conn.commit(); + } + catch (QueryParseException e) + { + // Ignore + } + finally + { + conn.end(); + } + assertThat(conn.createQueryStatement("ask { ?s ?p ?o }").execAsk()).isFalse(); + } + } + + + + /** + * Doesn't provide thread-safe transactions, but otherwise does honor transaction semantics and ensures that all operations occur inside of a transaction boundary + */ + private static class CopyOnTxGraphStore extends DatasetGraphTrackActive implements GraphStore + { + private final DatasetGraph dsg; + private DatasetGraph dsgWork; + private ReadWrite txState; + + public CopyOnTxGraphStore(DatasetGraph dsg) + { + this.dsg = dsg; + this.txState = null; + } + + @Override + public DatasetGraph get() + { + if (!isInTransaction()) throw new JenaTransactionException("Not in a transaction"); + return dsgWork; + } + + @Override + public Context getContext() + { + return dsg.getContext(); + } + + @Override + protected void checkActive() + { + if (!isInTransaction()) throw new JenaTransactionException("Not in a transaction"); + } + + @Override + protected void checkNotActive() + { + if (isInTransaction()) throw new JenaTransactionException("Currently in a transaction"); + } + + @Override + public boolean isInTransaction() + { + return (null != txState); + } + + private static void copy(DatasetGraph source, DatasetGraph dest) + { + if (source != dest) + { + dest.clear(); + for(Iterator<Quad> it = source.find(null); it.hasNext(); ) + { + dest.add(it.next()); + } + } + } + + @Override + protected void _begin(ReadWrite readWrite) + { + txState = readWrite; + if (ReadWrite.WRITE.equals(readWrite)) + { + dsgWork = DatasetGraphFactory.createMem(); + copy(dsg, dsgWork); + } + else + { + dsgWork = dsg; + } + } + + @Override + protected void _commit() + { + txState = null; + copy(dsgWork, dsg); + dsgWork = null; + } + + @Override + protected void _abort() + { + txState = null; + dsgWork = null; + } + + @Override + protected void _end() + { + if (null != txState) + { + _abort(); + } + } + + @Override + protected void _close() + { + _end(); + } + + @Override + public Dataset toDataset() + { + checkActive(); + return DatasetFactory.create(dsgWork); + } + + @Override + public void startRequest() + { + } + + @Override + public void finishRequest() + { + } + } +}
