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&foot=v2&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&foo[]=v2&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&foot=v2&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