HttpUrlConnectorConfigurationTest.java

/*
 * Copyright (c) 2025 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package org.glassfish.jersey.client;

import org.glassfish.jersey.client.innate.ClientProxy;
import org.glassfish.jersey.client.innate.ConnectorConfiguration;
import org.glassfish.jersey.client.internal.HttpUrlConnector;
import org.glassfish.jersey.client.internal.HttpUrlConnectorConfiguration;
import org.glassfish.jersey.http.HttpHeaders;
import org.glassfish.jersey.internal.MapPropertiesDelegate;
import org.glassfish.jersey.internal.util.JdkVersion;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import javax.net.ssl.SSLContext;
import javax.ws.rs.ProcessingException;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.core.MultivaluedMap;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.ProtocolException;
import java.net.URI;
import java.net.URL;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Supplier;

public class HttpUrlConnectorConfigurationTest {

    public static final String PREFIX = "test.";

    @Test
    public void testConnectTimeout() {
        final AtomicInteger result = new AtomicInteger(0);
        class RWConnect extends Conf.RW {
            @Override
            public int connectTimeout(ClientRequest request) {
                result.set(super.connectTimeout(request));
                throw new IllegalStateException();
            }

            @Override
            public RWConnect instance() {
                return new RWConnect();
            }

        }
        new TestClient(new RWConnect()).rw(rw -> rw.connectTimeout(1000)).request().apply();
        Assertions.assertEquals(1000, result.get());

        result.set(0);
        new TestClient(new RWConnect()).rw(rw -> rw.connectTimeout(1000))
                .client(c -> c.property(ClientProperties.CONNECT_TIMEOUT, 3000))
                .client(c -> c.property(ClientProperties.CONNECTOR_CONFIGURATION, new RWConnect().connectTimeout(2000)))
                .request().apply();
        Assertions.assertEquals(3000, result.get());

        result.set(0);
        new TestClient(new RWConnect()).rw(rw -> rw.connectTimeout(1000))
                .client(c -> c.property(ClientProperties.CONNECT_TIMEOUT, 3000))
                .client(c -> c.property(ClientProperties.CONNECTOR_CONFIGURATION, new RWConnect().connectTimeout(2000)))
                .request(r -> r.setProperty(ClientProperties.CONNECTOR_CONFIGURATION,
                        new RWConnect().connectTimeout(4000)))
                .apply();
        Assertions.assertEquals(3000, result.get());

        result.set(0);
        new TestClient(new RWConnect()).rw(rw -> rw.connectTimeout(1000))
                .client(c -> c.property(ClientProperties.CONNECTOR_CONFIGURATION, new RWConnect().connectTimeout(2000)))
                .request(r -> r.setProperty(ClientProperties.CONNECTOR_CONFIGURATION,
                        new RWConnect().connectTimeout(4000)))
                .apply();
        Assertions.assertEquals(4000, result.get());

        result.set(0);
        new TestClient(new RWConnect()).rw(rw -> rw.connectTimeout(1000))
                .client(c -> c.property(ClientProperties.CONNECTOR_CONFIGURATION, new RWConnect().connectTimeout(2000)))
                .request(r -> r.setProperty(ClientProperties.CONNECTOR_CONFIGURATION,
                        new RWConnect().connectTimeout(4000)))
                .request(r -> r.setProperty(ClientProperties.CONNECT_TIMEOUT, 5000))
                .apply();
        Assertions.assertEquals(5000, result.get());

        result.set(0);
        new TestClient(new RWConnect()).rw(rw -> rw.connectTimeout(1000).prefix(PREFIX))
                .client(c -> c.property(ClientProperties.CONNECTOR_CONFIGURATION,
                        HttpUrlConnectorProvider.config().connectTimeout(2000).prefix(PREFIX)))
                .request(r -> r.setProperty(ClientProperties.CONNECTOR_CONFIGURATION,
                        new RWConnect().connectTimeout(4000)))
                .apply();
        Assertions.assertEquals(2000, result.get());

        result.set(0);
        new TestClient(new RWConnect()).rw(rw -> rw.connectTimeout(1000).prefix(PREFIX))
                .client(c -> c.property(ClientProperties.CONNECTOR_CONFIGURATION, new RWConnect().connectTimeout(2000)))
                .request(r -> r.setProperty(ClientProperties.CONNECTOR_CONFIGURATION,
                        HttpUrlConnectorProvider.config().connectTimeout(4000).prefix(PREFIX)))
                .request(r -> r.setProperty(ClientProperties.CONNECT_TIMEOUT, 5000))
                .apply();
        Assertions.assertEquals(4000, result.get());
    }

    @Test
    public void testFollowRedirects() {
        final AtomicReference<Boolean> result = new AtomicReference<>();
        class RWFollow extends Conf.RW {

            @Override
            public boolean followRedirects(ClientRequest request) {
                result.set(super.followRedirects(request));
                throw new IllegalStateException();
            }

            @Override
            public RWFollow instance() {
                return new RWFollow();
            }
        }

        Request req;
        req = new TestClient(new RWFollow()).request().apply();
        Assertions.assertEquals(((RWFollow) new RWFollow().copy()).followRedirects(), result.get());

        result.set(null);
        req = new TestClient(new RWFollow().followRedirects(false)).request().apply();
        Assertions.assertEquals(false, result.get());

        result.set(null);
        req = new TestClient(new RWFollow().prefix(PREFIX).followRedirects(false))
                .request(r -> r.setProperty(PREFIX + ClientProperties.FOLLOW_REDIRECTS, true))
                .apply();
        Assertions.assertEquals(true, result.get());

        result.set(null);
        req = new TestClient(new RWFollow().prefix(PREFIX).followRedirects(false))
                .request(r -> r.setProperty(
                        PREFIX + ClientProperties.CONNECTOR_CONFIGURATION, new RWFollow().followRedirects(true)))
                .apply();
        Assertions.assertEquals(true, result.get());

        result.set(null);
        req = new TestClient(new RWFollow())
                .client(c -> c.property(ClientProperties.CONNECTOR_CONFIGURATION, new RWFollow().followRedirects(false)))
                .request(r -> r.setProperty(ClientProperties.CONNECTOR_CONFIGURATION,
                        new RWFollow().followRedirects(true)))
                .request(r -> r.setEntity(PREFIX)).apply();
        Assertions.assertEquals(true, result.get());

        result.set(null);
        req = new TestClient(new RWFollow().prefix(PREFIX))
                .client(c -> c.property(PREFIX + ClientProperties.FOLLOW_REDIRECTS, false))
                .request(r -> r.setProperty(PREFIX + ClientProperties.CONNECTOR_CONFIGURATION,
                        new RWFollow().followRedirects(true)))
                .request(r -> r.setEntity(PREFIX)).apply();
        Assertions.assertEquals(false, result.get());
    }

    @Test
    public void testProxy() {
        final AtomicReference<ClientProxy> result = new AtomicReference<>();
        class RWProxy extends Conf.RW {

            @Override
            public Optional<ClientProxy> proxy(ClientRequest request, URI requestUri) {
                Optional<ClientProxy> proxy = super.proxy(request, requestUri);
                result.set(proxy.orElse(null));
                throw new IllegalStateException();
            }

            @Override
            public RWProxy instance() {
                return new RWProxy();
            }
        }
        String proxyUri = "http://proxy.org:8080";
        String userName = "USERNAME";
        String password = "PASSWORD";

        new TestClient(new RWProxy()).request().apply();
        Assertions.assertNull(result.get());

        result.set(null);
        new TestClient(new RWProxy()).rw(rw -> rw.proxyUri(proxyUri).proxyUserName(userName).proxyPassword(password))
                .request().apply();
        Assertions.assertEquals(proxyUri, result.get().uri().toASCIIString());
        Assertions.assertEquals(userName, result.get().userName());
        Assertions.assertEquals(password, result.get().password());

        result.set(null);
        new TestClient(new RWProxy()).rw(rw -> rw.prefix(PREFIX).proxyUri(proxyUri).proxyUserName("XXX").proxyPassword(password))
                .request(r -> r.setProperty(PREFIX + ClientProperties.PROXY_USERNAME, userName))
                .request(r -> r.setProperty(ClientProperties.PROXY_PASSWORD, "XXX"))
                .apply();
        Assertions.assertEquals(proxyUri, result.get().uri().toASCIIString());
        Assertions.assertEquals(userName, result.get().userName());
        Assertions.assertEquals(password, result.get().password());

        result.set(null);
        new TestClient(new RWProxy()).rw(rw -> rw.prefix(PREFIX).proxyUri(proxyUri).proxyUserName("XXX").proxyPassword(password))
                .request(r -> r.setProperty(PREFIX + ClientProperties.CONNECTOR_CONFIGURATION,
                        HttpUrlConnectorProvider.config().proxyUserName(userName).proxyPassword(null)))
                .apply();
        Assertions.assertEquals(proxyUri, result.get().uri().toASCIIString());
        Assertions.assertEquals(userName, result.get().userName());
        Assertions.assertNull(result.get().password());

        result.set(null);
        new TestClient(new RWProxy().prefix(PREFIX)
                .proxy(new java.net.Proxy(java.net.Proxy.Type.HTTP,
                        new InetSocketAddress(proxyUri.split("g:")[0] + "g", Integer.parseInt(proxyUri.split("g:")[1])))))
                .request(r -> r.setProperty(ClientProperties.CONNECTOR_CONFIGURATION,
                        HttpUrlConnectorProvider.config().proxyUserName(userName).proxyPassword(password).prefix(PREFIX)))
                .apply();
        Assertions.assertEquals(proxyUri, result.get().uri().toASCIIString());
        Assertions.assertEquals(userName, result.get().userName());
        Assertions.assertEquals(password, result.get().password());
    }

    @Test
    public void testReadTimeout() {
        final AtomicInteger result = new AtomicInteger(0);
        class RWRead extends Conf.RW {

            @Override
            public RWRead readTimeout(ClientRequest clientRequest) {
                super.readTimeout(clientRequest);
                result.set(readTimeout());
                throw new IllegalStateException();
            }

            @Override
            public RWRead instance() {
                return new RWRead();
            }
        }
        new TestClient(new RWRead()).rw(rw -> rw.readTimeout(1000)).request().apply();
        Assertions.assertEquals(1000, result.get());

        result.set(0);
        new TestClient(new RWRead()).rw(rw -> rw.readTimeout(1000))
                .client(c -> c.property(ClientProperties.READ_TIMEOUT, 3000))
                .client(c -> c.property(ClientProperties.CONNECTOR_CONFIGURATION, new RWRead().readTimeout(2000)))
                .request().apply();
        Assertions.assertEquals(3000, result.get());

        result.set(0);
        new TestClient(new RWRead()).rw(rw -> rw.readTimeout(1000))
                .client(c -> c.property(ClientProperties.READ_TIMEOUT, 3000))
                .client(c -> c.property(ClientProperties.CONNECTOR_CONFIGURATION, new RWRead().connectTimeout(2000)))
                .request(r -> r.setProperty(ClientProperties.CONNECTOR_CONFIGURATION, new RWRead().readTimeout(4000)))
                .apply();
        Assertions.assertEquals(3000, result.get());

        result.set(0);
        new TestClient(new RWRead()).rw(rw -> rw.readTimeout(1000))
                .client(c -> c.property(ClientProperties.CONNECTOR_CONFIGURATION, new RWRead().readTimeout(2000)))
                .request(r -> r.setProperty(ClientProperties.CONNECTOR_CONFIGURATION, new RWRead().readTimeout(4000)))
                .apply();
        Assertions.assertEquals(4000, result.get());

        result.set(0);
        new TestClient(new RWRead()).rw(rw -> rw.readTimeout(1000))
                .client(c -> c.property(ClientProperties.CONNECTOR_CONFIGURATION, new RWRead().readTimeout(2000)))
                .request(r -> r.setProperty(ClientProperties.CONNECTOR_CONFIGURATION, new RWRead().readTimeout(4000)))
                .request(r -> r.setProperty(ClientProperties.READ_TIMEOUT, 5000))
                .apply();
        Assertions.assertEquals(5000, result.get());

        result.set(0);
        new TestClient(new RWRead()).rw(rw -> rw.readTimeout(1000).prefix(PREFIX))
                .client(c -> c.property(ClientProperties.CONNECTOR_CONFIGURATION,
                        HttpUrlConnectorProvider.config().readTimeout(2000).prefix(PREFIX)))
                .request(r -> r.setProperty(ClientProperties.CONNECTOR_CONFIGURATION,
                        new RWRead().readTimeout(4000)))
                .apply();
        Assertions.assertEquals(2000, result.get());

        result.set(0);
        new TestClient(new RWRead()).rw(rw -> rw.readTimeout(1000).prefix(PREFIX))
                .client(c -> c.property(ClientProperties.CONNECTOR_CONFIGURATION, new RWRead().readTimeout(2000)))
                .request(r -> r.setProperty(ClientProperties.CONNECTOR_CONFIGURATION,
                        HttpUrlConnectorProvider.config().readTimeout(4000).prefix(PREFIX)))
                .request(r -> r.setProperty(ClientProperties.READ_TIMEOUT, 5000))
                .apply();
        Assertions.assertEquals(4000, result.get());
    }

    @Test
    public void testRequestEntityProcessing() {
        final AtomicReference<RequestEntityProcessing> result = new AtomicReference<>();
        final AtomicReference<Conf.RW> config = new AtomicReference<>();
        class RWRP extends Conf.RW {

            @Override
            public RequestEntityProcessing requestEntityProcessing(ClientRequest request) {
                result.set(super.requestEntityProcessing(request));
                throw new IllegalStateException();
            }

            @Override
            public RWRP instance() {
                config.set(new RWRP());
                return (RWRP) config.get();
            }
        }

        Request req;
        req = new TestClient(new RWRP().requestEntityProcessing(RequestEntityProcessing.CHUNKED))
                .request(r -> r.setEntity(PREFIX)).apply();
        Assertions.assertEquals(RequestEntityProcessing.CHUNKED, result.get());

        result.set(null);
        req = new TestClient(new RWRP().prefix(PREFIX).requestEntityProcessing(RequestEntityProcessing.CHUNKED))
                .request(r -> r.setEntity(PREFIX))
                .request(r -> r.setProperty(ClientProperties.CONNECTOR_CONFIGURATION,
                        HttpUrlConnectorProvider.config()
                                .requestEntityProcessing(RequestEntityProcessing.BUFFERED).prefix(PREFIX)))
                .apply();
        Assertions.assertEquals(RequestEntityProcessing.BUFFERED, result.get());

        result.set(null);
        req = new TestClient(new RWRP().prefix(PREFIX).requestEntityProcessing(RequestEntityProcessing.CHUNKED))
                .request(r -> r.setEntity(PREFIX))
                .request(r -> r.setProperty(PREFIX + ClientProperties.REQUEST_ENTITY_PROCESSING,
                        RequestEntityProcessing.BUFFERED))
                .apply();
        Assertions.assertEquals(RequestEntityProcessing.BUFFERED, result.get());
    }

    @Test
    public void testSslContext() {
        final SSLContext testContext = new SSLContext(null, null, null){};
        final AtomicReference<SSLContext> result = new AtomicReference<>();
        final AtomicReference<Conf.RW> config = new AtomicReference<>();
        class RWSsl extends Conf.RW {

            @Override
            public SSLContext sslContext(Client client, ClientRequest request) {
                result.set(super.sslContext(client, request));
                return result.get();
            }

            @Override
            public boolean isSslContextSupplier() {
                throw new IllegalStateException();
            }

            @Override
            public RWSsl instance() {
                RWSsl rw = new RWSsl();
                config.set(rw);
                return rw;
            }
        }

        Request req;
        req = new TestClient(new RWSsl().sslContextSupplier(() -> testContext)).request().apply();
        config.get().sslContext(req.client, req.request);
        Assertions.assertEquals(testContext, result.get());

        result.set(null);
        req = new TestClient(new RWSsl().prefix(PREFIX))
                .request(r -> r.setProperty(PREFIX + ClientProperties.SSL_CONTEXT_SUPPLIER,
                        (Supplier<SSLContext>) () -> testContext)).apply();
        config.get().sslContext(req.client, req.request);
        Assertions.assertEquals(testContext, result.get());

        result.set(null);
        req = new TestClient(new RWSsl().prefix(PREFIX))
                .request(r -> r.setProperty(ClientProperties.CONNECTOR_CONFIGURATION,
                        new RWSsl().prefix(PREFIX).sslContextSupplier(() -> testContext))).apply();
        config.get().sslContext(req.client, req.request);
        Assertions.assertEquals(testContext, result.get());
    }

    @Test
    public void testConnectionFactory() {
        final AtomicReference<Boolean> result = new AtomicReference<>();
        final HttpUrlConnectorProvider.ConnectionFactory connectionFactory = new HttpUrlConnectorProvider.ConnectionFactory() {
            @Override
            public HttpURLConnection getConnection(URL url) throws IOException {
                result.set(true);
                throw new IllegalStateException();
            }
        };

        new TestClient(HttpUrlConnectorProvider.config().connectionFactory(connectionFactory)).request().apply();
        Assertions.assertTrue(result.get());

        result.set(null);
        new TestClient(HttpUrlConnectorProvider.config().prefix(PREFIX))
                .client(c -> c.property(ClientProperties.CONNECTOR_CONFIGURATION,
                        HttpUrlConnectorProvider.config().connectionFactory(connectionFactory)))
                .request().apply();
        Assertions.assertNull(result.get());

        result.set(null);
        new TestClient(HttpUrlConnectorProvider.config().prefix(PREFIX))
                .client(c -> c.property(ClientProperties.CONNECTOR_CONFIGURATION,
                        HttpUrlConnectorProvider.config().connectionFactory(connectionFactory).prefix(PREFIX)))
                .request().apply();
        Assertions.assertTrue(result.get());

        result.set(null);
        new TestClient(HttpUrlConnectorProvider.config().prefix(PREFIX))
                .request(r -> r.setProperty(ClientProperties.CONNECTOR_CONFIGURATION,
                        HttpUrlConnectorProvider.config().connectionFactory(connectionFactory).prefix(PREFIX)))
                .apply();
        Assertions.assertTrue(result.get());

        result.set(null);
        Client client = ClientBuilder.newClient();
        try {
            HttpUrlConnectorProvider.config().build()
                    .connectionFactory(connectionFactory)
                    .getConnector(client, client.getConfiguration()).apply(Request.createRequest(client));
        } catch (IllegalStateException ise) {
            // expected
        }
        Assertions.assertTrue(result.get());
    }

    @Test
    public void testChunkSize() {
        final AtomicReference<Integer> result = new AtomicReference<>();
        final HttpUrlConnectorProvider.ConnectionFactory connectionFactory = new HttpUrlConnectorProvider.ConnectionFactory() {
            @Override
            public HttpURLConnection getConnection(URL url) throws IOException {
                return new HttpURLConnection(url) {
                    @Override
                    public void disconnect() {

                    }

                    @Override
                    public boolean usingProxy() {
                        return false;
                    }

                    @Override
                    public void connect() throws IOException {

                    }

                    @Override
                    public void setChunkedStreamingMode(int chunklen) {
                        result.set(chunklen);
                        throw new IllegalStateException();
                    }
                };
            }
        };

        new TestClient(HttpUrlConnectorProvider.config()
                .requestEntityProcessing(RequestEntityProcessing.CHUNKED)
                .connectionFactory(connectionFactory))
                .request(r -> r.setEntity(PREFIX)).apply();
        Assertions.assertEquals(ClientProperties.DEFAULT_CHUNK_SIZE, result.get());

        result.set(null);
        new TestClient(HttpUrlConnectorProvider.config()
                .requestEntityProcessing(RequestEntityProcessing.CHUNKED)
                .connectionFactory(connectionFactory)
                .chunkSize(1000))
                .request(r -> r.setEntity(PREFIX)).apply();
        Assertions.assertEquals(1000, result.get());

        result.set(null);
        new TestClient(HttpUrlConnectorProvider.config()
                .requestEntityProcessing(RequestEntityProcessing.CHUNKED)
                .connectionFactory(connectionFactory)
                .chunkSize(1000)
                .prefix(PREFIX))
                .client(c -> c.property(PREFIX + ClientProperties.CHUNKED_ENCODING_SIZE, 2000))
                .request(r -> r.setEntity(PREFIX)).apply();
        Assertions.assertEquals(2000, result.get());

        result.set(null);
        new TestClient(HttpUrlConnectorProvider.config()
                .requestEntityProcessing(RequestEntityProcessing.CHUNKED)
                .connectionFactory(connectionFactory)
                .chunkSize(1000)
                .prefix(PREFIX))
                .request(r -> r.setProperty(PREFIX + ClientProperties.CHUNKED_ENCODING_SIZE, 2000))
                .request(r -> r.setEntity(PREFIX)).apply();
        Assertions.assertEquals(2000, result.get());

        result.set(null);
        new TestClient(HttpUrlConnectorProvider.config()
                .requestEntityProcessing(RequestEntityProcessing.CHUNKED)
                .connectionFactory(connectionFactory)
                .chunkSize(1000)
                .prefix(PREFIX))
                .client(c -> c.property(ClientProperties.CONNECTOR_CONFIGURATION,
                        HttpUrlConnectorProvider.config().chunkSize(2000).prefix(PREFIX)))
                .request(r -> r.setEntity(PREFIX)).apply();
        Assertions.assertEquals(2000, result.get());

        result.set(null);
        new TestClient(HttpUrlConnectorProvider.config()
                .requestEntityProcessing(RequestEntityProcessing.CHUNKED)
                .connectionFactory(connectionFactory)
                .chunkSize(1000)
                .prefix(PREFIX))
                .request(r -> r.setProperty(ClientProperties.CONNECTOR_CONFIGURATION,
                        HttpUrlConnectorProvider.config().chunkSize(2000).prefix(PREFIX)))
                .request(r -> r.setEntity(PREFIX)).apply();
        Assertions.assertEquals(2000, result.get());
    }

    @Test
    public void testUseFixedLengthStreaming() {
        final AtomicReference<Integer> result = new AtomicReference<>();
        final class FixedLengthStreamingUrlConnection extends HttpURLConnection {
            private FixedLengthStreamingUrlConnection(URL u) {
                super(u);
            }

            @Override
            public void disconnect() {

            }

            @Override
            public boolean usingProxy() {
                return false;
            }

            @Override
            public void connect() throws IOException {

            }

            @Override
            public void setFixedLengthStreamingMode(long contentLength) {
                super.setFixedLengthStreamingMode(contentLength);
                result.set((int) contentLength);
                throw new IllegalStateException();
            }
        }
        final HttpUrlConnectorProvider.ConnectionFactory connectionFactory = new HttpUrlConnectorProvider.ConnectionFactory() {
            @Override
            public HttpURLConnection getConnection(URL url) throws IOException {
                return new FixedLengthStreamingUrlConnection(url);
            }
        };

        new TestClient(HttpUrlConnectorProvider.config()
                .connectionFactory(connectionFactory))
                .request(r -> r.getRequestHeaders().add(HttpHeaders.CONTENT_LENGTH, String.valueOf(PREFIX.length())))
                .request(r -> r.setEntity(PREFIX)).apply();
        Assertions.assertNull(result.get());

        result.set(null);
        new TestClient(HttpUrlConnectorProvider.config()
                .connectionFactory(connectionFactory)
                .useFixedLengthStreaming(true))
                .request(r -> r.getRequestHeaders().add(HttpHeaders.CONTENT_LENGTH, String.valueOf(PREFIX.length())))
                .request(r -> r.setEntity(PREFIX)).apply();
        Assertions.assertEquals(PREFIX.length(), result.get());

        result.set(null);
        new TestClient(HttpUrlConnectorProvider.config()
                .connectionFactory(connectionFactory)
                .useFixedLengthStreaming(false)
                .prefix(PREFIX))
                .client(c -> c.property(PREFIX + HttpUrlConnectorProvider.USE_FIXED_LENGTH_STREAMING, true))
                .request(r -> r.getRequestHeaders().add(HttpHeaders.CONTENT_LENGTH, String.valueOf(PREFIX.length())))
                .request(r -> r.setEntity(PREFIX)).apply();
        Assertions.assertEquals(PREFIX.length(), result.get());

        result.set(null);
        new TestClient(HttpUrlConnectorProvider.config()
                .connectionFactory(connectionFactory)
                .useFixedLengthStreaming(false)
                .prefix(PREFIX))
                .request(r -> r.setProperty(PREFIX + HttpUrlConnectorProvider.USE_FIXED_LENGTH_STREAMING, true))
                .request(r -> r.getRequestHeaders().add(HttpHeaders.CONTENT_LENGTH, String.valueOf(PREFIX.length())))
                .request(r -> r.setEntity(PREFIX)).apply();
        Assertions.assertEquals(PREFIX.length(), result.get());

        result.set(null);
        new TestClient(HttpUrlConnectorProvider.config()
                .connectionFactory(connectionFactory)
                .useFixedLengthStreaming(false)
                .prefix(PREFIX))
                .client(c -> c.property(ClientProperties.CONNECTOR_CONFIGURATION,
                        HttpUrlConnectorProvider.config().useFixedLengthStreaming(true).prefix(PREFIX)))
                .request(r -> r.getRequestHeaders().add(HttpHeaders.CONTENT_LENGTH, String.valueOf(PREFIX.length())))
                .request(r -> r.setEntity(PREFIX)).apply();
        Assertions.assertEquals(PREFIX.length(), result.get());

        result.set(null);
        new TestClient(HttpUrlConnectorProvider.config()
                .connectionFactory(connectionFactory)
                .useFixedLengthStreaming(false)
                .prefix(PREFIX))
                .request(r -> r.setProperty(ClientProperties.CONNECTOR_CONFIGURATION,
                        HttpUrlConnectorProvider.config().useFixedLengthStreaming(true).prefix(PREFIX)))
                .request(r -> r.getRequestHeaders().add(HttpHeaders.CONTENT_LENGTH, String.valueOf(PREFIX.length())))
                .request(r -> r.setEntity(PREFIX)).apply();
        Assertions.assertEquals(PREFIX.length(), result.get());
    }

    @Test
    public void testUseMethodWorkaround() {
        JdkVersion version = JdkVersion.getJdkVersion();
        if (!version.isReflectiveAccessToJdkSupported()) {
            return;
        }
        final String method = "XYZ";
        final AtomicReference<HttpURLConnection> result = new AtomicReference<>();
        final HttpUrlConnectorProvider.ConnectionFactory connectionFactory = new HttpUrlConnectorProvider.ConnectionFactory() {
            @Override
            public HttpURLConnection getConnection(URL url) throws IOException {
                return new HttpURLConnection(url) {
                    @Override
                    public void disconnect() {

                    }

                    @Override
                    public boolean usingProxy() {
                        return false;
                    }

                    @Override
                    public void connect() throws IOException {

                    }

                    @Override
                    public void setRequestMethod(String method) throws ProtocolException {
                        result.set(this);
                        super.setRequestMethod(method);
                        throw new IllegalStateException();
                    }
                };
            }
        };

        new TestClient(HttpUrlConnectorProvider.config()
                .connectionFactory(connectionFactory))
                .request(r -> r.setMethod(method))
                .apply();
        Assertions.assertNotEquals(method, result.get().getRequestMethod());

        new TestClient(HttpUrlConnectorProvider.config()
                .connectionFactory(connectionFactory)
                .useSetMethodWorkaround(true))
                .request(r -> r.setMethod(method))
                .apply();
        Assertions.assertEquals(method, result.get().getRequestMethod());

        HttpUrlConnectorProvider.Config config = HttpUrlConnectorProvider.config();
        config.build().useSetMethodWorkaround();
        new TestClient(config
                .connectionFactory(connectionFactory))
                .request(r -> r.setMethod(method))
                .apply();
        Assertions.assertEquals(method, result.get().getRequestMethod());

        new TestClient(HttpUrlConnectorProvider.config().prefix(PREFIX)
                .connectionFactory(connectionFactory))
                .request(r -> r.setMethod(method))
                .request(r -> r.setProperty(ClientProperties.CONNECTOR_CONFIGURATION,
                        HttpUrlConnectorProvider.config().useSetMethodWorkaround(true).prefix(PREFIX)))
                .apply();
        Assertions.assertEquals(method, result.get().getRequestMethod());

        new TestClient(HttpUrlConnectorProvider.config().prefix(PREFIX)
                .connectionFactory(connectionFactory))
                .client(c -> c.property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true))
                .request(r -> r.setMethod(method))
                .apply();
        Assertions.assertNotEquals(method, result.get().getRequestMethod());

        new TestClient(HttpUrlConnectorProvider.config().prefix(PREFIX)
                .connectionFactory(connectionFactory))
                .client(c -> c.property(PREFIX + HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true))
                .request(r -> r.setMethod(method))
                .apply();
        Assertions.assertEquals(method, result.get().getRequestMethod());
    }

    private static class Conf extends HttpUrlConnectorConfiguration<Conf> {
        private static class RW extends ReadWrite {
            @Override
            public <X extends ConnectorConfiguration<?>> void setNonEmpty(X otherC) {
                super.setNonEmpty(otherC);
            }

            @Override
            public RW prefix(String prefix) {
                super.prefix(prefix);
                return this;
            }
        }
    }

    private static class TestClient {
        final Conf.RW rw;
        final Client client;

        private TestClient(ConnectorConfiguration<?> config) {
            this.rw = config instanceof Conf.RW ? (Conf.RW) config : new Conf.RW() {
                {
                    setNonEmpty(config);
                }
            };
            this.client = ClientBuilder.newClient(new ClientConfig());
        }

        public TestClient client(Consumer<Client> consumer) {
            consumer.accept(client);
            return this;
        }

        public TestClient rw(Consumer<HttpUrlConnectorConfiguration<?>> consumer) {
            consumer.accept(rw);
            return this;
        }

        public Request request() {
            return new Request(client, rw);
        }

        public Request request(Consumer<ClientRequest> consumer) {
            return request().request(consumer);
        }
    }

    private static class Request {
        final ClientRequest request;
        final Client client;
        final Conf.RW rw;

        Request(Client client, Conf.RW rw) {
            this.client = client;
            this.rw = rw;
            request = createRequest(client);
        }

        private static ClientRequest createRequest(Client client) {
            ClientRequest request =  new ClientRequest(URI.create("http://localhost:8080"),
                    (ClientConfig) client.getConfiguration(), new MapPropertiesDelegate()) {
                @Override
                public void writeEntity() throws IOException {
                    throw new IllegalStateException();
                }

                @Override
                public MultivaluedMap<String, String> getStringHeaders() {
                    throw new IllegalStateException();
                }
            };
            request.setMethod("POST");
            return request;
        }

        public Request request(Consumer<ClientRequest> consumer) {
            consumer.accept(request);
            return this;
        }

        public Request apply() {
            try {
                HttpUrlConnector connector = new HttpUrlConnector(client, client.getConfiguration(), rw);
                connector.apply(request);
            } catch (ProcessingException | IllegalStateException expected) {
            }
            return this;
        }
    }
}