Skip to content

Commit

Permalink
GH-1900 Create a SHACL exception in the ProtocolExceptionResolver ins…
Browse files Browse the repository at this point in the history
…tead of relying on a generic exception with a message

Signed-off-by: Håvard Ottestad <hmottestad@gmail.com>
  • Loading branch information
hmottestad committed Mar 30, 2020
1 parent 90d6e16 commit 3fab174
Show file tree
Hide file tree
Showing 6 changed files with 187 additions and 38 deletions.
@@ -0,0 +1,25 @@
/*******************************************************************************
* Copyright (c) 2019 Eclipse RDF4J contributors.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Distribution License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*******************************************************************************/

package org.eclipse.rdf4j.http.client;

import org.eclipse.rdf4j.model.Model;
import org.eclipse.rdf4j.model.Resource;
import org.eclipse.rdf4j.model.impl.DynamicModelFactory;

public interface ModelInterface {

default Model asModel() {
return asModel(new DynamicModelFactory().createEmptyModel());
}

Model asModel(Model model);

Resource getId();

}
@@ -0,0 +1,36 @@
/*******************************************************************************
* Copyright (c) 2019 Eclipse RDF4J contributors.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Distribution License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*******************************************************************************/

package org.eclipse.rdf4j.http.client;

import org.eclipse.rdf4j.model.Model;
import org.eclipse.rdf4j.model.vocabulary.SHACL;
import org.eclipse.rdf4j.rio.RDFFormat;

import java.io.StringReader;

public class RemoteShaclSailValidationException extends Exception {

private final RemoteValidation remoteValidation;

public RemoteShaclSailValidationException(StringReader stringReader, String s, RDFFormat format) {
remoteValidation = new RemoteValidation(stringReader, s, format);
}

/**
* @return A Model containing the validation report as specified by the SHACL Recommendation
*/
@SuppressWarnings("WeakerAccess")
public Model validationReportAsModel() {
Model model = remoteValidation.asModel();
model.setNamespace(SHACL.PREFIX, SHACL.NAMESPACE);
return model;

}

}
@@ -0,0 +1,48 @@
package org.eclipse.rdf4j.http.client;

import org.eclipse.rdf4j.model.Model;
import org.eclipse.rdf4j.model.Resource;
import org.eclipse.rdf4j.rio.RDFFormat;
import org.eclipse.rdf4j.rio.Rio;

import java.io.IOException;
import java.io.StringReader;

public class RemoteValidation implements ModelInterface {

StringReader stringReader;
String baseUri;
RDFFormat format;

Model model;

public RemoteValidation(StringReader stringReader, String baseUri, RDFFormat format) {
this.stringReader = stringReader;
this.baseUri = baseUri;
this.format = format;
}

@Override
public Model asModel(Model model) {
model.addAll(asModel());
return model;
}

@Override
public Model asModel() {
if (model == null) {
try {
model = Rio.parse(stringReader, baseUri, format);
} catch (IOException e) {
throw new RuntimeException(e);
}
}

return model;
}

@Override
public Resource getId() {
return null;
}
}
Expand Up @@ -7,23 +7,6 @@
*******************************************************************************/
package org.eclipse.rdf4j.http.client;

import static org.eclipse.rdf4j.http.protocol.Protocol.ACCEPT_PARAM_NAME;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.nio.charset.IllegalCharsetNameException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;

import org.apache.commons.io.IOUtils;
import org.apache.http.Header;
import org.apache.http.HeaderElement;
Expand Down Expand Up @@ -102,6 +85,24 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringReader;
import java.net.HttpURLConnection;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.nio.charset.IllegalCharsetNameException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;

import static org.eclipse.rdf4j.http.protocol.Protocol.ACCEPT_PARAM_NAME;

/**
* The SPARQLProtocolSession provides low level HTTP methods for communication with SPARQL endpoints. All methods are
* compliant to the <a href="https://www.w3.org/TR/sparql11-protocol/">SPARQL 1.1 Protocol W3C Recommendation</a>.
Expand All @@ -113,7 +114,7 @@
* <p/>
* Functionality specific to the RDF4J HTTP protocol can be found in {@link RDF4JProtocolSession} (which is used by
* HTTPRepository).
*
*
* @author Herko ter Horst
* @author Arjohn Kampman
* @author Andreas Schwarte
Expand Down Expand Up @@ -250,7 +251,7 @@ protected void setUpdateURL(String updateURL) {
/**
* Sets the preferred format for encoding tuple query results. The {@link TupleQueryResultFormat#BINARY binary}
* format is preferred by default.
*
*
* @param format The preferred {@link TupleQueryResultFormat}, or <tt>null</tt> to indicate no specific format is
* preferred.
*/
Expand All @@ -260,7 +261,7 @@ public void setPreferredTupleQueryResultFormat(TupleQueryResultFormat format) {

/**
* Gets the preferred {@link TupleQueryResultFormat} for encoding tuple query results.
*
*
* @return The preferred format, of <tt>null</tt> if no specific format is preferred.
*/
public TupleQueryResultFormat getPreferredTupleQueryResultFormat() {
Expand All @@ -270,7 +271,7 @@ public TupleQueryResultFormat getPreferredTupleQueryResultFormat() {
/**
* Sets the preferred format for encoding RDF documents. The {@link RDFFormat#TURTLE Turtle} format is preferred by
* default.
*
*
* @param format The preferred {@link RDFFormat}, or <tt>null</tt> to indicate no specific format is preferred.
*/
public void setPreferredRDFFormat(RDFFormat format) {
Expand All @@ -279,7 +280,7 @@ public void setPreferredRDFFormat(RDFFormat format) {

/**
* Gets the preferred {@link RDFFormat} for encoding RDF documents.
*
*
* @return The preferred format, of <tt>null</tt> if no specific format is preferred.
*/
public RDFFormat getPreferredRDFFormat() {
Expand All @@ -289,7 +290,7 @@ public RDFFormat getPreferredRDFFormat() {
/**
* Sets the preferred format for encoding boolean query results. The {@link BooleanQueryResultFormat#TEXT binary}
* format is preferred by default.
*
*
* @param format The preferred {@link BooleanQueryResultFormat}, or <tt>null</tt> to indicate no specific format is
* preferred.
*/
Expand All @@ -299,7 +300,7 @@ public void setPreferredBooleanQueryResultFormat(BooleanQueryResultFormat format

/**
* Gets the preferred {@link BooleanQueryResultFormat} for encoding boolean query results.
*
*
* @return The preferred format, of <tt>null</tt> if no specific format is preferred.
*/
public BooleanQueryResultFormat getPreferredBooleanQueryResultFormat() {
Expand All @@ -308,7 +309,7 @@ public BooleanQueryResultFormat getPreferredBooleanQueryResultFormat() {

/**
* Set the username and password for authentication with the remote server.
*
*
* @param username the username
* @param password the password
*/
Expand Down Expand Up @@ -450,7 +451,7 @@ public boolean sendBooleanQuery(QueryLanguage ql, String query, String baseURI,

/**
* Get the additional HTTP headers which will be used
*
*
* @return a read-only view of the additional HTTP headers which will be included in every request to the server.
*/
public Map<String, String> getAdditionalHttpHeaders() {
Expand All @@ -460,7 +461,7 @@ public Map<String, String> getAdditionalHttpHeaders() {
/**
* Set additional HTTP headers to be included in every request to the server, which may be required for certain
* unusual server configurations.
*
*
* @param additionalHttpHeaders a map containing pairs of header names and values. May be null
*/
public void setAdditionalHttpHeaders(Map<String, String> additionalHttpHeaders) {
Expand All @@ -479,8 +480,9 @@ protected HttpUriRequest getQueryMethod(QueryLanguage ql, String query, String b
String queryUrlWithParams;
try {
URIBuilder urib = new URIBuilder(getQueryURL());
for (NameValuePair nvp : queryParams)
for (NameValuePair nvp : queryParams) {
urib.addParameter(nvp.getName(), nvp.getValue());
}
queryUrlWithParams = urib.toString();
} catch (URISyntaxException e) {
throw new AssertionError(e);
Expand All @@ -506,7 +508,7 @@ protected HttpUriRequest getQueryMethod(QueryLanguage ql, String query, String b

/**
* Return whether the provided query should use POST (otherwise use GET)
*
*
* @param fullQueryUrl the complete URL, including hostname and all HTTP query parameters
*/
protected boolean shouldUsePost(String fullQueryUrl) {
Expand All @@ -530,8 +532,9 @@ protected HttpUriRequest getUpdateMethod(QueryLanguage ql, String update, String
method.setEntity(new UrlEncodedFormEntity(queryParams, UTF8));

if (this.additionalHttpHeaders != null) {
for (Map.Entry<String, String> additionalHeader : additionalHttpHeaders.entrySet())
for (Map.Entry<String, String> additionalHeader : additionalHttpHeaders.entrySet()) {
method.addHeader(additionalHeader.getKey(), additionalHeader.getValue());
}
}

return method;
Expand Down Expand Up @@ -729,7 +732,7 @@ protected void getTupleQueryResult(HttpUriRequest method, TupleQueryResultHandle
* Send the tuple query via HTTP and throws an exception in case anything goes wrong, i.e. only for HTTP 200 the
* method returns without exception. If HTTP status code is not equal to 200, the request is aborted, however pooled
* connections are not released.
*
*
* @param method
* @throws RepositoryException
* @throws HttpException
Expand Down Expand Up @@ -908,7 +911,7 @@ private HttpResponse sendGraphQueryViaHttp(HttpUriRequest method, boolean requir
/**
* Parse the response in this thread using a suitable {@link BooleanQueryResultParser}. All HTTP connections are
* closed and released in this method
*
*
* @throws RDF4JException
*/
protected boolean getBoolean(HttpUriRequest method) throws IOException, RDF4JException {
Expand Down Expand Up @@ -975,7 +978,7 @@ private HttpResponse sendBooleanQueryViaHttp(HttpUriRequest method, Set<QueryRes
/**
* Convenience method to deal with HTTP level errors of tuple, graph and boolean queries in the same way. This
* method aborts the HTTP connection.
*
*
* @param method
* @throws RDF4JException
*/
Expand Down Expand Up @@ -1041,6 +1044,10 @@ protected HttpResponse execute(HttpUriRequest method) throws IOException, RDF4JE
throw new MalformedQueryException(errInfo.getErrorMessage());
} else if (errInfo.getErrorType() == ErrorType.UNSUPPORTED_QUERY_LANGUAGE) {
throw new UnsupportedQueryLanguageException(errInfo.getErrorMessage());
} else if (exceptionIs(response, "ShaclSailValidationException")) {
throw new RepositoryException(new RemoteShaclSailValidationException(
new StringReader(errInfo.toString()), "", RDFFormat.NQUADS));

} else if (errInfo.toString().length() > 0) {
throw new RepositoryException(errInfo.toString());
} else {
Expand All @@ -1055,6 +1062,21 @@ protected HttpResponse execute(HttpUriRequest method) throws IOException, RDF4JE
}
}

private boolean exceptionIs(HttpResponse response, String exceptionName) {
Header[] headers = response.getHeaders("X-Eclipse-RDF4J-Exception");
if (headers.length == 0) {
return false;
}

for (Header header : headers) {
if (exceptionName.equals(header.getValue())) {
return true;
}
}

return false;
}

/*-------------------------*
* General utility methods *
*-------------------------*/
Expand All @@ -1063,7 +1085,7 @@ protected HttpResponse execute(HttpUriRequest method) throws IOException, RDF4JE
* Gets the MIME type specified in the response headers of the supplied method, if any. For example, if the response
* headers contain <tt>Content-Type: application/xml;charset=UTF-8</tt>, this method will return
* <tt>application/xml</tt> as the MIME type.
*
*
* @param method The method to get the reponse MIME type from.
* @return The response MIME type, or <tt>null</tt> if not available.
*/
Expand Down Expand Up @@ -1098,7 +1120,7 @@ protected ErrorInfo getErrorInfo(HttpResponse response) throws RepositoryExcepti

/**
* Sets the parser configuration used to process HTTP response data.
*
*
* @param parserConfig The parserConfig to set.
*/
public void setParserConfig(ParserConfig parserConfig) {
Expand All @@ -1124,7 +1146,7 @@ public long getConnectionTimeout() {

/**
* Sets the http connection read timeout.
*
*
* @param timeout timeout in milliseconds. Zero sets to infinity.
*/
public void setConnectionTimeout(long timeout) {
Expand Down
Expand Up @@ -77,6 +77,7 @@ public ModelAndView resolveException(HttpServletRequest request, HttpServletResp
Rio.write(validationReportModel, stringWriter, RDFFormat.NQUADS);
errMsg = stringWriter.toString();
statusCode = HttpServletResponse.SC_BAD_REQUEST;
response.setHeader("X-Eclipse-RDF4J-Exception", ShaclSailValidationException.class.getSimpleName());
}

Map<String, Object> model = new HashMap<>();
Expand Down

0 comments on commit 3fab174

Please sign in to comment.