Skip to content

Commit c107201

Browse files
committed
GH2701: Fuseki Mod to list and abort running executions.
1 parent bc2ad43 commit c107201

File tree

100 files changed

+6616
-368
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

100 files changed

+6616
-368
lines changed

jena-arq/src/main/java/org/apache/jena/http/AsyncHttpRDF.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,10 @@ public static <T> T getOrElseThrow(CompletableFuture<T> cf) {
161161
} catch (CompletionException ex) {
162162
if ( ex.getCause() != null ) {
163163
Throwable cause = ex.getCause();
164-
if ( cause instanceof RuntimeException )
164+
if ( cause instanceof RuntimeException ) {
165+
cause.addSuppressed(new RuntimeException("Passed through here."));
165166
throw (RuntimeException)cause;
167+
}
166168
if ( cause instanceof IOException ) {
167169
IOException iox = (IOException)cause;
168170
// Rather than an HTTP exception, bad authentication becomes IOException("too many authentication attempts");
@@ -173,10 +175,12 @@ public static <T> T getOrElseThrow(CompletableFuture<T> cf) {
173175
IO.exception((IOException)cause);
174176
}
175177
}
178+
ex.addSuppressed(new RuntimeException("Passed through here."));
176179
throw ex;
177180
}
178181
}
179182

183+
180184
/**
181185
* MUST consume or close the input stream
182186
* @see HttpLib#finish(HttpResponse)

jena-arq/src/main/java/org/apache/jena/http/HttpLib.java

Lines changed: 82 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import java.time.Duration;
3838
import java.util.*;
3939
import java.util.concurrent.CompletableFuture;
40+
import java.util.concurrent.CompletionException;
4041
import java.util.concurrent.TimeUnit;
4142
import java.util.function.Consumer;
4243
import java.util.function.Function;
@@ -557,7 +558,7 @@ public static Builder contentTypeHeader(Builder builder, String contentType) {
557558
* @return HttpResponse
558559
*/
559560
public static HttpResponse<InputStream> execute(HttpClient httpClient, HttpRequest httpRequest) {
560-
return execute(httpClient, httpRequest, BodyHandlers.ofInputStream());
561+
return AsyncHttpRDF.getOrElseThrow(executeAsync(httpClient, httpRequest, BodyHandlers.ofInputStream()));
561562
}
562563

563564
/**
@@ -577,13 +578,35 @@ public static HttpResponse<InputStream> execute(HttpClient httpClient, HttpReque
577578
*
578579
* @param httpClient
579580
* @param httpRequest
580-
* @param bodyHandler
581581
* @return HttpResponse
582582
*/
583-
/*package*/ static <X> HttpResponse<X> execute(HttpClient httpClient, HttpRequest httpRequest, BodyHandler<X> bodyHandler) {
583+
public static CompletableFuture<HttpResponse<InputStream>> executeAsync(HttpClient httpClient, HttpRequest httpRequest) {
584+
return executeAsync(httpClient, httpRequest, BodyHandlers.ofInputStream());
585+
}
586+
587+
/**
588+
* Execute a request, return a {@code HttpResponse<X>} which
589+
* can be passed to {@link #handleHttpStatusCode(HttpResponse)} which will
590+
* convert non-2xx status code to {@link HttpException HttpExceptions}.
591+
* <p>
592+
* This function applies the HTTP authentication challenge support
593+
* and will repeat the request if necessary with added authentication.
594+
* <p>
595+
* See {@link AuthEnv} for authentication registration.
596+
* <br/>
597+
* See {@link #executeJDK} to execute exactly once without challenge response handling.
598+
*
599+
* @see AuthEnv AuthEnv for authentic registration
600+
* @see #executeJDK executeJDK to execute exacly once.
601+
*
602+
* @param httpClient
603+
* @param httpRequest
604+
* @param bodyHandler
605+
* @return HttpResponse
606+
*/ /*package*/ static <X> CompletableFuture<HttpResponse<X>> executeAsync(HttpClient httpClient, HttpRequest httpRequest, BodyHandler<X> bodyHandler) {
584607
// To run with no jena-supplied authentication handling.
585608
if ( false )
586-
return executeJDK(httpClient, httpRequest, bodyHandler);
609+
return executeJDKAsync(httpClient, httpRequest, bodyHandler);
587610
URI uri = httpRequest.uri();
588611
URI key = null;
589612

@@ -599,29 +622,16 @@ public static HttpResponse<InputStream> execute(HttpClient httpClient, HttpReque
599622
authEnv.registerUsernamePassword(key, userpasswd[0], userpasswd[1]);
600623
}
601624
}
602-
try {
603-
return AuthLib.authExecute(httpClient, httpRequest, bodyHandler);
604-
} finally {
605-
if ( key != null )
606-
// The AuthEnv is "per tenant".
607-
// Temporary registration within the AuthEnv of the
608-
// user:password is acceptable.
609-
authEnv.unregisterUsernamePassword(key);
610-
}
611-
}
612625

613-
/**
614-
* Execute request and return a {@code HttpResponse<InputStream>} response.
615-
* Status codes have not been handled. The response can be passed to
616-
* {@link #handleResponseInputStream(HttpResponse)} which will convert non-2xx
617-
* status code to {@link HttpException HttpExceptions}.
618-
*
619-
* @param httpClient
620-
* @param httpRequest
621-
* @return HttpResponse
622-
*/
623-
public static HttpResponse<InputStream> executeJDK(HttpClient httpClient, HttpRequest httpRequest) {
624-
return execute(httpClient, httpRequest, BodyHandlers.ofInputStream());
626+
URI finalKey = key;
627+
return AuthLib.authExecuteAsync(httpClient, httpRequest, bodyHandler)
628+
.whenComplete((httpResponse, throwable) -> {
629+
if ( finalKey != null )
630+
// The AuthEnv is "per tenant".
631+
// Temporary registration within the AuthEnv of the
632+
// user:password is acceptable.
633+
authEnv.unregisterUsernamePassword(finalKey);
634+
});
625635
}
626636

627637
/**
@@ -637,25 +647,53 @@ public static HttpResponse<InputStream> executeJDK(HttpClient httpClient, HttpRe
637647
* @return HttpResponse
638648
*/
639649
public static <T> HttpResponse<T> executeJDK(HttpClient httpClient, HttpRequest httpRequest, BodyHandler<T> bodyHandler) {
640-
try {
641-
// This is the one place all HTTP requests go through.
642-
logRequest(httpRequest);
643-
HttpResponse<T> httpResponse = httpClient.send(httpRequest, bodyHandler);
644-
logResponse(httpResponse);
645-
return httpResponse;
646-
//} catch (HttpTimeoutException ex) {
647-
} catch (IOException | InterruptedException ex) {
648-
if ( ex.getMessage() != null ) {
649-
// This is silly.
650-
// Rather than an HTTP exception, bad authentication becomes IOException("too many authentication attempts");
651-
// or IOException("No credentials provided") if the authenticator decides to return null.
652-
if ( ex.getMessage().contains("too many authentication attempts") ||
653-
ex.getMessage().contains("No credentials provided") ) {
654-
throw new HttpException(401, HttpSC.getMessage(401));
650+
return AsyncHttpRDF.getOrElseThrow(executeJDKAsync(httpClient, httpRequest, bodyHandler));
651+
}
652+
653+
/**
654+
* Execute request and return a {@code HttpResponse<InputStream>} response.
655+
* Status codes have not been handled. The response can be passed to
656+
* {@link #handleResponseInputStream(HttpResponse)} which will convert non-2xx
657+
* status code to {@link HttpException HttpExceptions}.
658+
*
659+
* @param httpClient
660+
* @param httpRequest
661+
* @return HttpResponse
662+
*/
663+
public static CompletableFuture<HttpResponse<InputStream>> executeJDKAsync(HttpClient httpClient, HttpRequest httpRequest) {
664+
return executeAsync(httpClient, httpRequest, BodyHandlers.ofInputStream());
665+
}
666+
667+
public static <T> CompletableFuture<HttpResponse<T>> executeJDKAsync(HttpClient httpClient, HttpRequest httpRequest, BodyHandler<T> bodyHandler) {
668+
// This is the one place all HTTP requests go through.
669+
logRequest(httpRequest);
670+
CompletableFuture<HttpResponse<T>> future = httpClient.sendAsync(httpRequest, bodyHandler)
671+
.thenApply(httpResponse -> {
672+
logResponse(httpResponse);
673+
return httpResponse;
674+
})
675+
.exceptionally(ex -> {
676+
Throwable cause = ex instanceof CompletionException ? ex.getCause() : ex;
677+
if (cause instanceof IOException) {
678+
if ( ex.getMessage() != null ) {
679+
// This is silly.
680+
// Rather than an HTTP exception, bad authentication becomes IOException("too many authentication attempts");
681+
// or IOException("No credentials provided") if the authenticator decides to return null.
682+
if ( ex.getMessage().contains("too many authentication attempts") ||
683+
ex.getMessage().contains("No credentials provided") ) {
684+
throw new HttpException(401, HttpSC.getMessage(401));
685+
}
686+
}
687+
// Note: Can't reuse AsyncHttpRDF.handleRuntimeException because of this HttpException.
688+
throw new HttpException(httpRequest.method()+" "+httpRequest.uri().toString(), cause);
689+
} else if (cause instanceof RuntimeException re) {
690+
re.addSuppressed(new RuntimeException("Passed through here."));
691+
throw re;
692+
} else {
693+
throw new RuntimeException(cause);
655694
}
656-
}
657-
throw new HttpException(httpRequest.method()+" "+httpRequest.uri().toString(), ex);
658-
}
695+
});
696+
return future;
659697
}
660698

661699
/*package*/ static CompletableFuture<HttpResponse<InputStream>> asyncExecute(HttpClient httpClient, HttpRequest httpRequest) {

jena-arq/src/main/java/org/apache/jena/http/auth/AuthLib.java

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,24 @@
2929
import java.nio.charset.StandardCharsets;
3030
import java.util.Base64;
3131
import java.util.List;
32+
import java.util.concurrent.CompletableFuture;
3233

3334
import org.apache.jena.atlas.lib.Bytes;
3435
import org.apache.jena.atlas.web.AuthScheme;
3536
import org.apache.jena.atlas.web.HttpException;
37+
import org.apache.jena.http.AsyncHttpRDF;
3638
import org.apache.jena.http.HttpLib;
3739
import org.apache.jena.riot.web.HttpNames;
3840
import org.apache.jena.web.HttpSC;
3941

4042
public class AuthLib {
43+
/** @see #authExecuteAsync */
44+
public static <T> HttpResponse<T> authExecute(HttpClient httpClient, HttpRequest httpRequest, BodyHandler<T> bodyHandler) {
45+
return AsyncHttpRDF.getOrElseThrow(authExecuteAsync(httpClient, httpRequest, bodyHandler));
46+
}
47+
4148
/**
42-
* Call {@link HttpClient#send} after applying an active {@link AuthRequestModifier}
49+
* Call {@link HttpClient#sendAsync} after applying an active {@link AuthRequestModifier}
4350
* to modify the {@link java.net.http.HttpRequest.Builder}.
4451
* If no {@link AuthRequestModifier} is available and if a 401 response is received,
4552
* setup a {@link AuthRequestModifier} passed on registered username and password information.
@@ -50,25 +57,26 @@ public class AuthLib {
5057
* @param bodyHandler
5158
* @return HttpResponse&lt;T&gt;
5259
*/
53-
public static <T> HttpResponse<T> authExecute(HttpClient httpClient, HttpRequest httpRequest, BodyHandler<T> bodyHandler) {
54-
HttpResponse<T> httpResponse = HttpLib.executeJDK(httpClient, httpRequest, bodyHandler);
55-
56-
// -- 401 handling.
57-
if ( httpResponse.statusCode() != 401 )
58-
return httpResponse;
59-
HttpResponse<T> httpResponse2 = handle401(httpClient, httpRequest, bodyHandler, httpResponse);
60-
return httpResponse2;
60+
public static <T> CompletableFuture<HttpResponse<T>> authExecuteAsync(HttpClient httpClient, HttpRequest httpRequest, BodyHandler<T> bodyHandler) {
61+
return HttpLib.executeJDKAsync(httpClient, httpRequest, bodyHandler)
62+
.thenCompose(httpResponse -> {
63+
// -- 401 handling.
64+
if ( httpResponse.statusCode() != 401 )
65+
return CompletableFuture.completedFuture(httpResponse);
66+
CompletableFuture<HttpResponse<T>> httpResponse2 = handle401Async(httpClient, httpRequest, bodyHandler, httpResponse);
67+
return httpResponse2;
68+
});
6169
}
6270

6371
/* Handle a 401 (authentication challenge). */
64-
private static <T> HttpResponse<T> handle401(HttpClient httpClient,
72+
private static <T> CompletableFuture<HttpResponse<T>> handle401Async(HttpClient httpClient,
6573
HttpRequest request,
6674
BodyHandler<T> bodyHandler,
6775
HttpResponse<T> httpResponse401) {
6876
AuthChallenge aHeader = wwwAuthenticateHeader(httpResponse401);
6977
if ( aHeader == null )
7078
// No valid header - simply return the original response.
71-
return httpResponse401;
79+
return CompletableFuture.completedFuture(httpResponse401);
7280

7381
// Currently on a URI endpoint-by-endpoint basis.
7482
// String realm = aHeader.getRealm();
@@ -102,14 +110,14 @@ private static <T> HttpResponse<T> handle401(HttpClient httpClient,
102110
}
103111
case UNKNOWN :
104112
// Not handled. Pass back the 401.
105-
return httpResponse401;
113+
return CompletableFuture.completedFuture(httpResponse401);
106114
default:
107115
throw new HttpException("Not an authentication scheme -- "+aHeader.authScheme);
108116
}
109117

110118
// Failed to generate a request modifier for a retry.
111119
if ( authRequestModifier == null)
112-
return httpResponse401;
120+
return CompletableFuture.completedFuture(httpResponse401);
113121

114122
// ---- Register for next time the app calls this URI.
115123
AuthEnv.get().registerAuthModifier(request.uri().toString(), authRequestModifier);
@@ -119,7 +127,7 @@ private static <T> HttpResponse<T> handle401(HttpClient httpClient,
119127
request2builder = authRequestModifier.addAuth(request2builder);
120128

121129
HttpRequest httpRequest2 = request2builder.build();
122-
HttpResponse<T> httpResponse2 = HttpLib.executeJDK(httpClient, httpRequest2, bodyHandler);
130+
CompletableFuture<HttpResponse<T>> httpResponse2 = HttpLib.executeJDKAsync(httpClient, httpRequest2, bodyHandler);
123131
// Pass back to application regardless of response code.
124132
return httpResponse2;
125133
}

jena-arq/src/main/java/org/apache/jena/http/sys/ExecHTTPBuilder.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.apache.jena.query.*;
2828
import org.apache.jena.sparql.core.Var;
2929
import org.apache.jena.sparql.engine.binding.Binding;
30+
import org.apache.jena.sparql.engine.dispatch.SparqlDispatcherRegistry;
3031
import org.apache.jena.sparql.exec.http.Params;
3132
import org.apache.jena.sparql.exec.http.QuerySendMode;
3233
import org.apache.jena.sparql.syntax.syntaxtransform.QueryTransformOps;
@@ -44,7 +45,7 @@ public abstract class ExecHTTPBuilder<X, Y> {
4445
protected String serviceURL = null;
4546
private Query query = null;
4647
protected String queryString = null;
47-
protected boolean parseCheck = true;
48+
protected Boolean parseCheck = null;
4849
private HttpClient httpClient = null;
4950
protected Map<String, String> httpHeaders = new HashMap<>();
5051
protected Params params = Params.create();
@@ -79,6 +80,10 @@ public Y parseCheck(boolean parseCheck) {
7980
return thisBuilder();
8081
}
8182

83+
protected boolean effectiveParseCheck() {
84+
return SparqlDispatcherRegistry.effectiveParseCheck(parseCheck, contextAcc);
85+
}
86+
8287
/** Set the query - this also sets the query string to agree with the query argument. */
8388
public Y query(Query query) {
8489
Objects.requireNonNull(query);
@@ -93,14 +98,14 @@ public Y query(Query query) {
9398
*/
9499
public Y query(String queryStr) {
95100
Objects.requireNonNull(queryStr);
96-
Query query = parseCheck ? QueryFactory.create(queryStr) : null;
101+
Query query = effectiveParseCheck() ? QueryFactory.create(queryStr) : null;
97102
setQuery(query, queryStr);
98103
return thisBuilder();
99104
}
100105

101106
public Y query(String queryStr, Syntax syntax) {
102107
Objects.requireNonNull(queryStr);
103-
Query query = QueryFactory.create(queryStr, syntax);
108+
Query query = effectiveParseCheck() ? QueryFactory.create(queryStr, syntax) : null;
104109
setQuery(query, queryStr);
105110
return thisBuilder();
106111
}

0 commit comments

Comments
 (0)