VIVO-1025 Improvements to the Virtuoso driver.

This commit is contained in:
Jim Blake 2015-04-27 10:36:50 -04:00
parent 586fa09666
commit 0b79dfdf01
5 changed files with 179 additions and 45 deletions

View file

@ -27,6 +27,7 @@ import edu.cornell.mannlib.vitro.webapp.rdfservice.ChangeSet;
import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService; import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService;
import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFServiceException; import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFServiceException;
import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.RDFServiceUtils; import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.RDFServiceUtils;
import edu.cornell.mannlib.vitro.webapp.utils.logging.ToString;
public class RDFServiceGraphBulkUpdater implements BulkUpdateHandler { public class RDFServiceGraphBulkUpdater implements BulkUpdateHandler {
private static final Log log = LogFactory.getLog(RDFServiceGraphBulkUpdater.class); private static final Log log = LogFactory.getLog(RDFServiceGraphBulkUpdater.class);
@ -203,7 +204,8 @@ public class RDFServiceGraphBulkUpdater implements BulkUpdateHandler {
private static void removeAll(Graph g, Node s, Node p, Node o) private static void removeAll(Graph g, Node s, Node p, Node o)
{ {
log.debug("removeAll: g=" + g + ", s=" + s + ", p=" + p + ", o=" + o); log.debug("removeAll: g=" + ToString.graphToString(g) + ", s=" + s
+ ", p=" + p + ", o=" + o);
if (!(g instanceof RDFServiceGraph)) { if (!(g instanceof RDFServiceGraph)) {
removeAllTripleByTriple(g, s, p, o); removeAllTripleByTriple(g, s, p, o);
return; return;

View file

@ -70,7 +70,7 @@ public class RDFServiceSparql extends RDFServiceImpl implements RDFService {
private static final Log log = LogFactory.getLog(RDFServiceImpl.class); private static final Log log = LogFactory.getLog(RDFServiceImpl.class);
protected String readEndpointURI; protected String readEndpointURI;
protected String updateEndpointURI; protected String updateEndpointURI;
private CloseableHttpClient httpClient; protected CloseableHttpClient httpClient;
// the number of triples to be // the number of triples to be
private static final int CHUNK_SIZE = 1000; // added/removed in a single private static final int CHUNK_SIZE = 1000; // added/removed in a single
// SPARQL UPDATE // SPARQL UPDATE
@ -467,7 +467,7 @@ public class RDFServiceSparql extends RDFServiceImpl implements RDFService {
try { try {
int statusCode = response.getStatusLine().getStatusCode(); int statusCode = response.getStatusLine().getStatusCode();
if (statusCode > 399) { if (statusCode > 399) {
log.error("response " + statusCode + " to update. \n"); log.error("response " + response.getStatusLine() + " to update. \n");
//log.debug("update string: \n" + updateString); //log.debug("update string: \n" + updateString);
throw new RDFServiceException("Unable to perform SPARQL UPDATE"); throw new RDFServiceException("Unable to perform SPARQL UPDATE");
} }

View file

@ -7,41 +7,132 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.BasicCredentialsProvider;
import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFServiceException; import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFServiceException;
import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.sparql.RDFServiceSparql; import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.sparql.RDFServiceSparql;
/** /**
* For now, at least, it is just like an RDFServiceSparql except for: * For now, at least, it is just like an RDFServiceSparql except:
* *
* A small change in the syntax of an UPDATE request. * A username and password are required. These should refer to a Virtuoso user
* that posesses the SPARQL_UPDATE role.
*
* The endpoint URI and the update endpoint URI are derived from the base URI.
* You provide: http://localhost:8890
* endpoint is: http://localhost:8890/sparql/
* update is: http://localhost:8890/DAV/home/username/rdf_sink/vitro_update
*
* A change in the syntax of an UPDATE request: "INSERT DATA" becomes "INSERT".
* This fixes a problem with inserting blank nodes.
*
* The HTTP request is equipped with the username and password, to answer a
* challenge for basic authentication.
* *
* Allow for the nonNegativeInteger bug when checking to see whether a graph has * Allow for the nonNegativeInteger bug when checking to see whether a graph has
* changed. * changed.
*/ */
public class RDFServiceVirtuoso extends RDFServiceSparql { public class RDFServiceVirtuoso extends RDFServiceSparql {
private static final Log log = LogFactory.getLog(RDFServiceVirtuoso.class);
public RDFServiceVirtuoso(String readEndpointURI, String updateEndpointURI, private final String username;
String defaultWriteGraphURI) { private final String password;
super(readEndpointURI, updateEndpointURI, defaultWriteGraphURI);
public RDFServiceVirtuoso(String baseURI, String username, String password) {
super(figureReadEndpointUri(baseURI), figureUpdateEndpointUri(baseURI,
username));
this.username = username;
this.password = password;
} }
public RDFServiceVirtuoso(String readEndpointURI, String updateEndpointURI) { private static String figureReadEndpointUri(String baseUri) {
super(readEndpointURI, updateEndpointURI); return noTrailingSlash(baseUri) + "/sparql/";
} }
public RDFServiceVirtuoso(String endpointURI) { private static String figureUpdateEndpointUri(String baseUri,
super(endpointURI); String username) {
return noTrailingSlash(baseUri) + "/DAV/home/" + username
+ "/rdf_sink/vitro_update";
}
private static String noTrailingSlash(String uri) {
return uri.endsWith("/") ? uri.substring(0, uri.length() - 1) : uri;
} }
@Override @Override
protected void executeUpdate(String updateString) protected void executeUpdate(String updateString)
throws RDFServiceException { throws RDFServiceException {
super.executeUpdate(updateString.replace("INSERT DATA", "INSERT")); updateString = tweakUpdateStringSyntax(updateString);
log.debug("UPDATE STRING: " + updateString);
try {
CloseableHttpResponse response = httpClient.execute(
createHttpRequest(updateString), createHttpContext());
try {
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode > 399) {
log.error("response " + response.getStatusLine()
+ " to update. \n");
InputStream content = response.getEntity().getContent();
for (String line : IOUtils.readLines(content)) {
log.error("response-line >>" + line);
}
throw new RDFServiceException(
"Unable to perform SPARQL UPDATE: status code = "
+ statusCode);
}
} finally {
response.close();
}
} catch (Exception e) {
log.error("Failed to update: " + updateString, e);
throw new RDFServiceException(
"Unable to perform change set update", e);
}
}
private String tweakUpdateStringSyntax(String updateString) {
if (updateString.startsWith("INSERT DATA")) {
return updateString.replaceFirst("INSERT DATA", "INSERT");
}
return updateString;
}
// TODO entity.setContentType("application/sparql-query");
private HttpPost createHttpRequest(String updateString) {
HttpPost meth = new HttpPost(updateEndpointURI);
meth.addHeader("Content-Type", "application/sparql-query");
meth.setEntity(new StringEntity(updateString, "UTF-8"));
return meth;
} }
/** /**
* Virtuoso has a bug, which it shares with TDB: if given a literal of type * We need an HttpContext that will provide username and password in
* response to a basic authentication challenge.
*/
private HttpClientContext createHttpContext() {
CredentialsProvider provider = new BasicCredentialsProvider();
provider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(
username, password));
HttpClientContext context = HttpClientContext.create();
context.setCredentialsProvider(provider);
return context;
}
/**
* Virtuoso has a bug which it shares with TDB: if given a literal of type
* xsd:nonNegativeInteger, it stores a literal of type xsd:integer. * xsd:nonNegativeInteger, it stores a literal of type xsd:integer.
* *
* To determine whether this serialized graph is equivalent to what is * To determine whether this serialized graph is equivalent to what is

View file

@ -73,22 +73,20 @@ public class ContentTripleSourceSPARQL extends ContentTripleSource {
@Override @Override
public void startup(Application application, ComponentStartupStatus ss) { public void startup(Application application, ComponentStartupStatus ss) {
this.rdfServiceFactory = createRDFServiceFactory(createRDFService(ss, this.rdfServiceFactory = createRDFServiceFactory(createRDFService(ss));
endpointURI, updateEndpointURI));
this.rdfService = this.rdfServiceFactory.getRDFService(); this.rdfService = this.rdfServiceFactory.getRDFService();
this.dataset = createDataset(); this.dataset = createDataset();
this.modelMaker = createModelMaker(); this.modelMaker = createModelMaker();
} }
protected RDFService createRDFService(ComponentStartupStatus ss, protected RDFService createRDFService(ComponentStartupStatus ss) {
String endpoint, String updateEndpoint) { if (updateEndpointURI == null) {
if (updateEndpoint == null) { ss.info("Using endpoint at " + endpointURI);
ss.info("Using endpoint at " + endpoint); return new RDFServiceSparql(endpointURI);
return new RDFServiceSparql(endpoint);
} else { } else {
ss.info("Using read endpoint at " + endpoint ss.info("Using read endpoint at " + endpointURI
+ " and update endpoint at " + updateEndpoint); + " and update endpoint at " + updateEndpointURI);
return new RDFServiceSparql(endpoint, updateEndpoint); return new RDFServiceSparql(endpointURI, updateEndpointURI);
} }
} }

View file

@ -7,36 +7,79 @@ import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService;
import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.virtuoso.RDFServiceVirtuoso; import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.virtuoso.RDFServiceVirtuoso;
import edu.cornell.mannlib.vitro.webapp.triplesource.impl.sparql.ContentTripleSourceSPARQL; import edu.cornell.mannlib.vitro.webapp.triplesource.impl.sparql.ContentTripleSourceSPARQL;
import edu.cornell.mannlib.vitro.webapp.utils.configuration.Property; import edu.cornell.mannlib.vitro.webapp.utils.configuration.Property;
import edu.cornell.mannlib.vitro.webapp.utils.configuration.Validation;
import edu.cornell.mannlib.vitro.webapp.utils.logging.ToString;
/** /**
* So far, it's just like a ContentTripleSourceSPARQL but it uses an instance of * So far, it's just like a ContentTripleSourceSPARQL but it uses an instance of
* RDFServiceVirtuoso. * RDFServiceVirtuoso.
*/ */
public class ContentTripleSourceVirtuoso extends ContentTripleSourceSPARQL { public class ContentTripleSourceVirtuoso extends ContentTripleSourceSPARQL {
private String baseUri;
private String username;
private String password;
@Override @Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasBaseURI")
@Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasEndpointURI") public void setBaseUri(String uri) {
public void setEndpointURI(String eUri) { if (baseUri == null) {
super.setEndpointURI(eUri); baseUri = uri;
}
@Override
@Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasUpdateEndpointURI")
public void setUpdateEndpointURI(String ueUri) {
super.setUpdateEndpointURI(ueUri);
}
@Override
protected RDFService createRDFService(ComponentStartupStatus ss,
String endpoint, String updateEndpoint) {
if (updateEndpoint == null) {
ss.info("Using endpoint at " + endpoint);
return new RDFServiceVirtuoso(endpoint);
} else { } else {
ss.info("Using read endpoint at " + endpoint throw new IllegalStateException(
+ " and update endpoint at " + updateEndpoint); "Configuration includes multiple instances of BaseURI: "
return new RDFServiceVirtuoso(endpoint, updateEndpoint); + baseUri + ", and " + uri);
} }
} }
@Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasUsername")
public void setUsername(String user) {
if (username == null) {
username = user;
} else {
throw new IllegalStateException(
"Configuration includes multiple instances of Username: "
+ username + ", and " + user);
}
}
@Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasPassword")
public void setPassword(String pass) {
if (password == null) {
password = pass;
} else {
throw new IllegalStateException(
"Configuration includes multiple instances of Password: "
+ password + ", and " + pass);
}
}
@Override
@Validation
public void validate() throws Exception {
if (baseUri == null) {
throw new IllegalStateException(
"Configuration did not include a BaseURI.");
}
if (username == null) {
throw new IllegalStateException(
"Configuration did not include a Username.");
}
if (password == null) {
throw new IllegalStateException(
"Configuration did not include a Password.");
}
}
@Override
protected RDFService createRDFService(ComponentStartupStatus ss) {
ss.info("Using Virtuoso at " + baseUri + ", authenticating as "
+ username);
return new RDFServiceVirtuoso(baseUri, username, password);
}
@Override
public String toString() {
return "ContentTripleSourceVirtuoso[" + ToString.hashHex(this)
+ ", baseUri=" + baseUri + ", username=" + username + "]";
}
} }