Java tutorial
/* * 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 rdfconnection; import static java.util.Objects.requireNonNull; import java.io.File; import java.io.InputStream; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Supplier; import org.apache.http.HttpEntity; import org.apache.http.entity.EntityTemplate; import org.apache.jena.atlas.io.IO; import org.apache.jena.atlas.web.HttpException; import org.apache.jena.atlas.web.TypedInputStream; import org.apache.jena.atlas.web.auth.HttpAuthenticator; import org.apache.jena.graph.Graph; import org.apache.jena.query.*; import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.ModelFactory; import org.apache.jena.riot.*; import org.apache.jena.riot.web.HttpCaptureResponse; import org.apache.jena.riot.web.HttpOp; import org.apache.jena.riot.web.HttpResponseLib; import org.apache.jena.sparql.ARQException; import org.apache.jena.sparql.core.DatasetGraph; import org.apache.jena.sparql.core.Transactional; import org.apache.jena.system.Txn; import org.apache.jena.update.UpdateExecutionFactory; import org.apache.jena.update.UpdateProcessor; import org.apache.jena.update.UpdateRequest; import org.apache.jena.web.HttpSC; public class RDFConnectionRemote implements RDFConnection { private static final String fusekiDftSrvQuery = "sparql"; private static final String fusekiDftSrvUpdate = "update"; private static final String fusekiDftSrvGSP = "data"; private boolean isOpen = true; private final String destination; private final String svcQuery; private final String svcUpdate; private final String svcGraphStore; private HttpAuthenticator authenticator = null; /** Create connection, using URL of the datset and default service names */ public RDFConnectionRemote(String destination) { this(requireNonNull(destination), fusekiDftSrvQuery, fusekiDftSrvUpdate, fusekiDftSrvGSP); } /** Create connection, using full URLs for services */ public RDFConnectionRemote(String sQuery, String sUpdate, String sGSP) { this(null, sQuery, sUpdate, sGSP); } /** Create connection, using URL of the datset and short names for the services */ public RDFConnectionRemote(String destination, String sQuery, String sUpdate, String sGSP) { this.destination = destination; this.svcQuery = formServiceURL(destination, sQuery); this.svcUpdate = formServiceURL(destination, sUpdate); this.svcGraphStore = formServiceURL(destination, sGSP); } private static String formServiceURL(String destination, String srvEndpoint) { if (destination == null) return srvEndpoint; String dest = destination; if (dest.endsWith("/")) dest = dest.substring(0, dest.length() - 1); return dest + "/" + srvEndpoint; } @Override public QueryExecution query(Query query) { checkQuery(); return exec(() -> QueryExecutionFactory.createServiceRequest(svcQuery, query)); } @Override public void update(UpdateRequest update) { checkUpdate(); UpdateProcessor proc = UpdateExecutionFactory.createRemote(update, svcUpdate); exec(() -> proc.execute()); } @Override public Model fetch(String graphName) { checkGSP(); String url = RDFConn.urlForGraph(svcGraphStore, graphName); Graph graph = fetch$(url); return ModelFactory.createModelForGraph(graph); } @Override public Model fetch() { checkGSP(); return fetch(null); } private Graph fetch$(String url) { HttpCaptureResponse<Graph> graph = HttpResponseLib.graphHandler(); exec(() -> HttpOp.execHttpGet(url, WebContent.defaultGraphAcceptHeader, graph, this.authenticator)); return graph.get(); } @Override public void load(String graph, String file) { checkGSP(); upload(graph, file, false); } @Override public void load(String file) { checkGSP(); upload(null, file, false); } @Override public void load(Model model) { doPutPost(model, null, false); } @Override public void load(String graphName, Model model) { doPutPost(model, graphName, false); // Graph graph = model.getGraph() ; // HttpEntity entity = graphToHttpEntity(graph) ; // String url = RDFConn.urlForGraph(svcGraphStore, graphName) ; // exec(()->HttpOp.execHttpPost(url, entity, null, null, this.authenticator)) ; } @Override public void put(String graph, String file) { checkGSP(); upload(graph, file, true); } @Override public void put(String file) { checkGSP(); upload(null, file, true); } @Override public void put(String graphName, Model model) { checkGSP(); doPutPost(model, graphName, true); } @Override public void put(Model model) { checkGSP(); doPutPost(model, null, true); } private void upload(String graph, String file, boolean replace) { // if triples Lang lang = RDFLanguages.filenameToLang(file); if (RDFLanguages.isQuads(lang)) throw new ARQException("Can't load quads into a graph"); if (!RDFLanguages.isTriples(lang)) throw new ARQException("Not an RDF format: " + file + " (lang=" + lang + ")"); String url = RDFConn.urlForGraph(svcGraphStore, graph); doPutPost(url, file, lang, replace); } private void doPutPost(String url, String file, Lang lang, boolean replace) { File f = new File(file); long length = f.length(); InputStream source = IO.openFile(file); // Charset. exec(() -> { if (replace) HttpOp.execHttpPut(url, lang.getContentType().getContentType(), source, length, null, null, this.authenticator); else HttpOp.execHttpPost(url, lang.getContentType().getContentType(), source, length, null, null, null, null, this.authenticator); }); } private void doPutPost(Model model, String name, boolean replace) { String url = RDFConn.urlForGraph(svcGraphStore, name); exec(() -> { Graph graph = model.getGraph(); if (replace) HttpOp.execHttpPut(url, graphToHttpEntity(graph), null, null, this.authenticator); else HttpOp.execHttpPost(url, graphToHttpEntity(graph), null, null, this.authenticator); }); } @Override public void delete(String graph) { checkGSP(); String url = RDFConn.urlForGraph(svcGraphStore, graph); exec(() -> HttpOp.execHttpDelete(url)); } @Override public void delete() { checkGSP(); delete(null); } @Override public Dataset fetchDataset() { if (destination == null) throw new ARQException("Dataset operations not available - no dataset URL provided"); Dataset ds = DatasetFactory.createTxnMem(); Txn.execWrite(ds, () -> { TypedInputStream s = exec(() -> HttpOp.execHttpGet(destination, WebContent.defaultDatasetAcceptHeader)); Lang lang = RDFLanguages.contentTypeToLang(s.getContentType()); RDFDataMgr.read(ds, s, lang); }); return ds; } @Override public void loadDataset(String file) { if (destination == null) throw new ARQException("Dataset operations not available - no dataset URl provided"); doPutPostDataset(file, false); } @Override public void loadDataset(Dataset dataset) { if (destination == null) throw new ARQException("Dataset operations not available - no dataset URl provided"); doPutPostDataset(dataset, false); } @Override public void putDataset(String file) { if (destination == null) throw new ARQException("Dataset operations not available - no dataset URl provided"); doPutPostDataset(file, true); } @Override public void putDataset(Dataset dataset) { if (destination == null) throw new ARQException("Dataset operations not available - no dataset URl provided"); doPutPostDataset(dataset, true); } private void doPutPostDataset(String file, boolean replace) { Lang lang = RDFLanguages.filenameToLang(file); File f = new File(file); long length = f.length(); exec(() -> { InputStream source = IO.openFile(file); if (replace) HttpOp.execHttpPut(destination, lang.getContentType().getContentType(), source, length, null, null, this.authenticator); else HttpOp.execHttpPost(destination, lang.getContentType().getContentType(), source, length, null, null, null, null, this.authenticator); }); } private void doPutPostDataset(Dataset dataset, boolean replace) { exec(() -> { DatasetGraph dsg = dataset.asDatasetGraph(); if (replace) HttpOp.execHttpPut(destination, datasetToHttpEntity(dsg), null, null, this.authenticator); else HttpOp.execHttpPost(destination, datasetToHttpEntity(dsg), null, null, this.authenticator); }); } private void checkQuery() { checkOpen(); if (svcQuery == null) throw new ARQException("No query service defined for this RDFConnection"); } private void checkUpdate() { checkOpen(); if (svcUpdate == null) throw new ARQException("No update service defined for this RDFConnection"); } private void checkGSP() { checkOpen(); if (svcGraphStore == null) throw new ARQException("No SPARQL Graph Store service defined for this RDFConnection"); } private void checkDataset() { checkOpen(); if (destination == null) throw new ARQException("Dataset operations not available - no dataset URL provided"); } private void checkOpen() { if (!isOpen) throw new ARQException("closed"); } @Override public void close() { isOpen = false; } @Override public boolean isClosed() { return !isOpen; } /** Create an HttpEntity for the graph */ protected HttpEntity graphToHttpEntity(Graph graph) { return graphToHttpEntity(graph, RDFFormat.NTRIPLES); } /** Create an HttpEntity for the graph */ protected HttpEntity graphToHttpEntity(Graph graph, RDFFormat syntax) { EntityTemplate entity = new EntityTemplate((out) -> RDFDataMgr.write(out, graph, syntax)); String ct = syntax.getLang().getContentType().getContentType(); entity.setContentType(ct); return entity; } /** Create an HttpEntity for the dataset */ protected HttpEntity datasetToHttpEntity(DatasetGraph dataset) { return datasetToHttpEntity(dataset, RDFFormat.NQUADS); } /** Create an HttpEntity for the dataset */ protected HttpEntity datasetToHttpEntity(DatasetGraph dataset, RDFFormat syntax) { EntityTemplate entity = new EntityTemplate((out) -> RDFDataMgr.write(out, dataset, syntax)); String ct = syntax.getLang().getContentType().getContentType(); entity.setContentType(ct); return entity; } /** Convert HTTP status codes to exceptions */ static void exec(Runnable action) { try { action.run(); } catch (HttpException ex) { handleHttpException(ex, false); } } /** Convert HTTP status codes to exceptions */ static <X> X exec(Supplier<X> action) { try { return action.get(); } catch (HttpException ex) { handleHttpException(ex, true); return null; } } private static void handleHttpException(HttpException ex, boolean ignore404) { if (ex.getResponseCode() == HttpSC.NOT_FOUND_404 && ignore404) return; throw ex; } /** Engine for the transaction lifecycle. * MR+SW */ static class TxnLifecycle implements Transactional { // MR+SW policy. private ReentrantLock lock = new ReentrantLock(); private ThreadLocal<ReadWrite> mode = ThreadLocal.withInitial(() -> null); @Override public void begin(ReadWrite readWrite) { if (readWrite == ReadWrite.WRITE) lock.lock(); mode.set(readWrite); } @Override public void commit() { if (mode.get() == ReadWrite.WRITE) lock.unlock(); mode.set(null); } @Override public void abort() { if (mode.get() == ReadWrite.WRITE) lock.unlock(); mode.set(null); } @Override public boolean isInTransaction() { return mode.get() != null; } @Override public void end() { ReadWrite rw = mode.get(); if (rw == null) return; if (rw == ReadWrite.WRITE) { abort(); return; } mode.set(null); } } private TxnLifecycle inner = new TxnLifecycle(); @Override public void begin(ReadWrite readWrite) { inner.begin(readWrite); } @Override public void commit() { inner.commit(); } @Override public void abort() { inner.abort(); } @Override public boolean isInTransaction() { return inner.isInTransaction(); } @Override public void end() { inner.end(); } }