MicroProfile Rest Client 2.0 support (#4699)

* JERSEY-4654 MP Rest Client 2.0 - QueryParamStyle,  FOLLOW_REDIRECTS, PROXY_ADDRESS, SSE support

Signed-off-by: Gaurav Gupta <gaurav.gupta@payara.fish>
diff --git a/core-client/src/main/java/org/glassfish/jersey/client/ClientProperties.java b/core-client/src/main/java/org/glassfish/jersey/client/ClientProperties.java
index c0e8515..55576ea 100644
--- a/core-client/src/main/java/org/glassfish/jersey/client/ClientProperties.java
+++ b/core-client/src/main/java/org/glassfish/jersey/client/ClientProperties.java
@@ -451,6 +451,18 @@
      */
     public static final Long DEFAULT_EXPECT_100_CONTINUE_THRESHOLD_SIZE = 65536L;
 
+    /**
+     * The property defines the desired format of query param when multiple
+     * values are sent for the same parameter.
+     *
+     * <p>
+     * The value MUST be an instance of
+     * {@link org.glassfish.jersey.uri.QueryParamStyle}.</p>
+     * <p>
+     * The default value is {@code null}.</p>
+     */
+    public static final String QUERY_PARAM_STYLE = "jersey.config.client.uri.query.param.style";
+
     private ClientProperties() {
         // prevents instantiation
     }
diff --git a/core-client/src/main/java/org/glassfish/jersey/client/JerseyWebTarget.java b/core-client/src/main/java/org/glassfish/jersey/client/JerseyWebTarget.java
index ed986d7..833ba06 100644
--- a/core-client/src/main/java/org/glassfish/jersey/client/JerseyWebTarget.java
+++ b/core-client/src/main/java/org/glassfish/jersey/client/JerseyWebTarget.java
@@ -26,6 +26,8 @@
 import javax.ws.rs.core.UriBuilder;
 
 import org.glassfish.jersey.internal.guava.Preconditions;
+import org.glassfish.jersey.uri.JerseyQueryParamStyle;
+import org.glassfish.jersey.uri.internal.JerseyUriBuilder;
 
 /**
  * Jersey implementation of {@link javax.ws.rs.client.WebTarget JAX-RS client target}
@@ -146,7 +148,13 @@
     @Override
     public JerseyWebTarget queryParam(String name, Object... values) throws NullPointerException {
         checkNotClosed();
-        return new JerseyWebTarget(JerseyWebTarget.setQueryParam(getUriBuilder(), name, values), this);
+        UriBuilder uriBuilder = getUriBuilder();
+        if (uriBuilder instanceof JerseyUriBuilder) {
+            ((JerseyUriBuilder) uriBuilder).setQueryParamStyle((JerseyQueryParamStyle) this.getConfiguration()
+                            .getProperty(ClientProperties.QUERY_PARAM_STYLE)
+            );
+        }
+        return new JerseyWebTarget(JerseyWebTarget.setQueryParam(uriBuilder, name, values), this);
     }
 
     private static UriBuilder setQueryParam(UriBuilder uriBuilder, String name, Object[] values) {
diff --git a/core-common/src/main/java/org/glassfish/jersey/uri/JerseyQueryParamStyle.java b/core-common/src/main/java/org/glassfish/jersey/uri/JerseyQueryParamStyle.java
new file mode 100644
index 0000000..8fba07f
--- /dev/null
+++ b/core-common/src/main/java/org/glassfish/jersey/uri/JerseyQueryParamStyle.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2021 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.uri;
+
+/**
+ * JerseyQueryParamStyle is used to specify the desired format of query param
+ * when multiple values are sent for the same parameter.
+ */
+public enum JerseyQueryParamStyle {
+
+    /**
+     * Multiple parameter instances, e.g.:
+     * <code>foo=v1&amp;foot=v2&amp;foo=v3</code>
+     *
+     * This is the default if no style is configured.
+     */
+    MULTI_PAIRS,
+
+    /** A single parameter instance with multiple, comma-separated values, e.g.:
+     * <code>foo=v1,v2,v3</code>
+     */
+    COMMA_SEPARATED,
+
+    /**
+     * Multiple parameter instances with square brackets for each parameter, e.g.:
+     * <code>foo[]=v1&amp;foo[]=v2&amp;foo[]=v3</code>
+     */
+    ARRAY_PAIRS
+}
\ No newline at end of file
diff --git a/core-common/src/main/java/org/glassfish/jersey/uri/internal/JerseyUriBuilder.java b/core-common/src/main/java/org/glassfish/jersey/uri/internal/JerseyUriBuilder.java
index e58dfd3..1620803 100644
--- a/core-common/src/main/java/org/glassfish/jersey/uri/internal/JerseyUriBuilder.java
+++ b/core-common/src/main/java/org/glassfish/jersey/uri/internal/JerseyUriBuilder.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 2021 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
@@ -34,6 +34,7 @@
 import org.glassfish.jersey.internal.guava.InetAddresses;
 import org.glassfish.jersey.internal.util.ReflectionHelper;
 import org.glassfish.jersey.internal.util.collection.MultivaluedStringMap;
+import org.glassfish.jersey.uri.JerseyQueryParamStyle;
 import org.glassfish.jersey.uri.UriComponent;
 import org.glassfish.jersey.uri.UriTemplate;
 
@@ -68,6 +69,8 @@
 
     private MultivaluedMap<String, String> queryParams;
 
+    private JerseyQueryParamStyle queryParamStyle;
+
     private String fragment;
 
     /**
@@ -536,6 +539,31 @@
         }
 
         name = encode(name, UriComponent.Type.QUERY_PARAM);
+        if (null == queryParamStyle) {
+            clientQueryParamMultiPairs(name, values);
+        } else switch (queryParamStyle) {
+            case ARRAY_PAIRS:
+                clientQueryParamArrayPairs(name, values);
+                break;
+            case COMMA_SEPARATED:
+                clientQueryParamCommaSeparated(name, values);
+                break;
+            default:
+                clientQueryParamMultiPairs(name, values);
+                break;
+        }
+        return this;
+    }
+
+    /**
+     * Multiple parameter instances, e.g foo=v1&amp;foot=v2&amp;foo=v3 This is
+     * the default if no style is configured.
+     *
+     * @param name
+     * @param values
+     * @throws IllegalArgumentException
+     */
+    private void clientQueryParamMultiPairs(String name, final Object... values) {
         if (queryParams == null) {
             for (final Object value : values) {
                 if (query.length() > 0) {
@@ -558,7 +586,91 @@
                 queryParams.add(name, encode(value.toString(), UriComponent.Type.QUERY_PARAM));
             }
         }
-        return this;
+    }
+
+    /**
+     * A single parameter instance with multiple, comma-separated values, e.g
+     * key=value1,value2,value3.
+     *
+     * @param name
+     * @param values
+     * @throws IllegalArgumentException
+     */
+    private void clientQueryParamCommaSeparated(String name, final Object... values) throws IllegalArgumentException {
+        StringBuilder sb = new StringBuilder();
+        if (queryParams == null) {
+            if (query.length() > 0) {
+                query.append('&');
+            }
+            query.append(name);
+            int valuesCount = values.length - 1;
+            for (final Object value : values) {
+                if (value == null) {
+                    throw new IllegalArgumentException(LocalizationMessages.QUERY_PARAM_NULL());
+                }
+                sb.append(encode(value.toString(), UriComponent.Type.QUERY_PARAM));
+                if (valuesCount > 0) {
+                    sb.append(",");
+                    --valuesCount;
+                }
+            }
+            query.append('=').append(sb.toString());
+        } else {
+            int valuesCount = values.length - 1;
+            for (final Object value : values) {
+                if (value == null) {
+                    throw new IllegalArgumentException(LocalizationMessages.QUERY_PARAM_NULL());
+                }
+                sb.append(encode(value.toString(), UriComponent.Type.QUERY_PARAM));
+                if (valuesCount > 0) {
+                    sb.append(",");
+                    --valuesCount;
+                }
+            }
+            queryParams.add(name, sb.toString());
+        }
+
+    }
+
+    /**
+     * Multiple parameter instances with square brackets for each parameter, e.g
+     * key[]=value1&key[]=value2&key[]=value3.
+     *
+     * @param name
+     * @param values
+     * @throws IllegalArgumentException
+     */
+    private void clientQueryParamArrayPairs(String name, final Object... values) throws IllegalArgumentException {
+        if (queryParams == null) {
+            for (final Object value : values) {
+                if (query.length() > 0) {
+                    query.append('&');
+                }
+                query.append(name).append("[]");
+
+                if (value == null) {
+                    throw new IllegalArgumentException(LocalizationMessages.QUERY_PARAM_NULL());
+                }
+
+                query.append('=').append(encode(value.toString(), UriComponent.Type.QUERY_PARAM));
+            }
+        } else {
+            for (final Object value : values) {
+                if (value == null) {
+                    throw new IllegalArgumentException(LocalizationMessages.QUERY_PARAM_NULL());
+                }
+
+                queryParams.add(name + "[]", encode(value.toString(), UriComponent.Type.QUERY_PARAM));
+            }
+        }
+    }
+
+    public JerseyQueryParamStyle getQueryParamStyle() {
+        return queryParamStyle;
+    }
+
+    public void setQueryParamStyle(JerseyQueryParamStyle queryParamStyle) {
+        this.queryParamStyle = queryParamStyle;
     }
 
     @Override
diff --git a/ext/microprofile/mp-config/pom.xml b/ext/microprofile/mp-config/pom.xml
index e3b13fe..16d5eb7 100644
--- a/ext/microprofile/mp-config/pom.xml
+++ b/ext/microprofile/mp-config/pom.xml
@@ -34,7 +34,7 @@
         <dependency>
             <groupId>org.eclipse.microprofile.config</groupId>
             <artifactId>microprofile-config-api</artifactId>
-            <version>${config.version}</version>
+            <version>${microprofile.config.version}</version>
         </dependency>
 
         <dependency>
diff --git a/ext/microprofile/mp-rest-client/pom.xml b/ext/microprofile/mp-rest-client/pom.xml
index 8314836..a130075 100644
--- a/ext/microprofile/mp-rest-client/pom.xml
+++ b/ext/microprofile/mp-rest-client/pom.xml
@@ -17,8 +17,8 @@
 
 -->
 <project xmlns="http://maven.apache.org/POM/4.0.0"
-        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <parent>
         <artifactId>project</artifactId>
         <groupId>org.glassfish.jersey.ext.microprofile</groupId>
@@ -32,7 +32,7 @@
         <dependency>
             <groupId>org.eclipse.microprofile.rest.client</groupId>
             <artifactId>microprofile-rest-client-api</artifactId>
-            <version>1.4.1</version>
+            <version>2.0</version>
         </dependency>
         <dependency>
             <groupId>org.eclipse.microprofile.config</groupId>
@@ -83,10 +83,20 @@
             <groupId>org.glassfish</groupId>
             <artifactId>jsonp-jaxrs</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.reactivestreams</groupId>
+            <artifactId>reactive-streams</artifactId>
+            <version>1.0.3</version>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.jersey.media</groupId>
+            <artifactId>jersey-media-sse</artifactId>
+            <version>${project.version}</version>
+        </dependency>
     </dependencies>
 
     <build>
-       <plugins>
+        <plugins>
             <plugin>
                 <groupId>org.apache.felix</groupId>
                 <artifactId>maven-bundle-plugin</artifactId>
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/QueryParamModel.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/QueryParamModel.java
index ff9c4d5..f430741 100644
--- a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/QueryParamModel.java
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/QueryParamModel.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2021 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
@@ -17,6 +17,7 @@
 package org.glassfish.jersey.microprofile.restclient;
 
 import java.lang.annotation.Annotation;
+import java.util.Collection;
 import java.util.Map;
 
 import javax.ws.rs.QueryParam;
@@ -43,8 +44,10 @@
         Object resolvedValue = interfaceModel.resolveParamValue(instance, parameter);
         if (resolvedValue instanceof Object[]) {
             requestPart.put(queryParamName, (Object[]) resolvedValue);
+        } else if (resolvedValue instanceof Collection) {
+            requestPart.put(queryParamName, ((Collection) resolvedValue).toArray());
         } else {
-            requestPart.put(queryParamName, new Object[] {resolvedValue});
+            requestPart.put(queryParamName, new Object[]{resolvedValue});
         }
         return requestPart;
     }
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientBuilderImpl.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientBuilderImpl.java
index 16abf1f..8a2b47b 100644
--- a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientBuilderImpl.java
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientBuilderImpl.java
@@ -1,6 +1,6 @@
 /*
- * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2019 Payara Foundation and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2021 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2021 Payara Foundation 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
@@ -56,9 +56,11 @@
 import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
 import org.eclipse.microprofile.rest.client.ext.AsyncInvocationInterceptor;
 import org.eclipse.microprofile.rest.client.ext.AsyncInvocationInterceptorFactory;
+import org.eclipse.microprofile.rest.client.ext.QueryParamStyle;
 import org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper;
 import org.eclipse.microprofile.rest.client.spi.RestClientListener;
 import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.client.ClientProperties;
 import org.glassfish.jersey.client.Initializable;
 import org.glassfish.jersey.client.spi.ConnectorProvider;
 import org.glassfish.jersey.ext.cdi1x.internal.CdiUtil;
@@ -66,6 +68,7 @@
 import org.glassfish.jersey.internal.inject.InjectionManager;
 import org.glassfish.jersey.internal.inject.InjectionManagerSupplier;
 import org.glassfish.jersey.internal.util.ReflectionHelper;
+import org.glassfish.jersey.uri.JerseyQueryParamStyle;
 
 /**
  * Rest client builder implementation. Creates proxy instance of requested interface.
@@ -88,7 +91,7 @@
     private final Config config;
     private final ConfigWrapper configWrapper;
     private URI uri;
-    private ClientBuilder clientBuilder;
+    private final ClientBuilder clientBuilder;
     private Supplier<ExecutorService> executorService;
     private HostnameVerifier sslHostnameVerifier;
     private SSLContext sslContext;
@@ -96,6 +99,7 @@
     private KeyStore sslKeyStore;
     private char[] sslKeyStorePassword;
     private ConnectorProvider connector;
+    private boolean followRedirects;
 
     RestClientBuilderImpl() {
         clientBuilder = ClientBuilder.newBuilder();
@@ -154,6 +158,7 @@
         processProviders(interfaceClass);
         InjectionManagerExposer injectionManagerExposer = new InjectionManagerExposer();
         register(injectionManagerExposer);
+        register(SseMessageBodyReader.class);
 
         //We need to check first if default exception mapper was not disabled by property on builder.
         registerExceptionMapper();
@@ -185,13 +190,14 @@
             ClientConfig config = new ClientConfig();
             config.loadFrom(getConfiguration());
             config.connectorProvider(connector);
-            client = ClientBuilder.newClient(config);
+            client = clientBuilder.withConfig(config).build();
         }
 
         if (client instanceof Initializable) {
             ((Initializable) client).preInitialize();
         }
         WebTarget webTarget = client.target(this.uri);
+        webTarget.property(ClientProperties.FOLLOW_REDIRECTS, followRedirects);
 
         RestClientModel restClientModel = RestClientModel.from(interfaceClass,
                                                                responseExceptionMappers,
@@ -423,6 +429,33 @@
         }
     }
 
+    @Override
+    public RestClientBuilder followRedirects(boolean followRedirects) {
+        this.followRedirects = followRedirects;
+        return this;
+    }
+
+    @Override
+    public RestClientBuilder proxyAddress(String proxyHost, int proxyPort) {
+        if (proxyHost == null) {
+            throw new IllegalArgumentException("Proxy host must not be null");
+        }
+        if (proxyPort <= 0 || proxyPort > 65535) {
+            throw new IllegalArgumentException("Invalid proxy port");
+        }
+        property(ClientProperties.PROXY_URI, proxyHost + ":" + proxyPort);
+        return this;
+    }
+
+    @Override
+    public RestClientBuilder queryParamStyle(QueryParamStyle queryParamStyle) {
+         if (queryParamStyle != null) {
+            property(ClientProperties.QUERY_PARAM_STYLE,
+                    JerseyQueryParamStyle.valueOf(queryParamStyle.toString()));
+        }
+        return this;
+    }
+
     private static class InjectionManagerExposer implements Feature {
         InjectionManager injectionManager;
 
@@ -463,4 +496,4 @@
         }
     }
 
-}
+}
\ No newline at end of file
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientProducer.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientProducer.java
index d0c75ec..f386896 100644
--- a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientProducer.java
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientProducer.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2021 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
@@ -55,6 +55,7 @@
 import org.eclipse.microprofile.config.Config;
 import org.eclipse.microprofile.config.ConfigProvider;
 import org.eclipse.microprofile.rest.client.RestClientBuilder;
+import org.eclipse.microprofile.rest.client.ext.QueryParamStyle;
 import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
 import org.eclipse.microprofile.rest.client.inject.RestClient;
 import org.glassfish.jersey.internal.util.ReflectionHelper;
@@ -83,6 +84,9 @@
     private static final String CONFIG_SSL_KEY_STORE_PASSWORD = "/mp-rest/keyStorePassword";
     private static final String CONFIG_SSL_HOSTNAME_VERIFIER = "/mp-rest/hostnameVerifier";
     private static final String CONFIG_PROVIDERS = "/mp-rest/providers";
+    private static final String CONFIG_FOLLOW_REDIRECTS = "/mp-rest/followRedirects";
+    private static final String CONFIG_QUERY_PARAM_STYLE = "/mp-rest/queryParamStyle";
+    private static final String CONFIG_PROXY_ADDRESS = "/mp-rest/proxyAddress";
     private static final String DEFAULT_KEYSTORE_TYPE = "JKS";
     private static final String CLASSPATH_LOCATION = "classpath:";
     private static final String FILE_LOCATION = "file:";
@@ -135,6 +139,26 @@
         getConfigOption(Long.class, CONFIG_READ_TIMEOUT)
                 .ifPresent(aLong -> restClientBuilder.readTimeout(aLong, TimeUnit.MILLISECONDS));
 
+        getConfigOption(Boolean.class, CONFIG_FOLLOW_REDIRECTS)
+                .ifPresent(value -> restClientBuilder.followRedirects(value));
+        getConfigOption(String.class, CONFIG_QUERY_PARAM_STYLE)
+                .ifPresent(value -> restClientBuilder.queryParamStyle(QueryParamStyle.valueOf(value)));
+        Optional<String> proxyAddress = getConfigOption(String.class, CONFIG_PROXY_ADDRESS);
+        if (proxyAddress.isPresent()) {
+            String[] proxyAddressParts = proxyAddress.get().split(":");
+            if (proxyAddressParts.length < 2) {
+                throw new IllegalArgumentException("Invalid Proxy URI");
+            }
+            String proxyHost = proxyAddressParts[0];
+            int proxyPort;
+            try {
+                proxyPort = Integer.parseInt(proxyAddressParts[1]);
+            } catch (NumberFormatException nfe) {
+                throw new IllegalArgumentException("Invalid Proxy port", nfe);
+            }
+            restClientBuilder.proxyAddress(proxyHost, proxyPort);
+        }
+
         // Providers from configuration
         addConfiguredProviders(restClientBuilder);
 
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/SseEventPublisher.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/SseEventPublisher.java
new file mode 100644
index 0000000..d129589
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/SseEventPublisher.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2021 Payara Foundation 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.microprofile.restclient;
+
+import java.io.InputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.sse.InboundSseEvent;
+import org.glassfish.jersey.client.ChunkedInput;
+import org.glassfish.jersey.internal.PropertiesDelegate;
+import org.glassfish.jersey.internal.util.JerseyPublisher;
+import org.glassfish.jersey.media.sse.EventInput;
+import org.glassfish.jersey.media.sse.InboundEvent;
+import org.glassfish.jersey.message.MessageBodyWorkers;
+import org.reactivestreams.Publisher;
+import org.reactivestreams.Subscriber;
+
+public class SseEventPublisher extends EventInput implements Publisher<InboundEvent> {
+
+    private final Executor executor;
+    private final Type genericType;
+    private final JerseyPublisher<Object> publisher;
+
+    /**
+     * Package-private constructor used by the
+     * {@link org.glassfish.jersey.microprofile.restclient.SseMessageBodyReader}.
+     *
+     * @param inputStream response input stream.
+     * @param annotations annotations associated with response entity.
+     * @param mediaType response entity media type.
+     * @param headers response headers.
+     * @param messageBodyWorkers message body workers.
+     * @param propertiesDelegate properties delegate for this request/response.
+     */
+    SseEventPublisher(InputStream inputStream,
+            Type genericType,
+            Annotation[] annotations,
+            MediaType mediaType,
+            MultivaluedMap<String, String> headers,
+            MessageBodyWorkers messageBodyWorkers,
+            PropertiesDelegate propertiesDelegate,
+            ExecutorService executor) {
+        super(inputStream, annotations, mediaType, headers, messageBodyWorkers, propertiesDelegate);
+
+        this.executor = executor;
+        this.genericType = genericType;
+        this.publisher = new JerseyPublisher<>(executor::submit, JerseyPublisher.PublisherStrategy.BEST_EFFORT);
+    }
+
+    private static final Logger LOG = Logger.getLogger(SseEventPublisher.class.getName());
+
+    /**
+     * Request {@link SseEventPublisher} to start streaming data.
+     *
+     * Each {@link SseEventSubscription} will work for only a single
+     * {@link Subscriber}. If the {@link SseEventPublisher} rejects the
+     * subscription attempt or otherwise fails it will signal the error via
+     * {@link Subscriber#onError(Throwable)}.
+     *
+     * @param subscriber the {@link Subscriber} that will consume signals from
+     * the {@link SseEventPublisher}
+     */
+    @Override
+    public void subscribe(Subscriber subscriber) {
+        if (subscriber == null) {
+            throw new NullPointerException("The subscriber is `null`");
+        }
+        this.publisher.subscribe(new SseEventSuscriber(subscriber));
+
+        Runnable readEventTask = () -> {
+            Type typeArgument;
+            if (genericType instanceof ParameterizedType) {
+                typeArgument = ((ParameterizedType) genericType).getActualTypeArguments()[0];
+                ChunkedInput<InboundEvent> input = SseEventPublisher.this;
+                try {
+                    InboundSseEvent event;
+                    //  org.reactivestreams.Publisher<javax.ws.rs.sse.InboundSseEvent>
+                    if (typeArgument.equals(InboundSseEvent.class)) {
+                        while ((event = input.read()) != null) {
+                         this.publisher.publish(event);
+                        }
+                    } else {
+                        // Read event data as a given Java type e.g org.reactivestreams.Publisher<CustomEvent>
+                        while ((event = input.read()) != null) {
+                            this.publisher.publish(event.readData((Class) typeArgument));
+                        }
+                    }
+                } catch (Throwable t) {
+                    subscriber.onError(t);
+                    return;
+                }
+                this.publisher.close();
+            }
+        };
+        try {
+            executor.execute(readEventTask);
+        } catch (RejectedExecutionException ex) {
+            LOG.log(Level.WARNING, "Executor {0} rejected emit event task", executor);
+        }
+    }
+
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/SseEventSubscription.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/SseEventSubscription.java
new file mode 100644
index 0000000..18dbf4c
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/SseEventSubscription.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2021 Payara Foundation 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.microprofile.restclient;
+
+import org.glassfish.jersey.internal.jsr166.Flow;
+import java.util.logging.Logger;
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.Subscription;
+
+/**
+ * A {@link SseEventSubscription} represents a one-to-one life-cycle of a
+ * {@link Subscriber} subscribing to a {@link SseEventPublisher}.
+ *
+ * @param <T> the type of event
+ */
+public class SseEventSubscription<T> implements Subscription {
+
+    private final Subscriber subscriber;
+    private final Flow.Subscription subscription;
+
+    SseEventSubscription(Subscriber<T> subscriber, Flow.Subscription subscription) {
+        this.subscriber = subscriber;
+        this.subscription = subscription;
+    }
+
+    /**
+     * No events will be sent by a {@link SseEventPublisher} until demand is
+     * signaled via {@link SseEventSubscription#request} method.
+     *
+     * @param n the strictly positive number of elements to requests to the
+     * {@link SseEventPublisher}
+     */
+    @Override
+    public void request(long n) {
+        if (n > 0) {
+            subscription.request(n);
+        } else {
+            cancel();
+            subscriber.onError(
+                    new IllegalArgumentException(
+                            "Request must be positive number " + n
+                    )
+            );
+        }
+    }
+
+    /**
+     * Request the {@link SseEventPublisher} to stop sending data and clean up
+     * resources.
+     *
+     * Data may still be sent to meet previously signaled demand after calling
+     * cancel.
+     */
+    @Override
+    public void cancel() {
+        subscription.cancel();
+    }
+
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/SseEventSuscriber.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/SseEventSuscriber.java
new file mode 100644
index 0000000..da628a0
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/SseEventSuscriber.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2021 Payara Foundation 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.microprofile.restclient;
+
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.Subscription;
+import org.glassfish.jersey.internal.jsr166.Flow;
+
+public class SseEventSuscriber<T> implements Flow.Subscriber<T> {
+
+    private final Subscriber<T> subscriber;
+    private Subscription subscription;
+
+    public SseEventSuscriber(Subscriber<T> subscriber) {
+        this.subscriber = subscriber;
+    }
+
+    @Override
+    public void onSubscribe(final Flow.Subscription flowsubscription) {
+        subscription = new SseEventSubscription<T>(subscriber, flowsubscription);
+        subscriber.onSubscribe(subscription);
+    }
+
+    @Override
+    public void onNext(final T item) {
+        subscriber.onNext(item);
+    }
+
+    @Override
+    public void onError(final Throwable t) {
+        // As per Reactive Streams Rule 2.13, we need to throw a `java.lang.NullPointerException` if the `Throwable` is `null`
+        if (t == null) {
+            throw new NullPointerException("Reactive Streams Rule 2.13 violated: The received error is `null`");
+        }
+        subscriber.onError(t);
+    }
+
+    @Override
+    public void onComplete() {
+        subscriber.onComplete();
+    }
+
+    /**
+     * Get reference to subscriber's {@link Flow.Subscription}.
+     *
+     * @return subscriber's {@code subscription}
+     */
+    Subscription getSubscription() {
+        return this.subscription;
+    }
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/SseMessageBodyReader.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/SseMessageBodyReader.java
new file mode 100644
index 0000000..6bf3fc3
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/SseMessageBodyReader.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2021 Payara Foundation 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.microprofile.restclient;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.concurrent.ExecutorService;
+import javax.inject.Inject;
+import javax.inject.Provider;
+import javax.ws.rs.ConstrainedTo;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyReader;
+import javax.ws.rs.ext.Providers;
+import org.glassfish.jersey.internal.PropertiesDelegate;
+import org.glassfish.jersey.media.sse.InboundEvent;
+import org.glassfish.jersey.message.MessageBodyWorkers;
+import org.glassfish.jersey.message.internal.ReaderInterceptorExecutor;
+import org.reactivestreams.Publisher;
+
+@Consumes(MediaType.SERVER_SENT_EVENTS)
+@ConstrainedTo(RuntimeType.CLIENT)
+public class SseMessageBodyReader implements MessageBodyReader<Publisher<InboundEvent>> {
+
+    @Context
+    protected Providers providers;
+
+    @Inject
+    private Provider<MessageBodyWorkers> messageBodyWorkers;
+
+    @Inject
+    private Provider<PropertiesDelegate> propertiesDelegateProvider;
+
+    @Inject
+    private Provider<ExecutorService> executorServiceProvider;
+
+    @Override
+    public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
+        return Publisher.class.isAssignableFrom(type)
+                && MediaType.SERVER_SENT_EVENTS_TYPE.isCompatible(mediaType);
+    }
+
+    @Override
+    public Publisher<InboundEvent> readFrom(Class<Publisher<InboundEvent>> chunkedInputClass,
+            Type genericType,
+            Annotation[] annotations,
+            MediaType mediaType,
+            MultivaluedMap<String, String> headers,
+            InputStream inputStream) throws IOException, WebApplicationException {
+        InputStream closeableInputStream = ReaderInterceptorExecutor.closeableInputStream(inputStream);
+        return new SseEventPublisher(
+                closeableInputStream,
+                genericType,
+                annotations,
+                mediaType,
+                headers,
+                messageBodyWorkers.get(),
+                propertiesDelegateProvider.get(),
+                executorServiceProvider.get()
+        );
+    }
+}
diff --git a/media/sse/src/main/java/org/glassfish/jersey/media/sse/EventInput.java b/media/sse/src/main/java/org/glassfish/jersey/media/sse/EventInput.java
index 1843ccf..817d486 100644
--- a/media/sse/src/main/java/org/glassfish/jersey/media/sse/EventInput.java
+++ b/media/sse/src/main/java/org/glassfish/jersey/media/sse/EventInput.java
@@ -50,7 +50,7 @@
      * @param messageBodyWorkers message body workers.
      * @param propertiesDelegate properties delegate for this request/response.
      */
-    EventInput(InputStream inputStream,
+    protected EventInput(InputStream inputStream,
                Annotation[] annotations,
                MediaType mediaType,
                MultivaluedMap<String, String> headers,
diff --git a/media/sse/src/main/java/org/glassfish/jersey/media/sse/InboundEvent.java b/media/sse/src/main/java/org/glassfish/jersey/media/sse/InboundEvent.java
index 1d2d80c..43755b4 100644
--- a/media/sse/src/main/java/org/glassfish/jersey/media/sse/InboundEvent.java
+++ b/media/sse/src/main/java/org/glassfish/jersey/media/sse/InboundEvent.java
@@ -56,7 +56,7 @@
     /**
      * Inbound event builder. This implementation is not thread-safe.
      */
-    static class Builder {
+    public static class Builder {
 
         private String name;
         private String id;
diff --git a/pom.xml b/pom.xml
index fce06e9..5178b90 100644
--- a/pom.xml
+++ b/pom.xml
@@ -2079,7 +2079,7 @@
         <bnd.plugin.version>2.3.6</bnd.plugin.version>
         <cdi.api.version>1.1</cdi.api.version>
         <commons-lang3.version>3.3.2</commons-lang3.version>
-        <config.version>1.2.1</config.version>
+        <microprofile.config.version>2.0</microprofile.config.version>
         <checkstyle.mvn.plugin.version>3.1.0</checkstyle.mvn.plugin.version>
         <checkstyle.version>8.28</checkstyle.version>
         <easymock.version>3.3</easymock.version>
diff --git a/tests/integration/microprofile/rest-client/pom.xml b/tests/integration/microprofile/rest-client/pom.xml
index 82ca5c2..5346f5b 100644
--- a/tests/integration/microprofile/rest-client/pom.xml
+++ b/tests/integration/microprofile/rest-client/pom.xml
@@ -55,7 +55,7 @@
         <dependency>
             <groupId>org.eclipse.microprofile.rest.client</groupId>
             <artifactId>microprofile-rest-client-tck</artifactId>
-            <version>1.4.1</version>
+            <version>2.0</version>
             <scope>test</scope>
         </dependency>
         <dependency>
@@ -108,7 +108,29 @@
             <artifactId>jersey-apache-connector</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-servlet</artifactId>
+            <version>${jetty.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.jersey.ext.cdi</groupId>
+            <artifactId>jersey-weld2-se</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
+    
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.eclipse.jetty</groupId>
+                <artifactId>jetty-bom</artifactId>
+                <version>${jetty.version}</version>
+                <type>pom</type>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
 
     <profiles>
         <profile>
diff --git a/tests/integration/microprofile/rest-client/tck-suite.xml b/tests/integration/microprofile/rest-client/tck-suite.xml
index 592f136..d7a0dbb 100644
--- a/tests/integration/microprofile/rest-client/tck-suite.xml
+++ b/tests/integration/microprofile/rest-client/tck-suite.xml
@@ -23,6 +23,14 @@
             <package name="org.eclipse.microprofile.rest.client.tck.*">
             </package>
         </packages>
+        <classes>
+            <class name="org.eclipse.microprofile.rest.client.tck.ProxyServerTest">
+                <methods>
+                    <!--https://github.com/eclipse/microprofile-rest-client/pull/298-->
+                    <exclude name="testProxy"></exclude>
+                </methods>
+            </class>
+        </classes>
     </test>
 
 </suite>
\ No newline at end of file