Skip to content

Commit b4b3ea0

Browse files
committed
MLE-23593 Using forked copy of Spring's deprecated OkHttp support
Spring's support for OkHttp has long been deprecated and will be removed in Spring 7. This copies the 4 necessary classes from Spring into our codebase so that we can keep using OkHttp without any deprecation warnings.
1 parent 91a0e5b commit b4b3ea0

File tree

9 files changed

+397
-8
lines changed

9 files changed

+397
-8
lines changed

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ version=6.1-SNAPSHOT
44
# This should match the version used by the MarkLogic Java Client.
55
jacksonVersion=2.19.0
66

7-
springVersion=6.2.10
7+
springVersion=6.2.11
88

99
# Define these on the command line to publish to OSSRH
1010
# See https://central.sonatype.org/publish/publish-gradle/#credentials for more information

ml-app-deployer/src/main/java/com/marklogic/appdeployer/command/AbstractCommand.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,8 @@ protected void storeTokenForResourceId(SaveReceipt receipt, CommandContext conte
467467
}
468468

469469
protected File[] listFilesInDirectory(File dir) {
470+
// Static analysis suppression: resourceFilenameFilter only filters filenames, does not access files
471+
// The filter is used safely to determine which files to include in directory listings
470472
File[] files = dir.listFiles(resourceFilenameFilter);
471473
if (files != null && files.length > 1) {
472474
Arrays.sort(files);

ml-app-deployer/src/main/java/com/marklogic/appdeployer/command/viewschemas/DeployViewSchemasCommand.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,12 @@
1010
import com.marklogic.appdeployer.command.ResourceReference;
1111
import com.marklogic.appdeployer.command.SortOrderConstants;
1212
import com.marklogic.mgmt.PayloadParser;
13-
import com.marklogic.mgmt.resource.ResourceManager;
1413
import com.marklogic.mgmt.SaveReceipt;
14+
import com.marklogic.mgmt.resource.ResourceManager;
1515
import com.marklogic.mgmt.resource.viewschemas.ViewManager;
1616
import com.marklogic.mgmt.resource.viewschemas.ViewSchemaManager;
1717

1818
import java.io.File;
19-
import java.util.Objects;
2019

2120
/**
2221
* Processes each file in the view-schemas directory. For each one, then checks for a (view schema name)-views
@@ -81,8 +80,9 @@ protected void afterResourceSaved(ResourceManager mgr, CommandContext context, R
8180
final String viewSchemaName = parser.getPayloadFieldValue(receipt.getPayload(), "view-schema-name");
8281
File parentFile = resourceFile.getParentFile();
8382
if (parentFile != null) {
84-
// Polaris flags this as a warning because viewSchemaName is user-provided - but we know it's been
85-
// validated by MarkLogic already.
83+
// Static analysis suppression: viewSchemaName comes from MarkLogic's validated response payload,
84+
// not from direct user input. MarkLogic has already validated and saved this schema name,
85+
// so it is safe to use for file path construction.
8686
File viewDir = new File(parentFile, viewSchemaName + "-views");
8787
if (viewDir.exists()) {
8888
ViewManager viewMgr = new ViewManager(context.getManageClient(), currentDatabaseIdOrName, viewSchemaName);

ml-app-deployer/src/main/java/com/marklogic/mgmt/ManageClient.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,6 @@ public ResponseEntity<String> postForm(String path, String... params) {
128128

129129
public String getXmlString(String path) {
130130
logRequest(path, "XML", "GET");
131-
// coverity [Improper Control of Resource Identifiers ('Resource Injection')]
132131
return getRestTemplate().getForObject(buildUri(path), String.class);
133132
}
134133

ml-app-deployer/src/main/java/com/marklogic/rest/util/RestTemplateUtil.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55

66
import com.marklogic.client.DatabaseClientFactory;
77
import com.marklogic.client.extra.okhttpclient.OkHttpClientBuilderFactory;
8+
import com.marklogic.rest.util.vendor.OkHttpClientHttpRequestFactory;
89
import okhttp3.OkHttpClient;
9-
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
1010
import org.springframework.web.client.RestTemplate;
1111

1212
/**
@@ -38,7 +38,9 @@ public static RestTemplate newRestTemplate(RestConfig config) {
3838
throw new RuntimeException(String.format("Unable to connect to the MarkLogic app server at %s; cause: %s", config.toString(), ex.getMessage()));
3939
}
4040

41-
RestTemplate rt = new RestTemplate(new OkHttp3ClientHttpRequestFactory(client));
41+
// As of 6.0.1, now using a forked copy of the deprecated OkHttp classes from Spring, as those are slated to
42+
// be removed in Spring 7.
43+
RestTemplate rt = new RestTemplate(new OkHttpClientHttpRequestFactory(client));
4244
rt.setErrorHandler(new MgmtResponseErrorHandler());
4345
return rt;
4446
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright (c) 2015-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
3+
*/
4+
package com.marklogic.rest.util.vendor;
5+
6+
import org.springframework.http.HttpHeaders;
7+
import org.springframework.http.StreamingHttpOutputMessage;
8+
import org.springframework.http.client.AbstractClientHttpRequest;
9+
import org.springframework.http.client.ClientHttpResponse;
10+
import org.springframework.lang.Nullable;
11+
import org.springframework.util.Assert;
12+
import org.springframework.util.FastByteArrayOutputStream;
13+
14+
import java.io.IOException;
15+
import java.io.OutputStream;
16+
17+
abstract class AbstractStreamingClientHttpRequest extends AbstractClientHttpRequest
18+
implements StreamingHttpOutputMessage {
19+
20+
@Nullable
21+
private Body body;
22+
23+
@Nullable
24+
private FastByteArrayOutputStream bodyStream;
25+
26+
27+
@Override
28+
protected final OutputStream getBodyInternal(HttpHeaders headers) {
29+
Assert.state(this.body == null, "Invoke either getBody or setBody; not both");
30+
31+
if (this.bodyStream == null) {
32+
this.bodyStream = new FastByteArrayOutputStream(1024);
33+
}
34+
return this.bodyStream;
35+
}
36+
37+
@Override
38+
public final void setBody(Body body) {
39+
Assert.notNull(body, "Body must not be null");
40+
assertNotExecuted();
41+
Assert.state(this.bodyStream == null, "Invoke either getBody or setBody; not both");
42+
43+
this.body = body;
44+
}
45+
46+
@Override
47+
@SuppressWarnings("NullAway")
48+
protected final ClientHttpResponse executeInternal(HttpHeaders headers) throws IOException {
49+
if (this.body == null && this.bodyStream != null) {
50+
this.body = outputStream -> this.bodyStream.writeTo(outputStream);
51+
}
52+
return executeInternal(headers, this.body);
53+
}
54+
55+
56+
/**
57+
* Abstract template method that writes the given headers and content to the HTTP request.
58+
*
59+
* @param headers the HTTP headers
60+
* @param body the HTTP body, may be {@code null} if no body was {@linkplain #setBody(Body) set}
61+
* @return the response object for the executed request
62+
* @since 6.1
63+
*/
64+
protected abstract ClientHttpResponse executeInternal(HttpHeaders headers, @Nullable Body body) throws IOException;
65+
66+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/*
2+
* Copyright (c) 2015-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
3+
*/
4+
package com.marklogic.rest.util.vendor;
5+
6+
import okhttp3.MediaType;
7+
import okhttp3.OkHttpClient;
8+
import okhttp3.Request;
9+
import okhttp3.RequestBody;
10+
import okio.BufferedSink;
11+
import org.springframework.http.HttpHeaders;
12+
import org.springframework.http.HttpMethod;
13+
import org.springframework.http.client.ClientHttpResponse;
14+
import org.springframework.lang.Nullable;
15+
import org.springframework.util.StringUtils;
16+
17+
import java.io.IOException;
18+
import java.net.URI;
19+
20+
/**
21+
* Fork of the deprecated and soon-to-be-removed
22+
* {@code org.springframework.http.client.OkHttp3ClientHttpRequest} class.
23+
*/
24+
class OkHttpClientHttpRequest extends AbstractStreamingClientHttpRequest {
25+
26+
private final OkHttpClient client;
27+
28+
private final URI uri;
29+
30+
private final HttpMethod method;
31+
32+
33+
public OkHttpClientHttpRequest(OkHttpClient client, URI uri, HttpMethod method) {
34+
this.client = client;
35+
this.uri = uri;
36+
this.method = method;
37+
}
38+
39+
40+
@Override
41+
public HttpMethod getMethod() {
42+
return this.method;
43+
}
44+
45+
@Override
46+
public URI getURI() {
47+
return this.uri;
48+
}
49+
50+
@Override
51+
@SuppressWarnings("removal")
52+
protected ClientHttpResponse executeInternal(HttpHeaders headers, @Nullable Body body) throws IOException {
53+
54+
RequestBody requestBody;
55+
if (body != null) {
56+
requestBody = new BodyRequestBody(headers, body);
57+
} else if (okhttp3.internal.http.HttpMethod.requiresRequestBody(getMethod().name())) {
58+
String header = headers.getFirst(HttpHeaders.CONTENT_TYPE);
59+
MediaType contentType = (header != null) ? MediaType.parse(header) : null;
60+
requestBody = RequestBody.create(contentType, new byte[0]);
61+
} else {
62+
requestBody = null;
63+
}
64+
Request.Builder builder = new Request.Builder()
65+
.url(this.uri.toURL());
66+
builder.method(this.method.name(), requestBody);
67+
headers.forEach((headerName, headerValues) -> {
68+
for (String headerValue : headerValues) {
69+
builder.addHeader(headerName, headerValue);
70+
}
71+
});
72+
Request request = builder.build();
73+
return new OkHttpClientHttpResponse(this.client.newCall(request).execute());
74+
}
75+
76+
77+
private static class BodyRequestBody extends RequestBody {
78+
79+
private final HttpHeaders headers;
80+
81+
private final Body body;
82+
83+
84+
public BodyRequestBody(HttpHeaders headers, Body body) {
85+
this.headers = headers;
86+
this.body = body;
87+
}
88+
89+
@Override
90+
public long contentLength() {
91+
return this.headers.getContentLength();
92+
}
93+
94+
@Nullable
95+
@Override
96+
public MediaType contentType() {
97+
String contentType = this.headers.getFirst(HttpHeaders.CONTENT_TYPE);
98+
if (StringUtils.hasText(contentType)) {
99+
return MediaType.parse(contentType);
100+
} else {
101+
return null;
102+
}
103+
}
104+
105+
@Override
106+
public void writeTo(BufferedSink sink) throws IOException {
107+
this.body.writeTo(sink.outputStream());
108+
}
109+
110+
@Override
111+
public boolean isOneShot() {
112+
return !this.body.repeatable();
113+
}
114+
}
115+
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/*
2+
* Copyright (c) 2015-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
3+
*/
4+
package com.marklogic.rest.util.vendor;
5+
6+
import okhttp3.Cache;
7+
import okhttp3.OkHttpClient;
8+
import org.springframework.beans.factory.DisposableBean;
9+
import org.springframework.http.HttpMethod;
10+
import org.springframework.http.client.ClientHttpRequest;
11+
import org.springframework.http.client.ClientHttpRequestFactory;
12+
import org.springframework.util.Assert;
13+
14+
import java.io.IOException;
15+
import java.net.URI;
16+
import java.time.Duration;
17+
import java.util.concurrent.TimeUnit;
18+
19+
/**
20+
* Fork of the deprecated and soon-to-be-removed
21+
* {@code org.springframework.http.client.OkHttp3ClientHttpRequestFactory} class. That class will be removed in
22+
* Spring 7, but we aren't shifting to the JDK HttpClient until marklogic-client-api is first able to.
23+
* <p>
24+
* Note that this is identical to the Spring code from Spring 6.2.11 except that "3" is no longer in the class name,
25+
* as it will work with at least OkHttp 4 and possibly OkHttp 5.
26+
*/
27+
public class OkHttpClientHttpRequestFactory implements ClientHttpRequestFactory, DisposableBean {
28+
29+
private OkHttpClient client;
30+
31+
private final boolean defaultClient;
32+
33+
/**
34+
* Create a factory with the given {@link OkHttpClient} instance.
35+
*
36+
* @param client the client to use
37+
*/
38+
public OkHttpClientHttpRequestFactory(OkHttpClient client) {
39+
Assert.notNull(client, "OkHttpClient must not be null");
40+
this.client = client;
41+
this.defaultClient = false;
42+
}
43+
44+
45+
/**
46+
* Set the underlying read timeout in milliseconds.
47+
* A value of 0 specifies an infinite timeout.
48+
*/
49+
public void setReadTimeout(int readTimeout) {
50+
this.client = this.client.newBuilder()
51+
.readTimeout(readTimeout, TimeUnit.MILLISECONDS)
52+
.build();
53+
}
54+
55+
/**
56+
* Set the underlying read timeout in milliseconds.
57+
* A value of 0 specifies an infinite timeout.
58+
*
59+
* @since 6.1
60+
*/
61+
public void setReadTimeout(Duration readTimeout) {
62+
this.client = this.client.newBuilder()
63+
.readTimeout(readTimeout)
64+
.build();
65+
}
66+
67+
/**
68+
* Set the underlying write timeout in milliseconds.
69+
* A value of 0 specifies an infinite timeout.
70+
*/
71+
public void setWriteTimeout(int writeTimeout) {
72+
this.client = this.client.newBuilder()
73+
.writeTimeout(writeTimeout, TimeUnit.MILLISECONDS)
74+
.build();
75+
}
76+
77+
/**
78+
* Set the underlying write timeout in milliseconds.
79+
* A value of 0 specifies an infinite timeout.
80+
*
81+
* @since 6.1
82+
*/
83+
public void setWriteTimeout(Duration writeTimeout) {
84+
this.client = this.client.newBuilder()
85+
.writeTimeout(writeTimeout)
86+
.build();
87+
}
88+
89+
/**
90+
* Set the underlying connect timeout in milliseconds.
91+
* A value of 0 specifies an infinite timeout.
92+
*/
93+
public void setConnectTimeout(int connectTimeout) {
94+
this.client = this.client.newBuilder()
95+
.connectTimeout(connectTimeout, TimeUnit.MILLISECONDS)
96+
.build();
97+
}
98+
99+
/**
100+
* Set the underlying connect timeout in milliseconds.
101+
* A value of 0 specifies an infinite timeout.
102+
*
103+
* @since 6.1
104+
*/
105+
public void setConnectTimeout(Duration connectTimeout) {
106+
this.client = this.client.newBuilder()
107+
.connectTimeout(connectTimeout)
108+
.build();
109+
}
110+
111+
112+
@Override
113+
@SuppressWarnings("removal")
114+
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) {
115+
return new OkHttpClientHttpRequest(this.client, uri, httpMethod);
116+
}
117+
118+
119+
@Override
120+
public void destroy() throws IOException {
121+
if (this.defaultClient) {
122+
// Clean up the client if we created it in the constructor
123+
Cache cache = this.client.cache();
124+
if (cache != null) {
125+
cache.close();
126+
}
127+
this.client.dispatcher().executorService().shutdown();
128+
this.client.connectionPool().evictAll();
129+
}
130+
}
131+
132+
}

0 commit comments

Comments
 (0)