Skip to content

Commit 86f3c03

Browse files
committed
GH-4492 use new txn action endpoints in protocol client, backward compatible
1 parent ca28feb commit 86f3c03

File tree

4 files changed

+128
-50
lines changed

4 files changed

+128
-50
lines changed

core/http/client/src/main/java/org/eclipse/rdf4j/http/client/RDF4JProtocolSession.java

Lines changed: 84 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.io.OutputStreamWriter;
2121
import java.io.Reader;
2222
import java.net.HttpURLConnection;
23+
import java.net.URI;
2324
import java.net.URISyntaxException;
2425
import java.nio.charset.Charset;
2526
import java.nio.charset.StandardCharsets;
@@ -121,6 +122,8 @@ public class RDF4JProtocolSession extends SPARQLProtocolSession {
121122

122123
private long pingDelay = PINGDELAY;
123124

125+
private int serverProtocolVersion = 0;
126+
124127
/**
125128
* @deprecated Use {@link #RDF4JProtocolSession(HttpClient, ExecutorService)} instead
126129
*/
@@ -165,6 +168,7 @@ public void setServerURL(String serverURL) {
165168
}
166169

167170
this.serverURL = serverURL;
171+
this.serverProtocolVersion = 0; // side effect
168172
}
169173

170174
public String getServerURL() {
@@ -275,6 +279,16 @@ public String getServerProtocol() throws IOException, RepositoryException, Unaut
275279
}
276280
}
277281

282+
private int getServerProtocolVersion() throws UnauthorizedException, RepositoryException, IOException {
283+
if (serverProtocolVersion > 0) {
284+
return serverProtocolVersion;
285+
}
286+
287+
var protocolVersionString = getServerProtocol();
288+
serverProtocolVersion = Integer.parseInt(protocolVersionString);
289+
return serverProtocolVersion;
290+
}
291+
278292
/*-------------------------*
279293
* Repository/context size *
280294
*-------------------------*/
@@ -286,9 +300,8 @@ public long size(Resource... contexts) throws IOException, RepositoryException,
286300
String transactionURL = getTransactionURL();
287301
final boolean useTransaction = transactionURL != null;
288302

289-
String baseLocation = useTransaction ? appendAction(transactionURL, Action.SIZE)
290-
: Protocol.getSizeLocation(getQueryURL());
291-
URIBuilder url = new URIBuilder(baseLocation);
303+
URIBuilder url = useTransaction ? getTxnActionURIBuilder(Action.SIZE)
304+
: new URIBuilder(Protocol.getSizeLocation(getQueryURL()));
292305

293306
String[] encodedContexts = Protocol.encodeContexts(contexts);
294307
for (int i = 0; i < encodedContexts.length; i++) {
@@ -591,8 +604,8 @@ public void getStatements(Resource subj, IRI pred, Value obj, boolean includeInf
591604
String transactionURL = getTransactionURL();
592605
final boolean useTransaction = transactionURL != null;
593606

594-
String baseLocation = useTransaction ? transactionURL : Protocol.getStatementsLocation(getQueryURL());
595-
URIBuilder url = new URIBuilder(baseLocation);
607+
URIBuilder url = useTransaction ? getTxnActionURIBuilder(Action.GET)
608+
: new URIBuilder(Protocol.getStatementsLocation(getQueryURL()));
596609

597610
if (subj != null) {
598611
url.setParameter(Protocol.SUBJECT_PARAM_NAME, Protocol.encodeValue(subj));
@@ -607,9 +620,6 @@ public void getStatements(Resource subj, IRI pred, Value obj, boolean includeInf
607620
url.addParameter(Protocol.CONTEXT_PARAM_NAME, encodedContext);
608621
}
609622
url.setParameter(Protocol.INCLUDE_INFERRED_PARAM_NAME, Boolean.toString(includeInferred));
610-
if (useTransaction) {
611-
url.setParameter(Protocol.ACTION_PARAM_NAME, Action.GET.toString());
612-
}
613623

614624
HttpRequestBase method = useTransaction ? new HttpPut(url.build()) : new HttpGet(url.build());
615625
method = applyAdditionalHeaders(method);
@@ -683,6 +693,24 @@ public synchronized void beginTransaction(TransactionSetting... transactionSetti
683693
}
684694
}
685695

696+
private URIBuilder getTxnActionURIBuilder(Action action)
697+
throws RDF4JException, IOException, UnauthorizedException {
698+
Objects.requireNonNull(action);
699+
try {
700+
if (getServerProtocolVersion() < 14) { // use legacy action parameter instead of dedicated action endpoint
701+
URIBuilder builder = new URIBuilder(transactionURL);
702+
builder.addParameter(Protocol.ACTION_PARAM_NAME, action.toString());
703+
return builder;
704+
}
705+
706+
URIBuilder builder = new URIBuilder(transactionURL + "/" + action.toString().toLowerCase());
707+
return builder;
708+
} catch (URISyntaxException e) {
709+
logger.error("could not create URL for transaction " + action, e);
710+
throw new RuntimeException(e);
711+
}
712+
}
713+
686714
public synchronized void prepareTransaction() throws RDF4JException, IOException, UnauthorizedException {
687715
checkRepositoryURL();
688716

@@ -692,9 +720,8 @@ public synchronized void prepareTransaction() throws RDF4JException, IOException
692720

693721
HttpPut method = null;
694722
try {
695-
URIBuilder url = new URIBuilder(transactionURL);
696-
url.addParameter(Protocol.ACTION_PARAM_NAME, Action.PREPARE.toString());
697-
method = applyAdditionalHeaders(new HttpPut(url.build()));
723+
var uriBuilder = getTxnActionURIBuilder(Action.PREPARE);
724+
method = applyAdditionalHeaders(new HttpPut(uriBuilder.build()));
698725

699726
final HttpResponse response = execute(method);
700727
try {
@@ -725,9 +752,8 @@ public synchronized void commitTransaction() throws RDF4JException, IOException,
725752

726753
HttpPut method = null;
727754
try {
728-
URIBuilder url = new URIBuilder(transactionURL);
729-
url.addParameter(Protocol.ACTION_PARAM_NAME, Action.COMMIT.toString());
730-
method = applyAdditionalHeaders(new HttpPut(url.build()));
755+
var uriBuilder = getTxnActionURIBuilder(Action.COMMIT);
756+
method = applyAdditionalHeaders(new HttpPut(uriBuilder.build()));
731757

732758
final HttpResponse response = execute(method);
733759
try {
@@ -804,11 +830,10 @@ void executeTransactionPing() {
804830
if (transactionURL == null) {
805831
return; // transaction has already been closed
806832
}
807-
HttpPost method;
808833
try {
809-
URIBuilder url = new URIBuilder(transactionURL);
810-
url.addParameter(Protocol.ACTION_PARAM_NAME, Action.PING.toString());
811-
method = applyAdditionalHeaders(new HttpPost(url.build()));
834+
var uriBuilder = getTxnActionURIBuilder(Action.PING);
835+
var method = applyAdditionalHeaders(new HttpPost(uriBuilder.build()));
836+
812837
String text = EntityUtils.toString(executeOK(method).getEntity());
813838
long timeout = Long.parseLong(text);
814839
// clients should ping before server timeouts transaction
@@ -824,16 +849,16 @@ void executeTransactionPing() {
824849
pingTransaction(); // reschedule
825850
}
826851

827-
/**
828-
* Appends the action as a parameter to the supplied url
829-
*
830-
* @param url a url on which to append the parameter. it is assumed the url has no parameters.
831-
* @param action the action to add as a parameter
832-
* @return the url parametrized with the supplied action
833-
*/
834-
private String appendAction(String url, Action action) {
835-
return url + "?" + Protocol.ACTION_PARAM_NAME + "=" + action.toString();
836-
}
852+
// /**
853+
// * Appends the action as a parameter to the supplied url
854+
// *
855+
// * @param url a url on which to append the parameter. it is assumed the url has no parameters.
856+
// * @param action the action to add as a parameter
857+
// * @return the url parametrized with the supplied action
858+
// */
859+
// private String appendAction(String url, Action action) {
860+
// return url + "?" + Protocol.ACTION_PARAM_NAME + "=" + action.toString();
861+
// }
837862

838863
/**
839864
* Sends a transaction list as serialized XML to the server.
@@ -938,9 +963,16 @@ protected HttpUriRequest getQueryMethod(QueryLanguage ql, String query, String b
938963
RequestBuilder builder;
939964
String transactionURL = getTransactionURL();
940965
if (transactionURL != null) {
941-
builder = RequestBuilder.put(transactionURL);
966+
URI requestURI;
967+
try {
968+
requestURI = getTxnActionURIBuilder(Action.QUERY).build();
969+
} catch (URISyntaxException | IOException e) {
970+
logger.error("could not create URL for transaction query", e);
971+
throw new RuntimeException(e);
972+
}
973+
974+
builder = RequestBuilder.put(requestURI);
942975
builder.setHeader("Content-Type", Protocol.SPARQL_QUERY_MIME_TYPE + "; charset=utf-8");
943-
builder.addParameter(Protocol.ACTION_PARAM_NAME, Action.QUERY.toString());
944976
for (NameValuePair nvp : getQueryMethodParameters(ql, null, baseURI, dataset, includeInferred, maxQueryTime,
945977
bindings)) {
946978
builder.addParameter(nvp);
@@ -971,9 +1003,17 @@ protected HttpUriRequest getUpdateMethod(QueryLanguage ql, String update, String
9711003
RequestBuilder builder;
9721004
String transactionURL = getTransactionURL();
9731005
if (transactionURL != null) {
974-
builder = RequestBuilder.put(transactionURL);
1006+
1007+
URI requestURI;
1008+
try {
1009+
requestURI = getTxnActionURIBuilder(Action.UPDATE).build();
1010+
} catch (URISyntaxException | IOException e) {
1011+
logger.error("could not create URL for transaction update", e);
1012+
throw new RuntimeException(e);
1013+
}
1014+
1015+
builder = RequestBuilder.put(requestURI);
9751016
builder.addHeader("Content-Type", Protocol.SPARQL_UPDATE_MIME_TYPE + "; charset=utf-8");
976-
builder.addParameter(Protocol.ACTION_PARAM_NAME, Action.UPDATE.toString());
9771017
for (NameValuePair nvp : getUpdateMethodParameters(ql, null, baseURI, dataset, includeInferred,
9781018
maxExecutionTime, bindings)) {
9791019
builder.addParameter(nvp);
@@ -1061,36 +1101,28 @@ protected void upload(HttpEntity reqEntity, String baseURI, boolean overwrite, b
10611101
boolean useTransaction = transactionURL != null;
10621102

10631103
try {
1064-
1065-
String baseLocation = useTransaction ? transactionURL : Protocol.getStatementsLocation(getQueryURL());
1066-
URIBuilder url = new URIBuilder(baseLocation);
1104+
URIBuilder uriBuilder = useTransaction ? getTxnActionURIBuilder(action)
1105+
: new URIBuilder(Protocol.getStatementsLocation(getQueryURL()));
10671106

10681107
// Set relevant query parameters
10691108
for (String encodedContext : Protocol.encodeContexts(contexts)) {
1070-
url.addParameter(Protocol.CONTEXT_PARAM_NAME, encodedContext);
1109+
uriBuilder.addParameter(Protocol.CONTEXT_PARAM_NAME, encodedContext);
10711110
}
10721111
if (baseURI != null && baseURI.trim().length() != 0) {
10731112
String encodedBaseURI = Protocol.encodeValue(SimpleValueFactory.getInstance().createIRI(baseURI));
1074-
url.setParameter(Protocol.BASEURI_PARAM_NAME, encodedBaseURI);
1113+
uriBuilder.setParameter(Protocol.BASEURI_PARAM_NAME, encodedBaseURI);
10751114
}
10761115
if (preserveNodeIds) {
1077-
url.setParameter(Protocol.PRESERVE_BNODE_ID_PARAM_NAME, "true");
1078-
}
1079-
1080-
if (useTransaction) {
1081-
if (action == null) {
1082-
throw new IllegalArgumentException("action can not be null on transaction operation");
1083-
}
1084-
url.setParameter(Protocol.ACTION_PARAM_NAME, action.toString());
1116+
uriBuilder.setParameter(Protocol.PRESERVE_BNODE_ID_PARAM_NAME, "true");
10851117
}
10861118

10871119
// Select appropriate HTTP method
10881120
HttpEntityEnclosingRequestBase method = null;
10891121
try {
10901122
if (overwrite || useTransaction) {
1091-
method = applyAdditionalHeaders(new HttpPut(url.build()));
1123+
method = applyAdditionalHeaders(new HttpPut(uriBuilder.build()));
10921124
} else {
1093-
method = applyAdditionalHeaders(new HttpPost(url.build()));
1125+
method = applyAdditionalHeaders(new HttpPost(uriBuilder.build()));
10941126
}
10951127

10961128
// Set payload
@@ -1217,4 +1249,9 @@ private <T extends HttpUriRequest> T applyAdditionalHeaders(T method) {
12171249
}
12181250
return method;
12191251
}
1252+
1253+
private boolean useDeprecatedTxnActions()
1254+
throws UnauthorizedException, NumberFormatException, RepositoryException, IOException {
1255+
return Integer.parseInt(getServerProtocol()) < 14;
1256+
}
12201257
}

core/http/protocol/src/main/java/org/eclipse/rdf4j/http/protocol/Protocol.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,13 +104,15 @@ public enum TIMEOUT {
104104
* Protocol version.
105105
*
106106
* <ul>
107+
* <li>14: since RDF4J 4.3.0</li>
108+
* <li>13: since RDF4J 4.0.0</li>
107109
* <li>12: since RDF4J 3.5.0</li>
108110
* <li>11: since RDF4J 3.3.0</li>
109111
* <li>10: since RDF4J 3.1.0</li>
110112
* <li>9: since RDF4J 3.0.0</li>
111113
* </ul>
112114
*/
113-
public static final String VERSION = "12";
115+
public static final String VERSION = "14";
114116

115117
/**
116118
* Parameter name for the 'subject' parameter of a statement query.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
---
2+
title: "RDF4J REST API: Changelog"
3+
toc: true
4+
weight: 1
5+
---
6+
7+
[OpenAPI specification](/documentation/reference/rest-api/)
8+
9+
## Version history
10+
11+
The RDF4J REST API uses a single integer version numbering scheme. It does not follow semantic versioning. However, the implementations of the API client and server in RDF4J make a conscious effort to stay backward compatible with at least one previous protocol version.
12+
13+
### 14: since RDF4J 4.3.0
14+
15+
- Replaced usage of the `action` parameter on the `/transactions/{txnID}` endpoint with separate endpoints for each available action: `/transactions/{txnID}/add`, `/transactions/{txnID}/query`, and so on.
16+
- `action` parameter for transactions is deprecated
17+
18+
### 13: since RDF4J 4.0.0
19+
20+
- Removed support for the deprecated SYSTEM repository.
21+
22+
### 12: since RDF4J 3.5.0
23+
24+
- Added suport for the `prepare` transaction operation.
25+
26+
### 11: since RDF4J 3.3.0
27+
28+
- Added support for sending general transaction setting parameters.
29+
30+
### 10: since RDF4J 3.1.0
31+
32+
- Added support for retrieving a repository configuration.
33+
34+
### 9: since RDF4J 3.0.0
35+
36+
- Added direct API support for creating a new repository remotely and/or updating an existing repository's configuration.
37+
- SYSTEM repository support is deprecated.

site/static/documentation/reference/rest-api/rdf4j-openapi.yaml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
openapi: "3.0.1"
22
info:
33
title: RDF4J REST API
4-
version: "10"
4+
version: "14"
55
description: |
66
The RDF4J REST API is an HTTP protocol that covers a fully compliant implementation of the [SPARQL 1.1 Protocol W3C Recommendation](https://www.w3.org/TR/sparql11-protocol/). This ensures that RDF4J Server functions as a fully standard-compliant [SPARQL](https://www.w3.org/TR/sparql11-query/) endpoint.
77
88
The RDF4J REST API additionally supports the [SPARQL 1.1 Graph Store HTTP Protocol W3C Recommendation](https://www.w3.org/TR/sparql11-http-rdf-update/). The RDF4J REST API extends the W3C standards in several aspects, the most important of which is database transaction management.
99
10+
Version 13 was released as part of RDF4J 4.3.0. See the [REST API Changelog](/documentation/reference/rest-api/changelog) for details.
11+
1012
externalDocs:
11-
url: https://rdf4j.org/documentation/reference/rest-api/
13+
url: https://rdf4j.org/documentation/reference/rest-api/changelog.md
1214

1315
servers:
1416
- url: http://localhost:8080/rdf4j-server/

0 commit comments

Comments
 (0)