Rest client implementation
Signed-off-by: David Kral <david.k.kral@oracle.com>
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 c7d54be..26df84f 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
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2019 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
@@ -26,6 +26,7 @@
import javax.ws.rs.core.UriBuilder;
import org.glassfish.jersey.internal.guava.Preconditions;
+import org.glassfish.jersey.internal.inject.InjectionManager;
/**
* Jersey implementation of {@link javax.ws.rs.client.WebTarget JAX-RS client target}
diff --git a/ext/microprofile/mp-rest-client/pom.xml b/ext/microprofile/mp-rest-client/pom.xml
new file mode 100644
index 0000000..dc47382
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/pom.xml
@@ -0,0 +1,202 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2019 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
+
+-->
+<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">
+ <parent>
+ <artifactId>jersey-microprofile</artifactId>
+ <groupId>org.glassfish.jersey.ext</groupId>
+ <version>2.29-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>jersey-mp-rest-client</artifactId>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.eclipse.microprofile.rest.client</groupId>
+ <artifactId>microprofile-rest-client-api</artifactId>
+ <version>1.2.1</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.microprofile.config</groupId>
+ <artifactId>microprofile-config-api</artifactId>
+ <version>1.3</version>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-client</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-server</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.inject</groupId>
+ <artifactId>jersey-hk2</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.media</groupId>
+ <artifactId>jersey-media-json-binding</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>jakarta.json</groupId>
+ <artifactId>jakarta.json-api</artifactId>
+ <version>1.1.5</version>
+ </dependency>
+ <dependency>
+ <groupId>javax.enterprise</groupId>
+ <artifactId>cdi-api</artifactId>
+ <version>2.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish</groupId>
+ <artifactId>jsonp-jaxrs</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.weld.se</groupId>
+ <artifactId>weld-se-core</artifactId>
+ <version>3.0.3.Final</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework</groupId>
+ <artifactId>jersey-test-framework-core</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-bundle</artifactId>
+ <version>${project.version}</version>
+ <type>pom</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>io.smallrye</groupId>
+ <artifactId>smallrye-config</artifactId>
+ <version>1.3.6</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.microprofile.rest.client</groupId>
+ <artifactId>microprofile-rest-client-tck</artifactId>
+ <version>1.2.1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.testng</groupId>
+ <artifactId>testng</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.github.tomakehurst</groupId>
+ <artifactId>wiremock</artifactId>
+ <version>2.21.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.arquillian.testng</groupId>
+ <artifactId>arquillian-testng-container</artifactId>
+ <version>1.4.1.Final</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.arquillian.container</groupId>
+ <artifactId>arquillian-container-test-spi</artifactId>
+ <version>1.4.1.Final</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.arquillian.container</groupId>
+ <artifactId>arquillian-weld-embedded</artifactId>
+ <version>2.0.1.Final</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>io.opentracing</groupId>
+ <artifactId>opentracing-noop</artifactId>
+ <version>0.30.0</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <!--<parallel>classes</parallel>
+ <perCoreThreadCount>true</perCoreThreadCount>
+ <threadCount>1</threadCount>
+ <forkCount>1C</forkCount>
+ <reuseForks>true</reuseForks>-->
+ <suiteXmlFiles>
+ <suiteXmlFile>tck-suite.xml</suiteXmlFile>
+ </suiteXmlFiles>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>uk.co.deliverymind</groupId>
+ <artifactId>wiremock-maven-plugin</artifactId>
+ <version>2.7.0</version>
+ <executions>
+ <execution>
+ <phase>test-compile</phase>
+ <goals>
+ <goal>run</goal>
+ </goals>
+ <configuration>
+ <dir>target/classes</dir>
+ <params>--port=8765 --verbose</params>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <inherited>true</inherited>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Export-Package>
+ org.glassfish.jersey.restclient.*;version=${project.version}
+ </Export-Package>
+ <Import-Package>
+ *
+ </Import-Package>
+ </instructions>
+ <unpackBundle>true</unpackBundle>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+<!-- <properties>-->
+<!-- <surefire.security.argline>-Djava.security.manager -Djava.security.policy=${project.build.directory}/test-classes/server.policy</surefire.security.argline>-->
+<!-- </properties>-->
+
+
+</project>
\ No newline at end of file
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/BeanClassModel.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/BeanClassModel.java
new file mode 100644
index 0000000..d5c4d74
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/BeanClassModel.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright (c) 2019 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.microprofile.restclient;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.ws.rs.BeanParam;
+import javax.ws.rs.CookieParam;
+import javax.ws.rs.FormParam;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.MatrixParam;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Form;
+import javax.ws.rs.core.MultivaluedMap;
+
+import org.glassfish.jersey.model.Parameter;
+
+/**
+ * Model of method parameter annotated by {@link BeanParam} annotation.
+ *
+ * @author David Kral
+ */
+class BeanClassModel {
+
+ private final Class<?> beanClass;
+ private final List<ParamModel> parameterModels;
+
+ /**
+ * Create new instance of bean annotated parameter.
+ *
+ * @param interfaceModel rest client interface model
+ * @param beanClass bean annotated parameter class
+ * @return new instance
+ */
+ static BeanClassModel fromClass(InterfaceModel interfaceModel, Class<?> beanClass) {
+ return new Builder(interfaceModel, beanClass)
+ .processPathFields()
+ .processHeaderFields()
+ .processCookieFields()
+ .processQueryFields()
+ .processMatrixFields()
+ .build();
+ }
+
+ private BeanClassModel(Builder builder) {
+ this.beanClass = builder.beanClass;
+ this.parameterModels = builder.parameterModels;
+ }
+
+ /**
+ * List of all class fields annotated with supported parameter annotation
+ *
+ * @return parameter model list
+ */
+ List<ParamModel> getParameterModels() {
+ return parameterModels;
+ }
+
+ /**
+ * Resolves bean path parameters.
+ *
+ * @param webTarget web target path
+ * @param instance actual method parameter value
+ * @return updated web target path
+ */
+ @SuppressWarnings("unchecked")
+ WebTarget resolvePath(WebTarget webTarget, Object instance) {
+ AtomicReference<WebTarget> toReturn = new AtomicReference<>(webTarget);
+ parameterModels.stream()
+ .filter(paramModel -> paramModel.handles(PathParam.class))
+ .forEach(parameterModel -> {
+ Field field = (Field) parameterModel.getAnnotatedElement();
+ toReturn.set((WebTarget) parameterModel.handleParameter(webTarget,
+ PathParam.class,
+ resolveValueFromField(field, instance)));
+ });
+ return toReturn.get();
+ }
+
+ /**
+ * Resolves bean header parameters.
+ *
+ * @param headers headers
+ * @param instance actual method parameter value
+ * @return updated headers
+ */
+ @SuppressWarnings("unchecked")
+ MultivaluedMap<String, Object> resolveHeaders(MultivaluedMap<String, Object> headers,
+ Object instance) {
+ parameterModels.stream()
+ .filter(paramModel -> paramModel.handles(HeaderParam.class))
+ .forEach(parameterModel -> {
+ Field field = (Field) parameterModel.getAnnotatedElement();
+ parameterModel.handleParameter(headers,
+ HeaderParam.class,
+ resolveValueFromField(field, instance));
+ });
+ return headers;
+ }
+
+ /**
+ * Resolves bean cookie parameters.
+ *
+ * @param cookies cookies
+ * @param instance actual method parameter value
+ * @return updated cookies
+ */
+ @SuppressWarnings("unchecked")
+ Map<String, String> resolveCookies(Map<String, String> cookies,
+ Object instance) {
+ parameterModels.stream()
+ .filter(paramModel -> paramModel.handles(CookieParam.class))
+ .forEach(parameterModel -> {
+ Field field = (Field) parameterModel.getAnnotatedElement();
+ parameterModel.handleParameter(cookies,
+ CookieParam.class,
+ resolveValueFromField(field, instance));
+ });
+ return cookies;
+ }
+
+ /**
+ * Resolves bean query parameters.
+ *
+ * @param query queries
+ * @param instance actual method parameter value
+ * @return updated queries
+ */
+ @SuppressWarnings("unchecked")
+ Map<String, Object[]> resolveQuery(Map<String, Object[]> query,
+ Object instance) {
+ parameterModels.stream()
+ .filter(paramModel -> paramModel.handles(QueryParam.class))
+ .forEach(parameterModel -> {
+ Field field = (Field) parameterModel.getAnnotatedElement();
+ parameterModel.handleParameter(query,
+ QueryParam.class,
+ resolveValueFromField(field, instance));
+ });
+ return query;
+ }
+
+ /**
+ * Resolves bean matrix parameters.
+ *
+ * @param webTarget web target path
+ * @param instance actual method parameter value
+ * @return updated web target path
+ */
+ @SuppressWarnings("unchecked")
+ WebTarget resolveMatrix(WebTarget webTarget,
+ Object instance) {
+ AtomicReference<WebTarget> toReturn = new AtomicReference<>(webTarget);
+ parameterModels.stream()
+ .filter(paramModel -> paramModel.handles(MatrixParam.class))
+ .forEach(parameterModel -> {
+ Field field = (Field) parameterModel.getAnnotatedElement();
+ toReturn.set((WebTarget) parameterModel.handleParameter(webTarget,
+ MatrixParam.class,
+ resolveValueFromField(field, instance)));
+ });
+ return toReturn.get();
+ }
+
+
+ /**
+ * Resolves bean form parameters.
+ *
+ * @param form web form
+ * @param instance actual method parameter value
+ * @return updated web form
+ */
+ @SuppressWarnings("unchecked")
+ Form resolveForm(Form form,
+ Object instance) {
+ parameterModels.stream()
+ .filter(paramModel -> paramModel.handles(FormParam.class))
+ .forEach(parameterModel -> {
+ Field field = (Field) parameterModel.getAnnotatedElement();
+ parameterModel.handleParameter(form,
+ FormParam.class,
+ resolveValueFromField(field, instance));
+ });
+ return form;
+ }
+
+ private Object resolveValueFromField(Field field, Object instance) {
+ return AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
+ try {
+ Object toReturn;
+ if (field.isAccessible()) {
+ return field.get(instance);
+ }
+ field.setAccessible(true);
+ toReturn = field.get(instance);
+ field.setAccessible(false);
+ return toReturn;
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ }
+
+ private static class Builder {
+
+ private final InterfaceModel interfaceModel;
+ private final Class<?> beanClass;
+ private ArrayList<ParamModel> parameterModels = new ArrayList<>();
+
+ private Builder(InterfaceModel interfaceModel, Class<?> beanClass) {
+ this.interfaceModel = interfaceModel;
+ this.beanClass = beanClass;
+ }
+
+ /**
+ * Parses all {@link PathParam} annotated fields from bean class.
+ *
+ * @return updated builder instance
+ */
+ Builder processPathFields() {
+ return processFieldsByParameterClass(PathParam.class);
+ }
+
+ /**
+ * Parses all {@link HeaderParam} annotated fields from bean class.
+ *
+ * @return updated builder instance
+ */
+ Builder processHeaderFields() {
+ return processFieldsByParameterClass(HeaderParam.class);
+ }
+
+ /**
+ * Parses all {@link CookieParam} annotated fields from bean class.
+ *
+ * @return updated builder instance
+ */
+ Builder processCookieFields() {
+ return processFieldsByParameterClass(CookieParam.class);
+ }
+
+ /**
+ * Parses all {@link QueryParam} annotated fields from bean class.
+ *
+ * @return updated builder instance
+ */
+ Builder processQueryFields() {
+ return processFieldsByParameterClass(QueryParam.class);
+ }
+
+ /**
+ * Parses all {@link MatrixParam} annotated fields from bean class.
+ *
+ * @return updated builder instance
+ */
+ Builder processMatrixFields() {
+ return processFieldsByParameterClass(MatrixParam.class);
+ }
+
+ private Builder processFieldsByParameterClass(Class<? extends Annotation> parameterClass) {
+ for (Field field : beanClass.getDeclaredFields()) {
+ if (field.isAnnotationPresent(parameterClass)) {
+ Parameter parameter = Parameter.create(parameterClass, parameterClass, false,
+ field.getType(), field.getGenericType(),
+ field.getDeclaredAnnotations());
+ parameterModels.add(ParamModel.from(interfaceModel, field.getType(), field,
+ parameter, -1));
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Creates new BeanClassModel instance.
+ *
+ * @return new instance
+ */
+ BeanClassModel build() {
+ return new BeanClassModel(this);
+ }
+ }
+
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/BeanParamModel.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/BeanParamModel.java
new file mode 100644
index 0000000..348382e
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/BeanParamModel.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2019 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.microprofile.restclient;
+
+import java.lang.annotation.Annotation;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.ws.rs.BeanParam;
+import javax.ws.rs.CookieParam;
+import javax.ws.rs.FormParam;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.MatrixParam;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Form;
+import javax.ws.rs.core.MultivaluedMap;
+
+/**
+ * Contains information about method parameter or class field which is annotated by {@link BeanParam}.
+ *
+ * @author David Kral
+ */
+class BeanParamModel extends ParamModel<Object> {
+
+ private BeanClassModel beanClassModel;
+
+ BeanParamModel(Builder builder) {
+ super(builder);
+ beanClassModel = BeanClassModel.fromClass(interfaceModel, (Class<?>) getType());
+ }
+
+ @Override
+ public Object handleParameter(Object requestPart, Class<?> annotationClass, Object instance) {
+ if (PathParam.class.equals(annotationClass)) {
+ return beanClassModel.resolvePath((WebTarget) requestPart, instance);
+ } else if (HeaderParam.class.equals(annotationClass)) {
+ return beanClassModel.resolveHeaders((MultivaluedMap<String, Object>) requestPart, instance);
+ } else if (CookieParam.class.equals(annotationClass)) {
+ return beanClassModel.resolveCookies((Map<String, String>) requestPart, instance);
+ } else if (QueryParam.class.equals(annotationClass)) {
+ return beanClassModel.resolveQuery((Map<String, Object[]>) requestPart, instance);
+ } else if (MatrixParam.class.equals(annotationClass)) {
+ return beanClassModel.resolveMatrix((WebTarget) requestPart, instance);
+ } else if (FormParam.class.equals(annotationClass)) {
+ return beanClassModel.resolveForm((Form) requestPart, instance);
+ }
+ throw new UnsupportedOperationException(annotationClass.getName() + " is not supported!");
+ }
+
+ @Override
+ public boolean handles(Class<Annotation> annotation) {
+ return PathParam.class.equals(annotation)
+ || HeaderParam.class.equals(annotation)
+ || CookieParam.class.equals(annotation)
+ || QueryParam.class.equals(annotation)
+ || MatrixParam.class.equals(annotation)
+ || FormParam.class.equals(annotation);
+ }
+
+ /**
+ * Returns {@link List} of all parameters annotated by searched annotation.
+ *
+ * @param paramAnnotation searched annotation
+ * @return filtered list
+ */
+ List<ParamModel> getAllParamsWithType(Class<? extends Annotation> paramAnnotation) {
+ return beanClassModel.getParameterModels().stream()
+ .filter(paramModel -> paramModel.handles(paramAnnotation))
+ .collect(Collectors.toList());
+ }
+
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/ClientHeaderParamModel.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/ClientHeaderParamModel.java
new file mode 100644
index 0000000..fa5d3af
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/ClientHeaderParamModel.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2019 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.microprofile.restclient;
+
+import java.lang.reflect.Method;
+
+import org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam;
+
+/**
+ * Contains information about method annotation {@link ClientHeaderParam}.
+ *
+ * @author David Kral
+ */
+class ClientHeaderParamModel {
+
+ private final String headerName;
+ private final String[] headerValue;
+ private final Method computeMethod;
+ private final boolean required;
+
+ ClientHeaderParamModel(Class<?> iClass, ClientHeaderParam clientHeaderParam) {
+ headerName = clientHeaderParam.name();
+ headerValue = clientHeaderParam.value();
+ computeMethod = InterfaceUtil.parseComputeMethod(iClass, headerValue);
+ required = clientHeaderParam.required();
+ }
+
+ /**
+ * Returns header name.
+ *
+ * @return header name
+ */
+ String getHeaderName() {
+ return headerName;
+ }
+
+ /**
+ * Returns header value.
+ *
+ * @return header value
+ */
+ String[] getHeaderValue() {
+ return headerValue;
+ }
+
+ /**
+ * Returns method which is used to compute header value.
+ *
+ * @return compute method
+ */
+ Method getComputeMethod() {
+ return computeMethod;
+ }
+
+ /**
+ * Returns true if header is required and false if not. It header is not required and exception
+ * is thrown during compute method invocation, this header will be ignored and not included to request.
+ *
+ * @return if header is required
+ */
+ boolean isRequired() {
+ return required;
+ }
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/ConfigWrapper.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/ConfigWrapper.java
new file mode 100644
index 0000000..466ab38
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/ConfigWrapper.java
@@ -0,0 +1,91 @@
+package org.glassfish.jersey.microprofile.restclient;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.Feature;
+
+/**
+ * Configuration wrapper for {@link Configuration}. This class is needed due to custom provider registrations.
+ *
+ * @author David Kral
+ */
+class ConfigWrapper implements Configuration {
+
+ private final Configuration jerseyBuilderConfig;
+ private final Map<Class<?>, Map<Class<?>, Integer>> customProviders;
+
+ ConfigWrapper(Configuration jerseyBuilderConfig) {
+ this.jerseyBuilderConfig = jerseyBuilderConfig;
+ this.customProviders = new HashMap<>();
+ }
+
+ void addCustomProvider(Class<?> provider, Map<Class<?>, Integer> contracts) {
+ if (customProviders.containsKey(provider)) {
+ customProviders.get(provider).putAll(contracts);
+ } else {
+ customProviders.put(provider, contracts);
+ }
+ }
+
+ @Override
+ public RuntimeType getRuntimeType() {
+ return jerseyBuilderConfig.getRuntimeType();
+ }
+
+ @Override
+ public Map<String, Object> getProperties() {
+ return jerseyBuilderConfig.getProperties();
+ }
+
+ @Override
+ public Object getProperty(String name) {
+ return jerseyBuilderConfig.getProperty(name);
+ }
+
+ @Override
+ public Collection<String> getPropertyNames() {
+ return jerseyBuilderConfig.getPropertyNames();
+ }
+
+ @Override
+ public boolean isEnabled(Feature feature) {
+ return jerseyBuilderConfig.isEnabled(feature);
+ }
+
+ @Override
+ public boolean isEnabled(Class<? extends Feature> featureClass) {
+ return jerseyBuilderConfig.isEnabled(featureClass);
+ }
+
+ @Override
+ public boolean isRegistered(Object component) {
+ return jerseyBuilderConfig.isRegistered(component);
+ }
+
+ @Override
+ public boolean isRegistered(Class<?> componentClass) {
+ return jerseyBuilderConfig.isRegistered(componentClass);
+ }
+
+ @Override
+ public Map<Class<?>, Integer> getContracts(Class<?> componentClass) {
+ Map<Class<?>, Integer> map = new HashMap<>(jerseyBuilderConfig.getContracts(componentClass));
+ if (customProviders.containsKey(componentClass)) map.putAll(customProviders.get(componentClass));
+ return map;
+ }
+
+ @Override
+ public Set<Class<?>> getClasses() {
+ return jerseyBuilderConfig.getClasses();
+ }
+
+ @Override
+ public Set<Object> getInstances() {
+ return jerseyBuilderConfig.getInstances();
+ }
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/CookieParamModel.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/CookieParamModel.java
new file mode 100644
index 0000000..9477c34
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/CookieParamModel.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2019 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.microprofile.restclient;
+
+import java.lang.annotation.Annotation;
+import java.util.Map;
+
+import javax.ws.rs.CookieParam;
+
+/**
+ * Contains information about method parameter or class field which is annotated by {@link CookieParam}.
+ *
+ * @author David Kral
+ */
+class CookieParamModel extends ParamModel<Map<String, String>> {
+
+ private final String cookieParamName;
+
+ CookieParamModel(Builder builder) {
+ super(builder);
+ cookieParamName = builder.cookieParamName();
+ }
+
+ @Override
+ Map<String, String> handleParameter(Map<String, String> requestPart, Class<?> annotationClass, Object instance) {
+ Object resolvedValue = interfaceModel.resolveParamValue(instance, parameter);
+ requestPart.put(cookieParamName, (String) resolvedValue);
+ return requestPart;
+ }
+
+ @Override
+ boolean handles(Class<Annotation> annotation) {
+ return CookieParam.class.equals(annotation);
+ }
+
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/DefaultResponseExceptionMapper.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/DefaultResponseExceptionMapper.java
new file mode 100644
index 0000000..b699182
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/DefaultResponseExceptionMapper.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2019 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.microprofile.restclient;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Response;
+
+import org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper;
+
+/**
+ * Default {@link ResponseExceptionMapper} implementation
+ *
+ * @author David Kral
+ */
+public class DefaultResponseExceptionMapper implements ResponseExceptionMapper {
+ @Override
+ public Throwable toThrowable(Response response) {
+ return new WebApplicationException("Unknown error, status code " + response.getStatus(), response);
+ }
+
+ @Override
+ public int getPriority() {
+ return Integer.MAX_VALUE;
+ }
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/ExecutorServiceWrapper.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/ExecutorServiceWrapper.java
new file mode 100644
index 0000000..c3a8dd6
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/ExecutorServiceWrapper.java
@@ -0,0 +1,118 @@
+package org.glassfish.jersey.microprofile.restclient;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.stream.Collectors;
+
+import org.eclipse.microprofile.rest.client.ext.AsyncInvocationInterceptor;
+
+/**
+ * Invokes all {@link AsyncInvocationInterceptor} for every new thread.
+ *
+ * @author David Kral
+ */
+class ExecutorServiceWrapper implements ExecutorService {
+
+ private final ExecutorService wrapped;
+ private final List<AsyncInvocationInterceptor> asyncInterceptors;
+
+ ExecutorServiceWrapper(ExecutorService wrapped,
+ List<AsyncInvocationInterceptor> asyncInterceptors) {
+ this.wrapped = wrapped;
+ this.asyncInterceptors = asyncInterceptors;
+ }
+
+ @Override
+ public void shutdown() {
+ wrapped.shutdown();
+ }
+
+ @Override
+ public List<Runnable> shutdownNow() {
+ return wrapped.shutdownNow();
+ }
+
+ @Override
+ public boolean isShutdown() {
+ return wrapped.isShutdown();
+ }
+
+ @Override
+ public boolean isTerminated() {
+ return wrapped.isTerminated();
+ }
+
+ @Override
+ public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
+ return wrapped.awaitTermination(timeout, unit);
+ }
+
+ @Override
+ public <T> Future<T> submit(Callable<T> task) {
+ return wrapped.submit(wrap(task));
+ }
+
+ @Override
+ public <T> Future<T> submit(Runnable task, T result) {
+ return wrapped.submit(wrap(task), result);
+ }
+
+ @Override
+ public Future<?> submit(Runnable task) {
+ return wrapped.submit(wrap(task));
+ }
+
+ @Override
+ public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException {
+ return wrapped.invokeAll(wrap(tasks));
+ }
+
+ @Override
+ public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
+ throws InterruptedException {
+ return wrapped.invokeAll(wrap(tasks), timeout, unit);
+ }
+
+ @Override
+ public <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException {
+ return wrapped.invokeAny(wrap(tasks));
+ }
+
+ @Override
+ public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
+ throws InterruptedException, ExecutionException, TimeoutException {
+ return wrapped.invokeAny(wrap(tasks), timeout, unit);
+ }
+
+ @Override
+ public void execute(Runnable command) {
+ wrapped.execute(wrap(command));
+ }
+
+ private <T> Callable<T> wrap(Callable<T> task) {
+ return () -> {
+ asyncInterceptors.forEach(AsyncInvocationInterceptor::applyContext);
+ return task.call();
+ };
+ }
+
+ private Runnable wrap(Runnable task) {
+ return () -> {
+ asyncInterceptors.forEach(AsyncInvocationInterceptor::applyContext);
+ task.run();
+ };
+ }
+
+
+ private <T> Collection<? extends Callable<T>> wrap(Collection<? extends Callable<T>> tasks) {
+ return tasks.stream()
+ .map(this::wrap)
+ .collect(Collectors.toList());
+ }
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/FormParamModel.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/FormParamModel.java
new file mode 100644
index 0000000..7a8f738
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/FormParamModel.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2019 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.microprofile.restclient;
+
+import java.lang.annotation.Annotation;
+import java.util.Collection;
+
+import javax.ws.rs.FormParam;
+import javax.ws.rs.core.Form;
+
+/**
+ * Contains information about method parameter or class field which is annotated by {@link FormParam}.
+ *
+ * @author David Kral
+ */
+class FormParamModel extends ParamModel<Form> {
+
+ private final String formParamName;
+
+ FormParamModel(Builder builder) {
+ super(builder);
+ formParamName = builder.formParamName();
+ }
+
+ @Override
+ Form handleParameter(Form form, Class<?> annotationClass, Object instance) {
+ Object resolvedValue = interfaceModel.resolveParamValue(instance, parameter);
+ if (resolvedValue instanceof Collection) {
+ for (final Object v : ((Collection) resolvedValue)) {
+ form.param(formParamName, v.toString());
+ }
+ } else {
+ form.param(formParamName, resolvedValue.toString());
+ }
+ return form;
+ }
+
+ @Override
+ boolean handles(Class<Annotation> annotation) {
+ return FormParam.class.equals(annotation);
+ }
+
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/HeaderParamModel.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/HeaderParamModel.java
new file mode 100644
index 0000000..3417ada
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/HeaderParamModel.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2019 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.microprofile.restclient;
+
+import java.lang.annotation.Annotation;
+import java.util.Collections;
+
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.core.MultivaluedMap;
+
+/**
+ * Contains information about method parameter or class field which is annotated by {@link HeaderParam}.
+ *
+ * @author David Kral
+ */
+class HeaderParamModel extends ParamModel<MultivaluedMap<String, Object>> {
+
+ private String headerParamName;
+
+ HeaderParamModel(Builder builder) {
+ super(builder);
+ this.headerParamName = builder.headerParamName();
+ }
+
+ @Override
+ MultivaluedMap<String, Object> handleParameter(MultivaluedMap<String, Object> requestPart,
+ Class<?> annotationClass, Object instance) {
+ Object resolvedValue = interfaceModel.resolveParamValue(instance, parameter);
+ requestPart.put(headerParamName, Collections.singletonList(resolvedValue));
+ return requestPart;
+ }
+
+ @Override
+ boolean handles(Class<Annotation> annotation) {
+ return HeaderParam.class.equals(annotation);
+ }
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/HeadersContext.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/HeadersContext.java
new file mode 100644
index 0000000..77bcffe
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/HeadersContext.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2019 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.microprofile.restclient;
+
+import java.util.Optional;
+import java.util.function.Supplier;
+
+import javax.ws.rs.core.MultivaluedMap;
+
+/**
+ *
+ * @author David Kral
+ */
+public final class HeadersContext {
+
+ /**
+ * Headers context thread local, used by internal implementations of header filters.
+ */
+ private static final ThreadLocal<HeadersContext> HEADERS_CONTEXT = new ThreadLocal<>();
+
+ private final MultivaluedMap<String, String> inboundHeaders;
+
+ /**
+ * The instance associated with the current thread.
+ * @return context for current thread or {@code empty} if none associated
+ */
+ public static Optional<HeadersContext> get() {
+ return Optional.ofNullable(HEADERS_CONTEXT.get());
+ }
+
+ /**
+ * Computes the instance and associates it with current thread if none
+ * associated, or returns the instance already associated.
+ *
+ * @param contextSupplier supplier for header context to be associated with the thread if none is
+ * @return an instance associated with the current context, either from other provider, or from contextSupplier
+ */
+ public static HeadersContext compute(Supplier<HeadersContext> contextSupplier) {
+ HeadersContext headersContext = HEADERS_CONTEXT.get();
+ if (null == headersContext) {
+ set(contextSupplier.get());
+ }
+
+ return get().orElseThrow(() -> new IllegalStateException("Computed result was null"));
+ }
+
+ /**
+ * Set the header context to be associated with current thread.
+ *
+ * @param context context to associate
+ */
+ public static void set(HeadersContext context) {
+ HEADERS_CONTEXT.set(context);
+ }
+
+ /**
+ * Remove the header context associated with current thread.
+ */
+ public static void remove() {
+ HEADERS_CONTEXT.remove();
+ }
+
+ /**
+ * Create a new header context with client tracing enabled.
+ *
+ * @param inboundHeaders inbound header to be used for context propagation
+ * @return a new header context (not associated with current thread)
+ * @see #set(HeadersContext)
+ */
+ public static HeadersContext create(MultivaluedMap<String, String> inboundHeaders) {
+ return new HeadersContext(inboundHeaders);
+ }
+
+ public HeadersContext(MultivaluedMap<String, String> inboundHeaders) {
+ this.inboundHeaders = inboundHeaders;
+ }
+
+ /**
+ * Map of headers that were received by server for an inbound call,
+ * may be used to propagate additional headers fro outbound request.
+ *
+ * @return map of inbound headers
+ */
+ public MultivaluedMap<String, String> inboundHeaders() {
+ return inboundHeaders;
+ }
+
+
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/HeadersRequestFilter.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/HeadersRequestFilter.java
new file mode 100644
index 0000000..64ece19
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/HeadersRequestFilter.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2019 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.microprofile.restclient;
+
+import javax.ws.rs.ConstrainedTo;
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerRequestFilter;
+
+/**
+ * Server side request filter used for propagation of request headers to server client request.
+ *
+ * @author David Kral
+ */
+@ConstrainedTo(RuntimeType.SERVER)
+public class HeadersRequestFilter implements ContainerRequestFilter {
+
+ @Override
+ public void filter(ContainerRequestContext requestContext) {
+ HeadersContext.compute(() -> HeadersContext.create(requestContext.getHeaders()));
+ }
+
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/InterceptorInvocationContext.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/InterceptorInvocationContext.java
new file mode 100644
index 0000000..d559fb7
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/InterceptorInvocationContext.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 2019 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.microprofile.restclient;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.enterprise.inject.spi.InterceptionType;
+import javax.enterprise.inject.spi.Interceptor;
+import javax.interceptor.InvocationContext;
+import javax.ws.rs.client.WebTarget;
+
+/**
+ * Invokes all interceptors bound to the target.
+ *
+ * This approach needs to be used due to CDI does not handle properly interceptor invocation
+ * on proxy instances.
+ *
+ * @author David Kral
+ */
+class InterceptorInvocationContext implements InvocationContext {
+
+ private final MethodModel methodModel;
+ private final Method method;
+ private final Map<String, Object> contextData;
+ private final List<InvocationInterceptor> interceptors;
+ private final WebTarget classLevelWebTarget;
+ private Object[] args;
+ private int currentPosition;
+
+ /**
+ * Creates new instance of InterceptorInvocationContext.
+ *
+ * @param classLevelWebTarget class level web target
+ * @param methodModel method model
+ * @param method reflection method
+ * @param args actual method arguments
+ */
+ InterceptorInvocationContext(WebTarget classLevelWebTarget,
+ MethodModel methodModel,
+ Method method,
+ Object[] args) {
+ this.contextData = new HashMap<>();
+ this.currentPosition = 0;
+ this.methodModel = methodModel;
+ this.method = method;
+ this.args = args;
+ this.classLevelWebTarget = classLevelWebTarget;
+ this.interceptors = methodModel.getInvocationInterceptors();
+
+ }
+
+ @Override
+ public Object getTarget() {
+ return methodModel;
+ }
+
+ @Override
+ public Object getTimer() {
+ return null;
+ }
+
+ @Override
+ public Method getMethod() {
+ return method;
+ }
+
+ @Override
+ public Constructor<?> getConstructor() {
+ return null;
+ }
+
+ @Override
+ public Object[] getParameters() {
+ return args;
+ }
+
+ @Override
+ public void setParameters(Object[] params) {
+ this.args = params;
+ }
+
+ @Override
+ public Map<String, Object> getContextData() {
+ return contextData;
+ }
+
+ @Override
+ public Object proceed() {
+ if (currentPosition < interceptors.size()) {
+ return interceptors.get(currentPosition++).intercept(this);
+ } else {
+ return methodModel.invokeMethod(classLevelWebTarget, method, args);
+ }
+ }
+
+ /**
+ * Contains actual interceptor instance and interceptor itself.
+ */
+ static class InvocationInterceptor {
+
+ private final Object interceptorInstance;
+ private final Interceptor interceptor;
+
+ InvocationInterceptor(Object interceptorInstance, Interceptor interceptor) {
+ this.interceptorInstance = interceptorInstance;
+ this.interceptor = interceptor;
+ }
+
+ /**
+ * Invokes interceptor with interception type AROUND_INVOKE.
+ *
+ * @param ctx invocation context
+ * @return interception result
+ */
+ @SuppressWarnings("unchecked")
+ Object intercept(InvocationContext ctx) {
+ try {
+ return interceptor.intercept(InterceptionType.AROUND_INVOKE, interceptorInstance, ctx);
+ } catch (Exception e) {
+ if (e instanceof RuntimeException) {
+ throw (RuntimeException) e;
+ }
+ throw new RuntimeException(e);
+ }
+ }
+ }
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/InterfaceModel.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/InterfaceModel.java
new file mode 100644
index 0000000..dad9c5e
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/InterfaceModel.java
@@ -0,0 +1,366 @@
+/*
+ * Copyright (c) 2019 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.microprofile.restclient;
+
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import javax.enterprise.context.spi.CreationalContext;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.enterprise.inject.spi.CDI;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.ext.ParamConverterProvider;
+
+import org.eclipse.microprofile.rest.client.RestClientDefinitionException;
+import org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam;
+import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
+import org.eclipse.microprofile.rest.client.ext.AsyncInvocationInterceptor;
+import org.eclipse.microprofile.rest.client.ext.ClientHeadersFactory;
+import org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper;
+import org.glassfish.jersey.client.inject.ParameterInserter;
+import org.glassfish.jersey.client.inject.ParameterInserterProvider;
+import org.glassfish.jersey.internal.inject.InjectionManager;
+import org.glassfish.jersey.internal.inject.Providers;
+import org.glassfish.jersey.model.Parameter;
+
+/**
+ * Model of interface and its annotation.
+ *
+ * @author David Kral
+ */
+class InterfaceModel {
+
+ private final InjectionManager injectionManager;
+ private final Class<?> restClientClass;
+ private final String[] produces;
+ private final String[] consumes;
+ private final String path;
+ private final ClientHeadersFactory clientHeadersFactory;
+ private final CreationalContext<?> creationalContext;
+
+ private final List<ClientHeaderParamModel> clientHeaders;
+ private final List<AsyncInvocationInterceptor> asyncInterceptors;
+ private final Set<ResponseExceptionMapper> responseExceptionMappers;
+ private final Set<ParamConverterProvider> paramConverterProviders;
+ private final Set<Annotation> interceptorAnnotations;
+
+ /**
+ * Creates new model based on interface class. Interface is parsed according to specific annotations.
+ *
+ * @param restClientClass interface class
+ * @param responseExceptionMappers registered exception mappers
+ * @param paramConverterProviders registered parameter providers
+ * @param asyncInterceptors async interceptors
+ * @param injectionManager
+ * @return new model instance
+ */
+ static InterfaceModel from(Class<?> restClientClass,
+ Set<ResponseExceptionMapper> responseExceptionMappers,
+ Set<ParamConverterProvider> paramConverterProviders,
+ List<AsyncInvocationInterceptor> asyncInterceptors,
+ InjectionManager injectionManager) {
+ return new Builder(restClientClass,
+ responseExceptionMappers,
+ paramConverterProviders,
+ asyncInterceptors,
+ injectionManager)
+ .pathValue(restClientClass.getAnnotation(Path.class))
+ .produces(restClientClass.getAnnotation(Produces.class))
+ .consumes(restClientClass.getAnnotation(Consumes.class))
+ .clientHeaders(restClientClass.getAnnotationsByType(ClientHeaderParam.class))
+ .clientHeadersFactory(restClientClass.getAnnotation(RegisterClientHeaders.class))
+ .build();
+ }
+
+ private InterfaceModel(Builder builder) {
+ this.injectionManager = builder.injectionManager;
+ this.restClientClass = builder.restClientClass;
+ this.path = builder.pathValue;
+ this.produces = builder.produces;
+ this.consumes = builder.consumes;
+ this.clientHeaders = builder.clientHeaders;
+ this.clientHeadersFactory = builder.clientHeadersFactory;
+ this.responseExceptionMappers = builder.responseExceptionMappers;
+ this.paramConverterProviders = builder.paramConverterProviders;
+ this.interceptorAnnotations = builder.interceptorAnnotations;
+ this.creationalContext = builder.creationalContext;
+ this.asyncInterceptors = builder.asyncInterceptors;
+ }
+
+ /**
+ * Returns rest client interface class.
+ *
+ * @return interface class
+ */
+ Class<?> getRestClientClass() {
+ return restClientClass;
+ }
+
+ /**
+ * Returns defined produces media types.
+ *
+ * @return produces
+ */
+ String[] getProduces() {
+ return produces;
+ }
+
+ /**
+ * Returns defined consumes media types.
+ *
+ * @return consumes
+ */
+ String[] getConsumes() {
+ return consumes;
+ }
+
+ /**
+ * Returns path value defined on interface level.
+ *
+ * @return path value
+ */
+ String getPath() {
+ return path;
+ }
+
+ /**
+ * Returns registered instance of {@link ClientHeadersFactory}.
+ *
+ * @return registered factory
+ */
+ Optional<ClientHeadersFactory> getClientHeadersFactory() {
+ return Optional.ofNullable(clientHeadersFactory);
+ }
+
+ /**
+ * Returns {@link List} of processed annotation {@link ClientHeaderParam} to {@link ClientHeaderParamModel}
+ *
+ * @return registered factories
+ */
+ List<ClientHeaderParamModel> getClientHeaders() {
+ return clientHeaders;
+ }
+
+ /**
+ * Returns {@link List} of registered {@link AsyncInvocationInterceptor}
+ *
+ * @return registered async interceptors
+ */
+ List<AsyncInvocationInterceptor> getAsyncInterceptors() {
+ return asyncInterceptors;
+ }
+
+ /**
+ * Returns {@link Set} of registered {@link ResponseExceptionMapper}
+ *
+ * @return registered exception mappers
+ */
+ Set<ResponseExceptionMapper> getResponseExceptionMappers() {
+ return responseExceptionMappers;
+ }
+
+ /**
+ * Returns {@link Set} of registered {@link ParamConverterProvider}
+ *
+ * @return registered param converter providers
+ */
+ Set<ParamConverterProvider> getParamConverterProviders() {
+ return paramConverterProviders;
+ }
+
+ /**
+ * Returns {@link Set} of interceptor annotations
+ *
+ * @return interceptor annotations
+ */
+ Set<Annotation> getInterceptorAnnotations() {
+ return interceptorAnnotations;
+ }
+
+ /**
+ * Context bound to this model.
+ *
+ * @return context
+ */
+ CreationalContext<?> getCreationalContext() {
+ return creationalContext;
+ }
+
+ /**
+ *
+ *
+ * @return
+ */
+ public InjectionManager getInjectionManager() {
+ return injectionManager;
+ }
+
+ /**
+ * Resolves value of the method argument.
+ *
+ * @param arg actual argument value
+ * @return converted value of argument
+ */
+ Object resolveParamValue(Object arg, Parameter parameter) {
+ final Iterable<ParameterInserterProvider> parameterInserterProviders
+ = Providers.getAllProviders(injectionManager, ParameterInserterProvider.class);
+ for (final ParameterInserterProvider parameterInserterProvider : parameterInserterProviders) {
+ if (parameterInserterProvider != null) {
+ ParameterInserter<Object, Object> inserter =
+ (ParameterInserter<Object, Object>) parameterInserterProvider.get(parameter);
+ return inserter.insert(arg);
+ }
+ }
+ return arg;
+ }
+
+ private static class Builder {
+
+ private final Class<?> restClientClass;
+
+ private final InjectionManager injectionManager;
+ private String pathValue;
+ private String[] produces;
+ private String[] consumes;
+ private ClientHeadersFactory clientHeadersFactory;
+ private CreationalContext<?> creationalContext;
+ private List<ClientHeaderParamModel> clientHeaders;
+ private List<AsyncInvocationInterceptor> asyncInterceptors;
+ private Set<ResponseExceptionMapper> responseExceptionMappers;
+ private Set<ParamConverterProvider> paramConverterProviders;
+ private Set<Annotation> interceptorAnnotations;
+
+ private Builder(Class<?> restClientClass,
+ Set<ResponseExceptionMapper> responseExceptionMappers,
+ Set<ParamConverterProvider> paramConverterProviders,
+ List<AsyncInvocationInterceptor> asyncInterceptors,
+ InjectionManager injectionManager) {
+ this.injectionManager = injectionManager;
+ this.restClientClass = restClientClass;
+ this.responseExceptionMappers = responseExceptionMappers;
+ this.paramConverterProviders = paramConverterProviders;
+ this.asyncInterceptors = asyncInterceptors;
+ filterAllInterceptorAnnotations();
+ }
+
+ private void filterAllInterceptorAnnotations() {
+ creationalContext = null;
+ interceptorAnnotations = new HashSet<>();
+ try {
+ if (CDI.current() != null) {
+ BeanManager beanManager = CDI.current().getBeanManager();
+ creationalContext = beanManager.createCreationalContext(null);
+ for (Annotation annotation : restClientClass.getAnnotations()) {
+ if (beanManager.isInterceptorBinding(annotation.annotationType())) {
+ interceptorAnnotations.add(annotation);
+ }
+ }
+ }
+ } catch (IllegalStateException ignored) {
+ //CDI not present. Ignore.
+ }
+ }
+
+ /**
+ * Path value from {@link Path} annotation. If annotation is null, empty String is set as path.
+ *
+ * @param path {@link Path} annotation
+ * @return updated Builder instance
+ */
+ Builder pathValue(Path path) {
+ this.pathValue = path != null ? path.value() : "";
+ //if only / is added to path like this "localhost:80/test" it makes invalid path "localhost:80/test/"
+ this.pathValue = pathValue.equals("/") ? "" : pathValue;
+ return this;
+ }
+
+ /**
+ * Extracts MediaTypes from {@link Produces} annotation.
+ * If annotation is null, new String array with {@link MediaType#WILDCARD} is set.
+ *
+ * @param produces {@link Produces} annotation
+ * @return updated Builder instance
+ */
+ Builder produces(Produces produces) {
+ this.produces = produces != null ? produces.value() : new String[] {MediaType.WILDCARD};
+ return this;
+ }
+
+ /**
+ * Extracts MediaTypes from {@link Consumes} annotation.
+ * If annotation is null, new String array with {@link MediaType#WILDCARD} is set.
+ *
+ * @param consumes {@link Consumes} annotation
+ * @return updated Builder instance
+ */
+ Builder consumes(Consumes consumes) {
+ this.consumes = consumes != null ? consumes.value() : new String[] {MediaType.WILDCARD};
+ return this;
+ }
+
+ /**
+ * Process data from {@link ClientHeaderParam} annotation to extract methods and values.
+ *
+ * @param clientHeaderParams {@link ClientHeaderParam} annotations
+ * @return updated Builder instance
+ */
+ Builder clientHeaders(ClientHeaderParam[] clientHeaderParams) {
+ clientHeaders = Arrays.stream(clientHeaderParams)
+ .map(clientHeaderParam -> new ClientHeaderParamModel(restClientClass, clientHeaderParam))
+ .collect(Collectors.toList());
+ return this;
+ }
+
+ Builder clientHeadersFactory(RegisterClientHeaders registerClientHeaders) {
+ clientHeadersFactory = registerClientHeaders != null
+ ? ReflectionUtil.createInstance(registerClientHeaders.value())
+ : null;
+ return this;
+ }
+
+ /**
+ * Creates new InterfaceModel instance.
+ *
+ * @return new instance
+ */
+ InterfaceModel build() {
+ validateHeaderDuplicityNames();
+ return new InterfaceModel(this);
+ }
+
+ private void validateHeaderDuplicityNames() {
+ ArrayList<String> names = new ArrayList<>();
+ for (ClientHeaderParamModel clientHeaderParamModel : clientHeaders) {
+ String headerName = clientHeaderParamModel.getHeaderName();
+ if (names.contains(headerName)) {
+ throw new RestClientDefinitionException("Header name cannot be registered more then once on the same target."
+ + "See " + restClientClass.getName());
+ }
+ names.add(headerName);
+ }
+ }
+ }
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/InterfaceUtil.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/InterfaceUtil.java
new file mode 100644
index 0000000..83aa97a
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/InterfaceUtil.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2019 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.microprofile.restclient;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.security.AccessController;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import javax.ws.rs.HttpMethod;
+
+import org.eclipse.microprofile.rest.client.RestClientDefinitionException;
+import org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam;
+import org.glassfish.jersey.internal.util.ReflectionHelper;
+
+/**
+ * Utils for interface handling.
+ *
+ * @author David Kral
+ */
+class InterfaceUtil {
+
+ private static final String PARAMETER_PARSE_REGEXP = "(?<=\\{).+?(?=\\})";
+ private static final Pattern PATTERN = Pattern.compile(PARAMETER_PARSE_REGEXP);
+
+ /**
+ * Parses all required parameters from template string.
+ *
+ * @param template template string
+ * @return parsed parameters
+ */
+ static List<String> parseParameters(String template) {
+ List<String> allMatches = new ArrayList<>();
+ Matcher m = PATTERN.matcher(template);
+ while (m.find()) {
+ allMatches.add(m.group());
+ }
+ return allMatches;
+ }
+
+ /**
+ * Validates and returns proper compute method defined in {@link ClientHeaderParam}.
+ *
+ * @param iClass interface class
+ * @param headerValue value of the header
+ * @return parsed method
+ */
+ static Method parseComputeMethod(Class<?> iClass, String[] headerValue) {
+ List<String> computeMethodNames = InterfaceUtil.parseParameters(Arrays.toString(headerValue));
+ /*if more than one string is specified as the value attribute, and one of the strings is a
+ compute method (surrounded by curly braces), then the implementation will throw a
+ RestClientDefinitionException*/
+ if (headerValue.length > 1 && computeMethodNames.size() > 0) {
+ throw new RestClientDefinitionException("@ClientHeaderParam annotation should not contain compute method "
+ + "when multiple values are present in value attribute. "
+ + "See " + iClass.getName());
+ }
+ if (computeMethodNames.size() == 1) {
+ String methodName = computeMethodNames.get(0);
+ List<Method> computeMethods = getAnnotationComputeMethod(iClass, methodName);
+ if (computeMethods.size() != 1) {
+ throw new RestClientDefinitionException("No valid compute method found for name: " + methodName);
+ }
+ return computeMethods.get(0);
+ }
+ return null;
+ }
+
+ private static List<Method> getAnnotationComputeMethod(Class<?> iClass, String methodName) {
+ if (methodName.contains(".")) {
+ return getStaticComputeMethod(methodName);
+ }
+ return getComputeMethod(iClass, methodName);
+ }
+
+ private static List<Method> getStaticComputeMethod(String methodName) {
+ int lastIndex = methodName.lastIndexOf(".");
+ String className = methodName.substring(0, lastIndex);
+ String staticMethodName = methodName.substring(lastIndex + 1);
+ Class<?> classWithStaticMethod = AccessController.doPrivileged(ReflectionHelper.classForNamePA(className));
+ if (classWithStaticMethod == null) {
+ throw new IllegalStateException("No class with following name found: " + className);
+ }
+ return getComputeMethod(classWithStaticMethod, staticMethodName);
+ }
+
+ private static List<Method> getComputeMethod(Class<?> iClass, String methodName) {
+ return Arrays.stream(iClass.getMethods())
+ // filter out methods with specified name only
+ .filter(method -> method.getName().equals(methodName))
+ // filter out other methods than default and static
+ .filter(method -> method.isDefault() || Modifier.isStatic(method.getModifiers()))
+ // filter out methods without required return type
+ .filter(method -> method.getReturnType().equals(String.class)
+ || method.getReturnType().equals(String[].class))
+ // filter out methods without required parameter types
+ .filter(method -> method.getParameterTypes().length == 0 || (
+ method.getParameterTypes().length == 1
+ && method.getParameterTypes()[0].equals(String.class)))
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Returns {@link List} of annotations which are type of {@link HttpMethod}.
+ *
+ * @param annotatedElement element with annotations
+ * @return annotations of given type
+ */
+ static List<Class<?>> getHttpAnnotations(AnnotatedElement annotatedElement) {
+ return Arrays.stream(annotatedElement.getDeclaredAnnotations())
+ .filter(annotation -> annotation.annotationType().getAnnotation(HttpMethod.class) != null)
+ .map(Annotation::annotationType)
+ .collect(Collectors.toList());
+ }
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/JerseyRestClientBuilderResolver.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/JerseyRestClientBuilderResolver.java
new file mode 100644
index 0000000..db8e918
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/JerseyRestClientBuilderResolver.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2019 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.microprofile.restclient;
+
+import org.eclipse.microprofile.rest.client.RestClientBuilder;
+import org.eclipse.microprofile.rest.client.spi.RestClientBuilderResolver;
+
+/**
+ *
+ *
+ * @author David Kral
+ */
+public class JerseyRestClientBuilderResolver extends RestClientBuilderResolver {
+ @Override
+ public RestClientBuilder newBuilder() {
+ return new RestClientBuilderImpl();
+ }
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/MatrixParamModel.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/MatrixParamModel.java
new file mode 100644
index 0000000..440a414
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/MatrixParamModel.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019 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.microprofile.restclient;
+
+import java.lang.annotation.Annotation;
+import java.util.Collection;
+
+import javax.ws.rs.MatrixParam;
+import javax.ws.rs.client.WebTarget;
+
+/**
+ * Contains information to method parameter which is annotated by {@link MatrixParam}.
+ *
+ * @author David Kral
+ */
+class MatrixParamModel extends ParamModel<WebTarget> {
+
+ private final String matrixParamName;
+
+ /**
+ * Creates new matrix model.
+ *
+ * @param builder
+ */
+ MatrixParamModel(Builder builder) {
+ super(builder);
+ matrixParamName = builder.matrixParamName();
+ }
+
+ @Override
+ public WebTarget handleParameter(WebTarget requestPart, Class<?> annotationClass, Object instance) {
+ Object resolvedValue = interfaceModel.resolveParamValue(instance, parameter);
+ if (resolvedValue instanceof Collection) {
+ return requestPart.matrixParam(matrixParamName, ((Collection) resolvedValue).toArray());
+ } else {
+ return requestPart.matrixParam(matrixParamName, resolvedValue);
+ }
+ }
+
+ @Override
+ public boolean handles(Class<Annotation> annotation) {
+ return MatrixParam.class.equals(annotation);
+ }
+
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/MethodModel.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/MethodModel.java
new file mode 100644
index 0000000..52079eb
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/MethodModel.java
@@ -0,0 +1,670 @@
+/*
+ * Copyright (c) 2019 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.microprofile.restclient;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Parameter;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Proxy;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
+
+import javax.enterprise.inject.spi.BeanManager;
+import javax.enterprise.inject.spi.CDI;
+import javax.enterprise.inject.spi.InterceptionType;
+import javax.enterprise.inject.spi.Interceptor;
+import javax.json.JsonValue;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.CookieParam;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.MatrixParam;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.Invocation;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedHashMap;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+
+import org.eclipse.microprofile.rest.client.RestClientDefinitionException;
+import org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam;
+import org.eclipse.microprofile.rest.client.ext.AsyncInvocationInterceptor;
+import org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper;
+
+/**
+ * Method model contains all information about method defined in rest client interface.
+ *
+ * @author David Kral
+ */
+class MethodModel {
+
+ private static final String INVOKED_METHOD = "org.eclipse.microprofile.rest.client.invokedMethod";
+
+ private final InterfaceModel interfaceModel;
+
+ private final Method method;
+ private final Class<?> returnType;
+ private final String httpMethod;
+ private final String path;
+ private final String[] produces;
+ private final String[] consumes;
+ private final List<ParamModel> parameterModels;
+ private final List<ClientHeaderParamModel> clientHeaders;
+ private final List<InterceptorInvocationContext.InvocationInterceptor> invocationInterceptors;
+ private final RestClientModel subResourceModel;
+
+ /**
+ * Processes interface method and creates new instance of the model.
+ *
+ * @param interfaceModel
+ * @param method
+ * @return
+ */
+ static MethodModel from(InterfaceModel interfaceModel, Method method) {
+ return new Builder(interfaceModel, method)
+ .returnType(method.getGenericReturnType())
+ .httpMethod(parseHttpMethod(interfaceModel, method))
+ .pathValue(method.getAnnotation(Path.class))
+ .produces(method.getAnnotation(Produces.class))
+ .consumes(method.getAnnotation(Consumes.class))
+ .parameters(parameterModels(interfaceModel, method))
+ .clientHeaders(method.getAnnotationsByType(ClientHeaderParam.class))
+ .build();
+ }
+
+ private MethodModel(Builder builder) {
+ this.method = builder.method;
+ this.interfaceModel = builder.interfaceModel;
+ this.returnType = builder.returnType;
+ this.httpMethod = builder.httpMethod;
+ this.path = builder.pathValue;
+ this.produces = builder.produces;
+ this.consumes = builder.consumes;
+ this.parameterModels = builder.parameterModels;
+ this.clientHeaders = builder.clientHeaders;
+ this.invocationInterceptors = builder.invocationInterceptors;
+ if (httpMethod.isEmpty()) {
+ subResourceModel = RestClientModel.from(returnType,
+ interfaceModel.getResponseExceptionMappers(),
+ interfaceModel.getParamConverterProviders(),
+ interfaceModel.getAsyncInterceptors(),
+ interfaceModel.getInjectionManager());
+ } else {
+ subResourceModel = null;
+ }
+ }
+
+ /**
+ * Returns all registered cdi interceptors to this method.
+ *
+ * @return registered interceptors
+ */
+ List<InterceptorInvocationContext.InvocationInterceptor> getInvocationInterceptors() {
+ return invocationInterceptors;
+ }
+
+ /**
+ * Invokes corresponding method according to
+ *
+ * @param classLevelTarget
+ * @param method
+ * @param args
+ * @return
+ */
+ @SuppressWarnings("unchecked")
+ //I am checking the type of parameter and I know it should handle instance I am sending
+ Object invokeMethod(WebTarget classLevelTarget, Method method, Object[] args) {
+ WebTarget methodLevelTarget = classLevelTarget.path(path);
+
+ AtomicReference<Object> entity = new AtomicReference<>();
+ AtomicReference<WebTarget> webTargetAtomicReference = new AtomicReference<>(methodLevelTarget);
+ parameterModels.stream()
+ .filter(parameterModel -> parameterModel.handles(PathParam.class))
+ .forEach(parameterModel ->
+ webTargetAtomicReference.set((WebTarget)
+ parameterModel
+ .handleParameter(webTargetAtomicReference.get(),
+ PathParam.class,
+ args[parameterModel
+ .getParamPosition()])));
+
+ parameterModels.stream()
+ .filter(ParamModel::isEntity)
+ .findFirst()
+ .ifPresent(parameterModel -> entity.set(args[parameterModel.getParamPosition()]));
+
+ WebTarget webTarget = webTargetAtomicReference.get();
+ if (httpMethod.isEmpty()) {
+ //sub resource method
+ return subResourceProxy(webTarget, returnType);
+ }
+ webTarget = addQueryParams(webTarget, args);
+ webTarget = addMatrixParams(webTarget, args);
+
+ Invocation.Builder builder = webTarget
+ .request(produces)
+ .property(INVOKED_METHOD, method)
+ .headers(addCustomHeaders(args));
+ builder = addCookies(builder, args);
+
+ Object response;
+
+ if (CompletionStage.class.isAssignableFrom(method.getReturnType())) {
+ response = asynchronousCall(builder, entity.get(), method);
+ } else {
+ response = synchronousCall(builder, entity.get(), method);
+ }
+ return response;
+ }
+
+ private Object synchronousCall(Invocation.Builder builder, Object entity, Method method) {
+ Response response;
+
+ if (entity != null
+ && !httpMethod.equals(GET.class.getSimpleName())
+ && !httpMethod.equals(DELETE.class.getSimpleName())) {
+ response = builder.method(httpMethod, Entity.entity(entity, consumes[0]));
+ } else {
+ response = builder.method(httpMethod);
+ }
+
+ evaluateResponse(response, method);
+
+ if (returnType.equals(Void.class)) {
+ return null;
+ } else if (returnType.equals(Response.class)) {
+ return response;
+ }
+ return response.readEntity(returnType);
+ }
+
+ private CompletableFuture asynchronousCall(Invocation.Builder builder, Object entity, Method method) {
+ ParameterizedType type = (ParameterizedType) method.getGenericReturnType();
+ Type actualTypeArgument = type.getActualTypeArguments()[0]; //completionStage<actualTypeArgument>
+ CompletableFuture<Object> result = new CompletableFuture<>();
+ Future<Response> theFuture;
+ if (entity != null
+ && !httpMethod.equals(GET.class.getSimpleName())
+ && !httpMethod.equals(DELETE.class.getSimpleName())) {
+ theFuture = builder.async().method(httpMethod, Entity.entity(entity, consumes[0]));
+ } else {
+ theFuture = builder.async().method(httpMethod);
+ }
+
+ CompletableFuture<Response> completableFuture = (CompletableFuture<Response>) theFuture;
+ completableFuture.thenAccept(response -> {
+ interfaceModel.getAsyncInterceptors().forEach(AsyncInvocationInterceptor::removeContext);
+ try {
+ evaluateResponse(response, method);
+ if (returnType.equals(Void.class)) {
+ result.complete(null);
+ } else if (returnType.equals(Response.class)) {
+ result.complete(response);
+ } else {
+ result.complete(response.readEntity(new GenericType<>(actualTypeArgument)));
+ }
+ } catch (Exception e) {
+ result.completeExceptionally(e);
+ }
+ }).exceptionally(throwable -> {
+ interfaceModel.getAsyncInterceptors().forEach(AsyncInvocationInterceptor::removeContext);
+ result.completeExceptionally(throwable);
+ return null;
+ });
+
+ return result;
+ }
+
+ @SuppressWarnings("unchecked")
+ private <T> T subResourceProxy(WebTarget webTarget, Class<T> subResourceType) {
+ return (T) Proxy.newProxyInstance(subResourceType.getClassLoader(),
+ new Class[] {subResourceType},
+ new ProxyInvocationHandler(webTarget, subResourceModel)
+ );
+ }
+
+ @SuppressWarnings("unchecked") //I am checking the type of parameter and I know it should handle instance I am sending
+ private WebTarget addQueryParams(WebTarget webTarget, Object[] args) {
+ Map<String, Object[]> queryParams = new HashMap<>();
+ WebTarget toReturn = webTarget;
+ parameterModels.stream()
+ .filter(parameterModel -> parameterModel.handles(QueryParam.class))
+ .forEach(parameterModel -> parameterModel.handleParameter(queryParams,
+ QueryParam.class,
+ args[parameterModel.getParamPosition()]));
+
+ for (Map.Entry<String, Object[]> entry : queryParams.entrySet()) {
+ toReturn = toReturn.queryParam(entry.getKey(), entry.getValue());
+ }
+ return toReturn;
+ }
+
+ @SuppressWarnings("unchecked") //I am checking the type of parameter and I know it should handle instance I am sending
+ private WebTarget addMatrixParams(WebTarget webTarget, Object[] args) {
+ AtomicReference<WebTarget> toReturn = new AtomicReference<>(webTarget);
+ parameterModels.stream()
+ .filter(parameterModel -> parameterModel.handles(MatrixParam.class))
+ .forEach(parameterModel -> toReturn
+ .set((WebTarget) parameterModel.handleParameter(toReturn.get(),
+ MatrixParam.class,
+ args[parameterModel.getParamPosition()])));
+ return toReturn.get();
+ }
+
+ @SuppressWarnings("unchecked") //I am checking the type of parameter and I know it should handle instance I am sending
+ private Invocation.Builder addCookies(Invocation.Builder builder, Object[] args) {
+ Map<String, String> cookies = new HashMap<>();
+ Invocation.Builder toReturn = builder;
+ parameterModels.stream()
+ .filter(parameterModel -> parameterModel.handles(CookieParam.class))
+ .forEach(parameterModel -> parameterModel.handleParameter(cookies,
+ CookieParam.class,
+ args[parameterModel.getParamPosition()]));
+
+ for (Map.Entry<String, String> entry : cookies.entrySet()) {
+ toReturn = toReturn.cookie(entry.getKey(), entry.getValue());
+ }
+ return toReturn;
+ }
+
+ private MultivaluedMap<String, Object> addCustomHeaders(Object[] args) {
+ MultivaluedMap<String, Object> result = new MultivaluedHashMap<>();
+ for (Map.Entry<String, List<String>> entry : resolveCustomHeaders(args).entrySet()) {
+ entry.getValue().forEach(val -> result.add(entry.getKey(), val));
+ }
+ for (String produce : produces) {
+ result.add(HttpHeaders.ACCEPT, produce);
+ }
+ result.add(HttpHeaders.CONTENT_TYPE, consumes[0]);
+ return result;
+ }
+
+ @SuppressWarnings("unchecked") //I am checking the type of parameter and I know it should handle instance I am sending
+ private MultivaluedMap<String, String> resolveCustomHeaders(Object[] args) {
+ MultivaluedMap<String, String> customHeaders = new MultivaluedHashMap<>();
+ customHeaders.putAll(createMultivaluedHeadersMap(interfaceModel.getClientHeaders()));
+ customHeaders.putAll(createMultivaluedHeadersMap(clientHeaders));
+ parameterModels.stream()
+ .filter(parameterModel -> parameterModel.handles(HeaderParam.class))
+ .forEach(parameterModel -> parameterModel.handleParameter(customHeaders,
+ HeaderParam.class,
+ args[parameterModel.getParamPosition()]));
+
+ MultivaluedMap<String, String> inbound = new MultivaluedHashMap<>();
+ HeadersContext.get().ifPresent(headersContext -> inbound.putAll(headersContext.inboundHeaders()));
+
+ AtomicReference<MultivaluedMap<String, String>> toReturn = new AtomicReference<>(customHeaders);
+ interfaceModel.getClientHeadersFactory().ifPresent(clientHeadersFactory -> toReturn
+ .set(clientHeadersFactory.update(inbound, customHeaders)));
+ return toReturn.get();
+ }
+
+ private <T> MultivaluedMap<String, String> createMultivaluedHeadersMap(List<ClientHeaderParamModel> clientHeaders) {
+ MultivaluedMap<String, String> customHeaders = new MultivaluedHashMap<>();
+ for (ClientHeaderParamModel clientHeaderParamModel : clientHeaders) {
+ if (clientHeaderParamModel.getComputeMethod() == null) {
+ customHeaders
+ .put(clientHeaderParamModel.getHeaderName(), Arrays.asList(clientHeaderParamModel.getHeaderValue()));
+ } else {
+ try {
+ Method method = clientHeaderParamModel.getComputeMethod();
+ if (method.isDefault()) {
+ //method is interface default
+ //we need to create instance of the interface to be able to call default method
+ T instance = (T) ReflectionUtil.createProxyInstance(interfaceModel.getRestClientClass());
+ if (method.getParameterCount() > 0) {
+ customHeaders.put(clientHeaderParamModel.getHeaderName(),
+ createList(method.invoke(instance, clientHeaderParamModel.getHeaderName())));
+ } else {
+ customHeaders.put(clientHeaderParamModel.getHeaderName(),
+ createList(method.invoke(instance, null)));
+ }
+ } else {
+ //Method is static
+ if (method.getParameterCount() > 0) {
+ customHeaders.put(clientHeaderParamModel.getHeaderName(),
+ createList(method.invoke(null, clientHeaderParamModel.getHeaderName())));
+ } else {
+ customHeaders.put(clientHeaderParamModel.getHeaderName(),
+ createList(method.invoke(null, null)));
+ }
+ }
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ } catch (InvocationTargetException e) {
+ if (clientHeaderParamModel.isRequired()) {
+ if (e.getCause() instanceof RuntimeException) {
+ throw (RuntimeException) e.getCause();
+ }
+ throw new RuntimeException(e.getCause());
+ }
+ }
+ }
+ }
+ return customHeaders;
+ }
+
+ private static List<String> createList(Object value) {
+ if (value instanceof String[]) {
+ String[] array = (String[]) value;
+ return Arrays.asList(array);
+ }
+ String s = (String) value;
+ return Collections.singletonList(s);
+ }
+
+ /**
+ * Evaluation of {@link Response} if it is applicable for any of the registered {@link ResponseExceptionMapper} providers.
+ *
+ * @param response obtained response
+ * @param method called method
+ */
+ void evaluateResponse(Response response, Method method) {
+ ResponseExceptionMapper lowestMapper = null;
+ Throwable throwable = null;
+ for (ResponseExceptionMapper responseExceptionMapper : interfaceModel.getResponseExceptionMappers()) {
+ if (responseExceptionMapper.handles(response.getStatus(), response.getHeaders())) {
+ if (lowestMapper == null
+ || throwable == null
+ || lowestMapper.getPriority() > responseExceptionMapper.getPriority()) {
+ lowestMapper = responseExceptionMapper;
+ Throwable tmp = lowestMapper.toThrowable(response);
+ if (tmp != null) {
+ throwable = tmp;
+ }
+ }
+ }
+ }
+ if (throwable != null) {
+ if (throwable instanceof RuntimeException) {
+ throw (RuntimeException) throwable;
+ } else if (throwable instanceof Error) {
+ throw (Error) throwable;
+ }
+ for (Class<?> exception : method.getExceptionTypes()) {
+ if (throwable.getClass().isAssignableFrom(exception)) {
+ throw new WebApplicationException(throwable);
+ }
+ }
+ }
+ }
+
+ private static String parseHttpMethod(InterfaceModel classModel, Method method) {
+ List<Class<?>> httpAnnotations = InterfaceUtil.getHttpAnnotations(method);
+ if (httpAnnotations.size() > 1) {
+ throw new RestClientDefinitionException("Method can't have more then one annotation of @HttpMethod type. "
+ + "See " + classModel.getRestClientClass().getName()
+ + "::" + method.getName());
+ } else if (httpAnnotations.isEmpty()) {
+ //Sub resource method
+ return "";
+ }
+ return httpAnnotations.get(0).getSimpleName();
+ }
+
+ private static List<ParamModel> parameterModels(InterfaceModel classModel, Method method) {
+ ArrayList<ParamModel> parameterModels = new ArrayList<>();
+ final List<org.glassfish.jersey.model.Parameter> jerseyParameters = org.glassfish.jersey.model.Parameter
+ .create(classModel.getRestClientClass(), classModel.getRestClientClass(),
+ method, false);
+ Parameter[] parameters = method.getParameters();
+ for (int i = 0; i < parameters.length; i++) {
+ parameterModels.add(ParamModel.from(classModel, parameters[i].getType(), parameters[i], jerseyParameters.get(i), i));
+ }
+ return parameterModels;
+ }
+
+ private static class Builder {
+
+ private final InterfaceModel interfaceModel;
+ private final Method method;
+
+ private Class<?> returnType;
+ private String httpMethod;
+ private String pathValue;
+ private String[] produces;
+ private String[] consumes;
+ private List<ParamModel> parameterModels;
+ private List<ClientHeaderParamModel> clientHeaders;
+ private List<InterceptorInvocationContext.InvocationInterceptor> invocationInterceptors;
+
+ private Builder(InterfaceModel interfaceModel, Method method) {
+ this.interfaceModel = interfaceModel;
+ this.method = method;
+ filterAllInterceptorAnnotations();
+ }
+
+ private void filterAllInterceptorAnnotations() {
+ invocationInterceptors = new ArrayList<>();
+ try {
+ if (CDI.current() != null) {
+ Set<Annotation> interceptorAnnotations = new HashSet<>();
+ BeanManager beanManager = CDI.current().getBeanManager();
+ for (Annotation annotation : method.getAnnotations()) {
+ if (beanManager.isInterceptorBinding(annotation.annotationType())) {
+ interceptorAnnotations.add(annotation);
+ }
+ }
+ interceptorAnnotations.addAll(interfaceModel.getInterceptorAnnotations());
+ Annotation[] allInterceptorAnnotations = interceptorAnnotations.toArray(new Annotation[0]);
+ if (allInterceptorAnnotations.length == 0) {
+ return;
+ }
+ List<Interceptor<?>> interceptors = beanManager.resolveInterceptors(InterceptionType.AROUND_INVOKE,
+ allInterceptorAnnotations);
+ if (!interceptors.isEmpty()) {
+ for (Interceptor<?> interceptor : interceptors) {
+ Object interceptorInstance = beanManager.getReference(interceptor,
+ interceptor.getBeanClass(),
+ interfaceModel.getCreationalContext());
+ invocationInterceptors.add(new InterceptorInvocationContext
+ .InvocationInterceptor(interceptorInstance,
+ interceptor));
+ }
+ }
+ }
+ } catch (IllegalStateException ignored) {
+ //CDI not present. Ignore.
+ }
+ }
+
+ /**
+ * Return type of the method.
+ *
+ * @param returnType Method return type
+ * @return updated Builder instance
+ */
+ Builder returnType(Type returnType) {
+ if (returnType instanceof ParameterizedType) {
+ this.returnType = (Class<?>) ((ParameterizedType) returnType).getActualTypeArguments()[0];
+ } else {
+ this.returnType = (Class<?>) returnType;
+ }
+ return this;
+ }
+
+ /**
+ * HTTP method of the method.
+ *
+ * @param httpMethod HTTP method of the method
+ * @return updated Builder instance
+ */
+ Builder httpMethod(String httpMethod) {
+ this.httpMethod = httpMethod;
+ return this;
+ }
+
+ /**
+ * Path value from {@link Path} annotation. If annotation is null, empty String is set as path.
+ *
+ * @param path {@link Path} annotation
+ * @return updated Builder instance
+ */
+ Builder pathValue(Path path) {
+ this.pathValue = path != null ? path.value() : "";
+ //if only / is added to path like this "localhost:80/test" it makes invalid path "localhost:80/test/"
+ this.pathValue = pathValue.equals("/") ? "" : pathValue;
+ return this;
+ }
+
+ /**
+ * Extracts MediaTypes from {@link Produces} annotation.
+ * If annotation is null, value from {@link InterfaceModel} is set.
+ *
+ * @param produces {@link Produces} annotation
+ * @return updated Builder instance
+ */
+ Builder produces(Produces produces) {
+ this.produces = produces == null ? interfaceModel.getProduces() : produces.value();
+ return this;
+ }
+
+ /**
+ * Extracts MediaTypes from {@link Consumes} annotation.
+ * If annotation is null, value from {@link InterfaceModel} is set.
+ *
+ * @param consumes {@link Consumes} annotation
+ * @return updated Builder instance
+ */
+ Builder consumes(Consumes consumes) {
+ this.consumes = consumes == null ? interfaceModel.getConsumes() : consumes.value();
+ return this;
+ }
+
+ /**
+ * {@link List} of transformed method parameters.
+ *
+ * @param parameterModels {@link List} of parameters
+ * @return updated Builder instance
+ */
+ Builder parameters(List<ParamModel> parameterModels) {
+ this.parameterModels = parameterModels;
+ return this;
+ }
+
+ /**
+ * Process data from {@link ClientHeaderParam} annotation to extract methods and values.
+ *
+ * @param clientHeaderParams {@link ClientHeaderParam} annotations
+ * @return updated Builder instance
+ */
+ Builder clientHeaders(ClientHeaderParam[] clientHeaderParams) {
+ clientHeaders = Arrays.stream(clientHeaderParams)
+ .map(clientHeaderParam -> new ClientHeaderParamModel(interfaceModel.getRestClientClass(), clientHeaderParam))
+ .collect(Collectors.toList());
+ return this;
+ }
+
+ /**
+ * Creates new MethodModel instance.
+ *
+ * @return new instance
+ */
+ MethodModel build() {
+ validateParameters();
+ validateHeaderDuplicityNames();
+ Optional<ParamModel> entity = parameterModels.stream()
+ .filter(ParamModel::isEntity)
+ .findFirst();
+ if (JsonValue.class.isAssignableFrom(returnType)
+ || (
+ entity.isPresent() && entity.get().getType() instanceof Class
+ && JsonValue.class.isAssignableFrom((Class<?>) entity.get().getType()))) {
+ this.consumes = new String[] {MediaType.APPLICATION_JSON};
+ }
+ return new MethodModel(this);
+ }
+
+ private void validateParameters() {
+ UriBuilder uriBuilder = UriBuilder.fromUri(interfaceModel.getPath()).path(pathValue);
+ List<String> parameters = InterfaceUtil.parseParameters(uriBuilder.toTemplate());
+ List<String> methodPathParameters = new ArrayList<>();
+ List<ParamModel> pathHandlingParams = parameterModels.stream()
+ .filter(parameterModel -> parameterModel.handles(PathParam.class))
+ .collect(Collectors.toList());
+ for (ParamModel paramModel : pathHandlingParams) {
+ if (paramModel instanceof PathParamModel) {
+ methodPathParameters.add(((PathParamModel) paramModel).getPathParamName());
+ } else if (paramModel instanceof BeanParamModel) {
+ for (ParamModel beanPathParams : ((BeanParamModel) paramModel).getAllParamsWithType(PathParam.class)) {
+ methodPathParameters.add(((PathParamModel) beanPathParams).getPathParamName());
+ }
+ }
+ }
+ for (String parameterName : methodPathParameters) {
+ if (!parameters.contains(parameterName)) {
+ throw new RestClientDefinitionException("Parameter name " + parameterName + " on "
+ + interfaceModel.getRestClientClass().getName()
+ + "::" + method.getName()
+ + " doesn't match any @Path variable name.");
+ }
+ parameters.remove(parameterName);
+ }
+ if (!parameters.isEmpty()) {
+ throw new RestClientDefinitionException("Some variable names does not have matching @PathParam "
+ + "defined on method " + interfaceModel.getRestClientClass()
+ .getName()
+ + "::" + method.getName());
+ }
+ List<ParamModel> entities = parameterModels.stream()
+ .filter(ParamModel::isEntity)
+ .collect(Collectors.toList());
+ if (entities.size() > 1) {
+ throw new RestClientDefinitionException("You cant have more than 1 entity method parameter! Check "
+ + interfaceModel.getRestClientClass().getName()
+ + "::" + method.getName());
+ }
+ }
+
+ private void validateHeaderDuplicityNames() {
+ ArrayList<String> names = new ArrayList<>();
+ for (ClientHeaderParamModel clientHeaderParamModel : clientHeaders) {
+ String headerName = clientHeaderParamModel.getHeaderName();
+ if (names.contains(headerName)) {
+ throw new RestClientDefinitionException("Header name cannot be registered more then once on the same target."
+ + "See " + interfaceModel.getRestClientClass().getName());
+ }
+ names.add(headerName);
+ }
+ }
+ }
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/ParamModel.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/ParamModel.java
new file mode 100644
index 0000000..800005d
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/ParamModel.java
@@ -0,0 +1,310 @@
+/*
+ * Copyright (c) 2019 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.microprofile.restclient;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Type;
+
+import javax.ws.rs.BeanParam;
+import javax.ws.rs.CookieParam;
+import javax.ws.rs.FormParam;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.MatrixParam;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.QueryParam;
+
+import org.glassfish.jersey.model.Parameter;
+
+/**
+ * Abstract model for all elements with parameter annotation.
+ *
+ * @author David Kral
+ */
+abstract class ParamModel<T> {
+
+ protected final InterfaceModel interfaceModel;
+ protected final Parameter parameter;
+ private final Type type;
+ private final AnnotatedElement annotatedElement;
+ private final int paramPosition;
+ private final boolean entity;
+
+ /**
+ * Processes parameter annotations and creates new instance of the model corresponding model.
+ *
+ * @param interfaceModel model of the interface
+ * @param type annotated element type
+ * @param annotatedElement annotated element
+ * @param position position in method params
+ * @return new parameter instance
+ */
+ static ParamModel from(InterfaceModel interfaceModel, Type type, AnnotatedElement annotatedElement,
+ Parameter parameter, int position) {
+ return new Builder(interfaceModel, type, annotatedElement, parameter)
+ .pathParamName(annotatedElement.getAnnotation(PathParam.class))
+ .headerParamName(annotatedElement.getAnnotation(HeaderParam.class))
+ .beanParam(annotatedElement.getAnnotation(BeanParam.class))
+ .cookieParam(annotatedElement.getAnnotation(CookieParam.class))
+ .queryParam(annotatedElement.getAnnotation(QueryParam.class))
+ .matrixParam(annotatedElement.getAnnotation(MatrixParam.class))
+ .formParam(annotatedElement.getAnnotation(FormParam.class))
+ .paramPosition(position)
+ .build();
+ }
+
+ ParamModel(Builder builder) {
+ this.interfaceModel = builder.interfaceModel;
+ this.type = builder.type;
+ this.annotatedElement = builder.annotatedElement;
+ this.entity = builder.entity;
+ this.paramPosition = builder.paramPosition;
+ this.parameter = builder.parameter;
+ }
+
+ /**
+ * Returns {@link Type} of the parameter.
+ *
+ * @return parameter type
+ */
+ Type getType() {
+ return type;
+ }
+
+ /**
+ * Returns annotated element.
+ *
+ * @return annotated element
+ */
+ AnnotatedElement getAnnotatedElement() {
+ return annotatedElement;
+ }
+
+ int getParamPosition() {
+ return paramPosition;
+ }
+
+ /**
+ * Returns value if parameter is entity or not.
+ *
+ * @return if parameter is entity
+ */
+ boolean isEntity() {
+ return entity;
+ }
+
+ /**
+ * Transforms parameter to be part of the request.
+ *
+ * @param requestPart part of a request
+ * @param annotationClass annotation type
+ * @param instance actual method parameter value
+ * @return updated request part
+ */
+ abstract T handleParameter(T requestPart, Class<?> annotationClass, Object instance);
+
+ /**
+ * Evaluates if the annotation passed in parameter is supported by this parameter.
+ *
+ * @param annotation checked annotation
+ * @return if annotation is supported
+ */
+ abstract boolean handles(Class<Annotation> annotation);
+
+ protected static class Builder {
+
+ private InterfaceModel interfaceModel;
+ private Type type;
+ private AnnotatedElement annotatedElement;
+ private Parameter parameter;
+ private String pathParamName;
+ private String headerParamName;
+ private String cookieParamName;
+ private String queryParamName;
+ private String matrixParamName;
+ private String formParamName;
+ private boolean beanParam;
+ private boolean entity;
+ private int paramPosition;
+
+ private Builder(InterfaceModel interfaceModel, Type type, AnnotatedElement annotatedElement, Parameter parameter) {
+ this.interfaceModel = interfaceModel;
+ this.type = type;
+ this.annotatedElement = annotatedElement;
+ this.parameter = parameter;
+ }
+
+ /**
+ * Path parameter name.
+ *
+ * @param pathParam {@link PathParam} annotation
+ * @return updated Builder instance
+ */
+ Builder pathParamName(PathParam pathParam) {
+ this.pathParamName = pathParam == null ? null : pathParam.value();
+ return this;
+ }
+
+ /**
+ * Header parameter name.
+ *
+ * @param headerParam {@link HeaderParam} annotation
+ * @return updated Builder instance
+ */
+ Builder headerParamName(HeaderParam headerParam) {
+ this.headerParamName = headerParam == null ? null : headerParam.value();
+ return this;
+ }
+
+ /**
+ * Bean parameter identifier.
+ *
+ * @param beanParam {@link BeanParam} annotation
+ * @return updated Builder instance
+ */
+ Builder beanParam(BeanParam beanParam) {
+ this.beanParam = beanParam != null;
+ return this;
+ }
+
+ /**
+ * Cookie parameter.
+ *
+ * @param cookieParam {@link CookieParam} annotation
+ * @return updated Builder instance
+ */
+ Builder cookieParam(CookieParam cookieParam) {
+ this.cookieParamName = cookieParam == null ? null : cookieParam.value();
+ return this;
+ }
+
+ /**
+ * Query parameter.
+ *
+ * @param queryParam {@link QueryParam} annotation
+ * @return updated Builder instance
+ */
+ Builder queryParam(QueryParam queryParam) {
+ this.queryParamName = queryParam == null ? null : queryParam.value();
+ return this;
+ }
+
+ /**
+ * Matrix parameter.
+ *
+ * @param matrixParam {@link MatrixParam} annotation
+ * @return updated Builder instance
+ */
+ Builder matrixParam(MatrixParam matrixParam) {
+ this.matrixParamName = matrixParam == null ? null : matrixParam.value();
+ return this;
+ }
+
+ /**
+ * Form parameter.
+ *
+ * @param formParam {@link FormParam} annotation
+ * @return updated Builder instance
+ */
+ Builder formParam(FormParam formParam) {
+ this.formParamName = formParam == null ? null : formParam.value();
+ return this;
+ }
+
+ /**
+ * Position of parameter in method parameters
+ *
+ * @param paramPosition Parameter position
+ * @return updated Builder instance
+ */
+ Builder paramPosition(int paramPosition) {
+ this.paramPosition = paramPosition;
+ return this;
+ }
+
+ /**
+ * Returns path param name;
+ *
+ * @return path param name
+ */
+ String pathParamName() {
+ return pathParamName;
+ }
+
+ /**
+ * Returns header param name;
+ *
+ * @return header param name
+ */
+ String headerParamName() {
+ return headerParamName;
+ }
+
+ String cookieParamName() {
+ return cookieParamName;
+ }
+
+ String queryParamName() {
+ return queryParamName;
+ }
+
+ String matrixParamName() {
+ return matrixParamName;
+ }
+
+ String formParamName() {
+ return matrixParamName;
+ }
+
+ /**
+ * Creates new ParamModel instance.
+ *
+ * @return new instance
+ */
+ ParamModel build() {
+ if (pathParamName != null) {
+ return new PathParamModel(this);
+ } else if (headerParamName != null) {
+ return new HeaderParamModel(this);
+ } else if (beanParam) {
+ return new BeanParamModel(this);
+ } else if (cookieParamName != null) {
+ return new CookieParamModel(this);
+ } else if (queryParamName != null) {
+ return new QueryParamModel(this);
+ } else if (matrixParamName != null) {
+ return new MatrixParamModel(this);
+ } else if (formParamName != null) {
+ return new FormParamModel(this);
+ }
+ entity = true;
+ return new ParamModel(this) {
+ @Override
+ public Object handleParameter(Object requestPart, Class annotationClass, Object instance) {
+ return requestPart;
+ }
+
+ @Override
+ public boolean handles(Class annotation) {
+ return false;
+ }
+ };
+ }
+
+ }
+
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/PathParamModel.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/PathParamModel.java
new file mode 100644
index 0000000..4c8768c
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/PathParamModel.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2019 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.microprofile.restclient;
+
+import java.lang.annotation.Annotation;
+
+import javax.ws.rs.PathParam;
+import javax.ws.rs.client.WebTarget;
+
+/**
+ * Contains information about method parameter or class field which is annotated by {@link PathParam}.
+ *
+ * @author David Kral
+ */
+class PathParamModel extends ParamModel<WebTarget> {
+
+ private final String pathParamName;
+
+ PathParamModel(Builder builder) {
+ super(builder);
+ pathParamName = builder.pathParamName();
+ }
+
+ public String getPathParamName() {
+ return pathParamName;
+ }
+
+ @Override
+ public WebTarget handleParameter(WebTarget requestPart, Class<?> annotationClass, Object instance) {
+ Object resolvedValue = interfaceModel.resolveParamValue(instance, parameter);
+ return requestPart.resolveTemplate(pathParamName, resolvedValue);
+ }
+
+ @Override
+ public boolean handles(Class<Annotation> annotation) {
+ return PathParam.class.equals(annotation);
+ }
+
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/ProxyInvocationHandler.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/ProxyInvocationHandler.java
new file mode 100644
index 0000000..2a2675e
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/ProxyInvocationHandler.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2019 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.microprofile.restclient;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+
+import javax.ws.rs.client.WebTarget;
+
+/**
+ * Invocation handler for interface proxy.
+ *
+ * @author David Kral
+ */
+class ProxyInvocationHandler implements InvocationHandler {
+
+ private final WebTarget target;
+ private final RestClientModel restClientModel;
+
+ ProxyInvocationHandler(WebTarget target,
+ RestClientModel restClientModel) {
+ this.target = target;
+ this.restClientModel = restClientModel;
+ }
+
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) {
+ if (method.getName().contains("toString") && (args == null || args.length == 0)) {
+ return restClientModel.toString();
+ }
+ return restClientModel.invokeMethod(target, method, args);
+ }
+
+}
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
new file mode 100644
index 0000000..ce96099
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/QueryParamModel.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2019 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.microprofile.restclient;
+
+import java.lang.annotation.Annotation;
+import java.util.Map;
+
+import javax.ws.rs.QueryParam;
+
+/**
+ * Model which contains information about query parameter
+ *
+ * @author David Kral
+ */
+class QueryParamModel extends ParamModel<Map<String, Object[]>> {
+
+ private final String queryParamName;
+
+ QueryParamModel(Builder builder) {
+ super(builder);
+ queryParamName = builder.queryParamName();
+ }
+
+ @Override
+ public Map<String, Object[]> handleParameter(Map<String, Object[]> requestPart,
+ Class<?> annotationClass,
+ Object instance) {
+ Object resolvedValue = interfaceModel.resolveParamValue(instance, parameter);
+ if (resolvedValue instanceof Object[]) {
+ requestPart.put(queryParamName, (Object[]) resolvedValue);
+ } else {
+ requestPart.put(queryParamName, new Object[] {resolvedValue});
+ }
+ return requestPart;
+ }
+
+ @Override
+ public boolean handles(Class<Annotation> annotation) {
+ return QueryParam.class.equals(annotation);
+ }
+
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/ReflectionUtil.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/ReflectionUtil.java
new file mode 100644
index 0000000..27344cb
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/ReflectionUtil.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019 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.microprofile.restclient;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Proxy;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+import org.glassfish.jersey.internal.util.ReflectionHelper;
+
+/**
+ * Created by David Kral.
+ */
+class ReflectionUtil {
+
+ static <T> T createInstance(Class<T> tClass) {
+ return AccessController.doPrivileged((PrivilegedAction<T>) () -> {
+ try {
+ return tClass.newInstance();
+ } catch (Throwable t) {
+ throw new RuntimeException("No default constructor in class " + tClass + " present. Class cannot be created!", t);
+ }
+ });
+ }
+
+ @SuppressWarnings("unchecked")
+ static <T> T createProxyInstance(Class<T> restClientClass) {
+ return AccessController.doPrivileged((PrivilegedAction<T>) () -> (T) Proxy.newProxyInstance(
+ Thread.currentThread().getContextClassLoader(),
+ new Class[] {restClientClass},
+ (proxy, m, args) -> {
+ Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
+ .getDeclaredConstructor(Class.class);
+ constructor.setAccessible(true);
+ return constructor.newInstance(restClientClass)
+ .in(restClientClass)
+ .unreflectSpecial(m, restClientClass)
+ .bindTo(proxy)
+ .invokeWithArguments(args);
+ }));
+ }
+
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RequestHeaderAutoDiscoverable.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RequestHeaderAutoDiscoverable.java
new file mode 100644
index 0000000..bcda814
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RequestHeaderAutoDiscoverable.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2019 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.microprofile.restclient;
+
+import javax.ws.rs.ConstrainedTo;
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.core.FeatureContext;
+
+import org.glassfish.jersey.internal.spi.AutoDiscoverable;
+
+/**
+ * Auto discoverable feature to bind into jersey runtime.
+ */
+@ConstrainedTo(RuntimeType.SERVER)
+public class RequestHeaderAutoDiscoverable implements AutoDiscoverable {
+ @Override
+ public void configure(FeatureContext context) {
+ if (!context.getConfiguration().isRegistered(HeadersRequestFilter.class)) {
+ context.register(HeadersRequestFilter.class);
+ }
+ }
+}
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
new file mode 100644
index 0000000..fadca0f
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientBuilderImpl.java
@@ -0,0 +1,357 @@
+/*
+ * Copyright (c) 2019 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.microprofile.restclient;
+
+import java.lang.reflect.Proxy;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.security.AccessController;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.ServiceLoader;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+import javax.annotation.Priority;
+import javax.ws.rs.Priorities;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.Feature;
+import javax.ws.rs.core.FeatureContext;
+import javax.ws.rs.ext.ParamConverterProvider;
+
+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.RestClientDefinitionException;
+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.ResponseExceptionMapper;
+import org.eclipse.microprofile.rest.client.spi.RestClientListener;
+import org.glassfish.jersey.client.Initializable;
+import org.glassfish.jersey.client.JerseyClient;
+import org.glassfish.jersey.internal.inject.InjectionManager;
+import org.glassfish.jersey.internal.inject.InjectionManagerSupplier;
+import org.glassfish.jersey.internal.util.ReflectionHelper;
+
+/**
+ * Rest client builder implementation. Creates proxy instance of requested interface.
+ *
+ * @author David Kral
+ */
+public class RestClientBuilderImpl implements RestClientBuilder {
+
+ private static final String CONFIG_DISABLE_DEFAULT_MAPPER = "microprofile.rest.client.disable.default.mapper";
+ private static final String CONFIG_PROVIDERS = "/mp-rest/providers";
+ private static final String CONFIG_PROVIDER_PRIORITY = "/priority";
+ private static final String PROVIDER_SEPARATOR = ",";
+
+ private final Set<ResponseExceptionMapper> responseExceptionMappers;
+ private final Set<ParamConverterProvider> paramConverterProviders;
+ private final List<AsyncInvocationInterceptorFactory> asyncInterceptorFactories;
+ private final Config config;
+ private final ConfigWrapper configWrapper;
+ private URI uri;
+ private ClientBuilder clientBuilder;
+ private Supplier<ExecutorService> executorService;
+
+ RestClientBuilderImpl() {
+ clientBuilder = ClientBuilder.newBuilder();
+ responseExceptionMappers = new HashSet<>();
+ paramConverterProviders = new HashSet<>();
+ asyncInterceptorFactories = new ArrayList<>();
+ config = ConfigProvider.getConfig();
+ configWrapper = new ConfigWrapper(clientBuilder.getConfiguration());
+ executorService = Executors::newCachedThreadPool;
+ }
+
+ @Override
+ public RestClientBuilder baseUrl(URL url) {
+ try {
+ this.uri = url.toURI();
+ return this;
+ } catch (URISyntaxException e) {
+ throw new RuntimeException(e.getMessage());
+ }
+ }
+
+ @Override
+ public RestClientBuilder connectTimeout(long timeout, TimeUnit unit) {
+ clientBuilder.connectTimeout(timeout, unit);
+ return this;
+ }
+
+ @Override
+ public RestClientBuilder readTimeout(long timeout, TimeUnit unit) {
+ clientBuilder.readTimeout(timeout, unit);
+ return this;
+ }
+
+ @Override
+ public RestClientBuilder executorService(ExecutorService executor) {
+ if (executor == null) {
+ throw new IllegalArgumentException("ExecutorService cannot be null.");
+ }
+ executorService = () -> executor;
+ return this;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public <T> T build(Class<T> interfaceClass) throws IllegalStateException, RestClientDefinitionException {
+
+ if (uri == null) {
+ throw new IllegalStateException("Base uri/url cannot be null!");
+ }
+
+ //Provider registration part
+ Object providersFromJerseyConfig = clientBuilder.getConfiguration()
+ .getProperty(interfaceClass.getName() + CONFIG_PROVIDERS);
+ if (providersFromJerseyConfig instanceof String && !((String) providersFromJerseyConfig).isEmpty()) {
+ String[] providerArray = ((String) providersFromJerseyConfig).split(PROVIDER_SEPARATOR);
+ processConfigProviders(interfaceClass, providerArray);
+ }
+ Optional<String> providersFromConfig = config.getOptionalValue(interfaceClass.getName() + CONFIG_PROVIDERS, String.class);
+ if (providersFromConfig.isPresent() && !providersFromConfig.get().isEmpty()) {
+ String[] providerArray = providersFromConfig.get().split(PROVIDER_SEPARATOR);
+ processConfigProviders(interfaceClass, providerArray);
+ }
+ RegisterProvider[] registerProviders = interfaceClass.getAnnotationsByType(RegisterProvider.class);
+ for (RegisterProvider registerProvider : registerProviders) {
+ register(registerProvider.value(), registerProvider.priority() < 0 ? Priorities.USER : registerProvider.priority());
+ }
+ InjectionManagerExposer injectionManagerExposer = new InjectionManagerExposer();
+ register(injectionManagerExposer);
+
+ for (RestClientListener restClientListener : ServiceLoader.load(RestClientListener.class)) {
+ restClientListener.onNewClient(interfaceClass, this);
+ }
+
+ //We need to check first if default exception mapper was not disabled by property on builder.
+ Object disableDefaultMapperJersey = clientBuilder.getConfiguration().getProperty(CONFIG_DISABLE_DEFAULT_MAPPER);
+ if (disableDefaultMapperJersey != null && disableDefaultMapperJersey.equals(Boolean.FALSE)) {
+ register(new DefaultResponseExceptionMapper());
+ } else if (disableDefaultMapperJersey == null) {
+ //If property was not set on Jersey ClientBuilder, we need to check config.
+ Optional<Boolean> disableDefaultMapperConfig = config.getOptionalValue(CONFIG_DISABLE_DEFAULT_MAPPER, boolean.class);
+ if (!disableDefaultMapperConfig.isPresent() || !disableDefaultMapperConfig.get()) {
+ register(new DefaultResponseExceptionMapper());
+ }
+ }
+
+ //AsyncInterceptors initialization
+ List<AsyncInvocationInterceptor> asyncInterceptors = asyncInterceptorFactories.stream()
+ .map(AsyncInvocationInterceptorFactory::newInterceptor)
+ .collect(Collectors.toList());
+ asyncInterceptors.forEach(AsyncInvocationInterceptor::prepareContext);
+
+ clientBuilder.executorService(new ExecutorServiceWrapper(executorService.get(), asyncInterceptors));
+
+ Client client = clientBuilder.build();
+ if (client instanceof Initializable) {
+ ((Initializable) client).preInitialize();
+ }
+ WebTarget webTarget = client.target(this.uri);
+
+ RestClientModel restClientModel = RestClientModel.from(interfaceClass,
+ responseExceptionMappers,
+ paramConverterProviders,
+ asyncInterceptors,
+ injectionManagerExposer.injectionManager);
+
+
+ return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(),
+ new Class[] {interfaceClass},
+ new ProxyInvocationHandler(webTarget, restClientModel)
+ );
+ }
+
+ private void processConfigProviders(Class<?> restClientInterface, String[] providerArray) {
+ for (String provider : providerArray) {
+ Class<?> providerClass = AccessController.doPrivileged(ReflectionHelper.classForNamePA(provider));
+ if (providerClass == null) {
+ throw new IllegalStateException("No provider class with following name found: " + provider);
+ }
+ int priority = getProviderPriority(restClientInterface, providerClass);
+ register(providerClass, priority);
+ }
+ }
+
+ private int getProviderPriority(Class<?> restClientInterface, Class<?> providerClass) {
+ String property = restClientInterface.getName() + CONFIG_PROVIDERS + "/"
+ + providerClass.getName() + CONFIG_PROVIDER_PRIORITY;
+ Object providerPriorityJersey = clientBuilder.getConfiguration().getProperty(property);
+ if (providerPriorityJersey == null) {
+ //If property was not set on Jersey ClientBuilder, we need to check MP config.
+ Optional<Integer> providerPriorityMP = config.getOptionalValue(property, int.class);
+ if (providerPriorityMP.isPresent()) {
+ return providerPriorityMP.get();
+ }
+ } else if (providerPriorityJersey instanceof Integer) {
+ return (int) providerPriorityJersey;
+ }
+ Priority priority = providerClass.getAnnotation(Priority.class);
+ return priority == null ? -1 : priority.value();
+ }
+
+ @Override
+ public Configuration getConfiguration() {
+ return configWrapper;
+ }
+
+ @Override
+ public RestClientBuilder property(String name, Object value) {
+ clientBuilder.property(name, value);
+ return this;
+ }
+
+ @Override
+ public RestClientBuilder register(Class<?> aClass) {
+ if (isSupportedCustomProvider(aClass)) {
+ register(ReflectionUtil.createInstance(aClass));
+ } else {
+ clientBuilder.register(aClass);
+ }
+ return this;
+ }
+
+ @Override
+ public RestClientBuilder register(Class<?> aClass, int i) {
+ if (isSupportedCustomProvider(aClass)) {
+ register(ReflectionUtil.createInstance(aClass), i);
+ } else {
+ clientBuilder.register(aClass, i);
+ }
+ return this;
+ }
+
+ @Override
+ public RestClientBuilder register(Class<?> aClass, Class<?>... classes) {
+ if (isSupportedCustomProvider(aClass)) {
+ register(ReflectionUtil.createInstance(aClass), classes);
+ } else {
+ clientBuilder.register(aClass, classes);
+ }
+ return this;
+ }
+
+ @Override
+ public RestClientBuilder register(Class<?> aClass, Map<Class<?>, Integer> map) {
+ if (isSupportedCustomProvider(aClass)) {
+ register(ReflectionUtil.createInstance(aClass), map);
+ } else {
+ clientBuilder.register(aClass, map);
+ }
+ return this;
+ }
+
+ @Override
+ public RestClientBuilder register(Object o) {
+ if (o instanceof ResponseExceptionMapper) {
+ ResponseExceptionMapper mapper = (ResponseExceptionMapper) o;
+ registerCustomProvider(o, -1);
+ clientBuilder.register(mapper, mapper.getPriority());
+ } else {
+ clientBuilder.register(o);
+ registerCustomProvider(o, -1);
+ }
+ return this;
+ }
+
+ @Override
+ public RestClientBuilder register(Object o, int i) {
+ clientBuilder.register(o, i);
+ registerCustomProvider(o, i);
+ return this;
+ }
+
+ @Override
+ public RestClientBuilder register(Object o, Class<?>... classes) {
+ for (Class<?> clazz : classes) {
+ if (isSupportedCustomProvider(clazz)) {
+ register(o);
+ }
+ }
+ clientBuilder.register(o, classes);
+ return this;
+ }
+
+ @Override
+ public RestClientBuilder register(Object o, Map<Class<?>, Integer> map) {
+ if (isSupportedCustomProvider(o.getClass())) {
+ if (o instanceof ResponseExceptionMapper) {
+ registerCustomProvider(o, map.get(ResponseExceptionMapper.class));
+ } else if (o instanceof ParamConverterProvider) {
+ registerCustomProvider(o, map.get(ParamConverterProvider.class));
+ }
+ }
+ clientBuilder.register(o, map);
+ return this;
+ }
+
+ private boolean isSupportedCustomProvider(Class<?> providerClass) {
+ return ResponseExceptionMapper.class.isAssignableFrom(providerClass)
+ || ParamConverterProvider.class.isAssignableFrom(providerClass)
+ || AsyncInvocationInterceptorFactory.class.isAssignableFrom(providerClass);
+ }
+
+ private void registerCustomProvider(Object instance, int priority) {
+ if (!isSupportedCustomProvider(instance.getClass())) {
+ return;
+ }
+ if (instance instanceof ResponseExceptionMapper) {
+ responseExceptionMappers.add((ResponseExceptionMapper) instance);
+ //needs to be registered separately due to it is not possible to register custom provider in jersey
+ Map<Class<?>, Integer> contracts = new HashMap<>();
+ contracts.put(ResponseExceptionMapper.class, priority);
+ configWrapper.addCustomProvider(instance.getClass(), contracts);
+ }
+ if (instance instanceof ParamConverterProvider) {
+ paramConverterProviders.add((ParamConverterProvider) instance);
+ }
+ if (instance instanceof AsyncInvocationInterceptorFactory) {
+ asyncInterceptorFactories.add((AsyncInvocationInterceptorFactory) instance);
+ }
+ }
+
+ private static class InjectionManagerExposer implements Feature {
+ InjectionManager injectionManager;
+
+ @Override
+ public boolean configure(FeatureContext context) {
+ if (context instanceof InjectionManagerSupplier) {
+ this.injectionManager = ((InjectionManagerSupplier) context).getInjectionManager();
+ return true;
+ } else {
+ throw new IllegalArgumentException("The client needs Jersey runtime to work properly");
+ }
+ }
+ }
+
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientExtension.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientExtension.java
new file mode 100644
index 0000000..05743a2
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientExtension.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 2019 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.microprofile.restclient;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.enterprise.event.Observes;
+import javax.enterprise.inject.spi.AfterBeanDiscovery;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.enterprise.inject.spi.DeploymentException;
+import javax.enterprise.inject.spi.Extension;
+import javax.enterprise.inject.spi.InjectionPoint;
+import javax.enterprise.inject.spi.ProcessAnnotatedType;
+import javax.enterprise.inject.spi.ProcessInjectionPoint;
+import javax.enterprise.inject.spi.WithAnnotations;
+import javax.enterprise.util.AnnotationLiteral;
+import javax.inject.Qualifier;
+
+import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
+import org.eclipse.microprofile.rest.client.inject.RestClient;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Filters out all interfaces annotated with {@link RegisterRestClient}
+ * and creates new Producer from each of these selected interfaces.
+ *
+ * Also adds support for injection of rest client instances to fields
+ * without {@link RestClient} annotation.
+ *
+ * @author David Kral
+ */
+public class RestClientExtension implements Extension {
+
+ private Set<Class<?>> interfaces = new HashSet<>();
+
+ /**
+ * Filters out all interfaces annotated with {@link RegisterRestClient} annotation and
+ * adds them to the collection for further processing.
+ *
+ * @param processAnnotatedType filtered annotated types
+ */
+ public void collectClientRegistrations(@Observes
+ @WithAnnotations({RegisterRestClient.class})
+ ProcessAnnotatedType<?> processAnnotatedType) {
+ Class<?> typeDef = processAnnotatedType.getAnnotatedType().getJavaClass();
+ if (typeDef.isInterface()) {
+ interfaces.add(typeDef);
+ } else {
+ throw new DeploymentException("RegisterRestClient annotation has to be on interface! " + typeDef + " is not "
+ + "interface.");
+ }
+ }
+
+ /**
+ * Iterates over all {@link ProcessInjectionPoint} to find only those annotated
+ * with {@link RestClient} and configures their proper injection.
+ *
+ * @param pip processed injection point
+ */
+ public void collectClientProducer(@Observes ProcessInjectionPoint<?, ?> pip) {
+ RestClient restClient = pip.getInjectionPoint().getAnnotated().getAnnotation(RestClient.class);
+ if (restClient != null) {
+ InjectionPoint ip = pip.getInjectionPoint();
+ Class<?> type = (Class<?>) ip.getType();
+
+ RestClientLiteral q = new RestClientLiteral(type);
+
+ pip.configureInjectionPoint().addQualifier(q);
+ }
+ }
+
+ /**
+ * Creates new producers based on collected interfaces.
+ *
+ * @param abd after bean discovery instance
+ * @param bm bean manager instance
+ */
+ public void restClientRegistration(@Observes AfterBeanDiscovery abd, BeanManager bm) {
+ interfaces.forEach(type -> abd.addBean(new RestClientProducer(new RestClientLiteral(type), type, bm)));
+ interfaces.forEach(type -> abd.addBean(new RestClientProducer(null, type, bm)));
+ }
+
+ @Qualifier
+ @Retention(RUNTIME)
+ @Target({METHOD, FIELD})
+ @interface MpRestClientQualifier {
+
+ Class<?> interfaceType();
+
+ }
+
+ private static class RestClientLiteral extends AnnotationLiteral<MpRestClientQualifier> implements MpRestClientQualifier {
+
+ private final Class<?> interfaceType;
+
+ RestClientLiteral(Class<?> interfaceType) {
+ this.interfaceType = interfaceType;
+ }
+
+ @Override
+ public Class<?> interfaceType() {
+ return interfaceType;
+ }
+
+ @Override
+ public String toString() {
+ return "RestClientLiteral{"
+ + "interfaceType=" + interfaceType
+ + '}';
+ }
+
+ }
+
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientModel.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientModel.java
new file mode 100644
index 0000000..49d353c
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientModel.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) 2019 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.microprofile.restclient;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.ext.ParamConverterProvider;
+
+import org.eclipse.microprofile.rest.client.ext.AsyncInvocationInterceptor;
+import org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper;
+import org.glassfish.jersey.internal.inject.InjectionManager;
+
+/**
+ * Model of the rest client interface.
+ *
+ * @author David Kral
+ */
+class RestClientModel {
+
+ private final InterfaceModel interfaceModel;
+ private final Map<Method, MethodModel> methodModels;
+
+ /**
+ * Creates new instance of the {@link RestClientModel} base on interface class.
+ *
+ * @param restClientClass rest client interface
+ * @param responseExceptionMappers registered exception mappers
+ * @param paramConverterProviders registered param converters
+ * @param asyncInterceptors registered async interceptor factories
+ * @param injectionManager
+ * @return new instance
+ */
+ static RestClientModel from(Class<?> restClientClass,
+ Set<ResponseExceptionMapper> responseExceptionMappers,
+ Set<ParamConverterProvider> paramConverterProviders,
+ List<AsyncInvocationInterceptor> asyncInterceptors,
+ InjectionManager injectionManager) {
+ InterfaceModel interfaceModel = InterfaceModel.from(restClientClass,
+ responseExceptionMappers,
+ paramConverterProviders,
+ asyncInterceptors,
+ injectionManager);
+ return new Builder()
+ .interfaceModel(interfaceModel)
+ .methodModels(parseMethodModels(interfaceModel))
+ .build();
+ }
+
+ private RestClientModel(Builder builder) {
+ this.interfaceModel = builder.classModel;
+ this.methodModels = builder.methodModels;
+ }
+
+ /**
+ * Invokes desired rest client method.
+ *
+ * @param baseWebTarget path to endpoint
+ * @param method desired method
+ * @param args actual method parameters
+ * @return method return value
+ */
+ <T> Object invokeMethod(WebTarget baseWebTarget, Method method, Object[] args) {
+ WebTarget classLevelTarget = baseWebTarget.path(interfaceModel.getPath());
+ MethodModel methodModel = methodModels.get(method);
+ if (methodModel != null) {
+ return new InterceptorInvocationContext(classLevelTarget, methodModel, method, args).proceed();
+ }
+ try {
+ if (method.isDefault()) {
+ T instance = (T) ReflectionUtil.createProxyInstance(interfaceModel.getRestClientClass());
+ return method.invoke(instance, args);
+ } else {
+ throw new UnsupportedOperationException("This method is not supported!");
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static Map<Method, MethodModel> parseMethodModels(InterfaceModel classModel) {
+ Map<Method, MethodModel> methodMap = new HashMap<>();
+ for (Method method : classModel.getRestClientClass().getMethods()) {
+ if (method.isDefault() || Modifier.isStatic(method.getModifiers())) {
+ continue;
+ }
+ //Skip method processing if method does not have HTTP annotation
+ //and is not sub resource (does not have Path annotation)
+ methodMap.put(method, MethodModel.from(classModel, method));
+ }
+ return methodMap;
+ }
+
+ private static class Builder {
+
+ private InterfaceModel classModel;
+ private Map<Method, MethodModel> methodModels;
+
+ private Builder() {
+ }
+
+ /**
+ * Rest client class converted to {@link InterfaceModel}
+ *
+ * @param classModel {@link InterfaceModel} instance
+ * @return Updated Builder instance
+ */
+ Builder interfaceModel(InterfaceModel classModel) {
+ this.classModel = classModel;
+ return this;
+ }
+
+ /**
+ * Rest client class methods converted to {@link Map} of {@link MethodModel}
+ *
+ * @param methodModels Method models
+ * @return Updated Builder instance
+ */
+ Builder methodModels(Map<Method, MethodModel> methodModels) {
+ this.methodModels = methodModels;
+ return this;
+ }
+
+ /**
+ * Creates new RestClientModel instance.
+ *
+ * @return new instance
+ */
+ public RestClientModel build() {
+ return new RestClientModel(this);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return interfaceModel.getRestClientClass().getName();
+ }
+}
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
new file mode 100644
index 0000000..7c0c4b3
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientProducer.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (c) 2019 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.microprofile.restclient;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.security.AccessController;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+import javax.enterprise.context.Dependent;
+import javax.enterprise.context.spi.CreationalContext;
+import javax.enterprise.inject.Default;
+import javax.enterprise.inject.spi.Bean;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.enterprise.inject.spi.DeploymentException;
+import javax.enterprise.inject.spi.InjectionPoint;
+import javax.enterprise.inject.spi.PassivationCapable;
+
+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.inject.RegisterRestClient;
+import org.eclipse.microprofile.rest.client.inject.RestClient;
+import org.glassfish.jersey.internal.util.ReflectionHelper;
+
+/**
+ * Handles proper rest client injection.
+ *
+ * Contains information about the rest client interface and extracts additional parameters from
+ * config.
+ *
+ * @author David Kral
+ */
+class RestClientProducer implements Bean<Object>, PassivationCapable {
+
+ private static final String CONFIG_URL = "/mp-rest/url";
+ private static final String CONFIG_URI = "/mp-rest/uri";
+ private static final String CONFIG_SCOPE = "/mp-rest/scope";
+ private static final String CONFIG_CONNECTION_TIMEOUT = "/mp-rest/connectTimeout";
+ private static final String CONFIG_READ_TIMEOUT = "/mp-rest/readTimeout";
+
+ private final RestClientExtension.MpRestClientQualifier qualifier;
+ private final BeanManager beanManager;
+ private final Class<?> interfaceType;
+ private final Class<? extends Annotation> scope;
+ private final Config config;
+ private final String baseUrl;
+
+ /**
+ * Creates new instance of RestClientProducer.
+ *
+ * @param qualifier qualifier which defines rest client interface
+ * @param interfaceType rest client interface
+ * @param beanManager bean manager
+ */
+ RestClientProducer(RestClientExtension.MpRestClientQualifier qualifier,
+ Class<?> interfaceType,
+ BeanManager beanManager) {
+ this.qualifier = qualifier;
+ this.interfaceType = interfaceType;
+ this.beanManager = beanManager;
+ this.config = ConfigProvider.getConfig();
+ this.baseUrl = getBaseUrl(interfaceType);
+ this.scope = resolveProperClientScope();
+ }
+
+ private String getBaseUrl(Class<?> interfaceType) {
+ Optional<String> uri = config.getOptionalValue(interfaceType.getName() + CONFIG_URI, String.class);
+ return uri.orElse(config.getOptionalValue(interfaceType.getName() + CONFIG_URL, String.class).orElseGet(
+ () -> {
+ RegisterRestClient registerRestClient = interfaceType.getAnnotation(RegisterRestClient.class);
+ if (registerRestClient != null) {
+ return registerRestClient.baseUri();
+ }
+ throw new DeploymentException("This interface has to be annotated with @RegisterRestClient annotation.");
+ }
+ ));
+ }
+
+ @Override
+ public Class<?> getBeanClass() {
+ return interfaceType;
+ }
+
+ @Override
+ public Set<InjectionPoint> getInjectionPoints() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public boolean isNullable() {
+ return false;
+ }
+
+ @Override
+ public Object create(CreationalContext<Object> creationalContext) {
+ try {
+ RestClientBuilder restClientBuilder = RestClientBuilder.newBuilder().baseUrl(new URL(baseUrl));
+ config.getOptionalValue(interfaceType.getName() + CONFIG_CONNECTION_TIMEOUT, Long.class)
+ .ifPresent(aLong -> restClientBuilder.connectTimeout(aLong, TimeUnit.MILLISECONDS));
+ config.getOptionalValue(interfaceType.getName() + CONFIG_READ_TIMEOUT, Long.class)
+ .ifPresent(aLong -> restClientBuilder.readTimeout(aLong, TimeUnit.MILLISECONDS));
+ return restClientBuilder.build(interfaceType);
+ } catch (MalformedURLException e) {
+ throw new IllegalStateException("URL is not in valid format: " + baseUrl);
+ }
+ }
+
+ @Override
+ public void destroy(Object instance, CreationalContext<Object> creationalContext) {
+ }
+
+ @Override
+ public Set<Type> getTypes() {
+ return Collections.singleton(interfaceType);
+ }
+
+ @Override
+ public Set<Annotation> getQualifiers() {
+ if (qualifier == null) {
+ return Collections.singleton(Default.Literal.INSTANCE);
+ }
+ Set<Annotation> annotations = new HashSet<>();
+ annotations.add(qualifier);
+ annotations.add(RestClient.LITERAL);
+ return annotations;
+ }
+
+ @Override
+ public Class<? extends Annotation> getScope() {
+ return scope;
+ }
+
+ @Override
+ public String getName() {
+ if (qualifier == null) {
+ return interfaceType.getName() + "RestClient";
+ }
+ return interfaceType.getName();
+ }
+
+ @Override
+ public Set<Class<? extends Annotation>> getStereotypes() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public boolean isAlternative() {
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "RestClientProducer [ interfaceType: " + interfaceType.getSimpleName()
+ + " ] with Qualifiers [" + getQualifiers() + "]";
+ }
+
+ @Override
+ public String getId() {
+ return interfaceType.getName();
+ }
+
+ private Class<? extends Annotation> resolveProperClientScope() {
+ String configScope = config.getOptionalValue(interfaceType.getName() + CONFIG_SCOPE, String.class).orElse(null);
+ if (configScope != null) {
+ Class<Annotation> scope = AccessController.doPrivileged(ReflectionHelper.classForNamePA(configScope));
+ if (scope == null) {
+ throw new IllegalStateException("Invalid scope from config: " + configScope);
+ }
+ return scope;
+ }
+ List<Annotation> possibleScopes = Arrays.stream(interfaceType.getDeclaredAnnotations())
+ .filter(annotation -> beanManager.isScope(annotation.annotationType()))
+ .collect(Collectors.toList());
+
+ if (possibleScopes.size() == 1) {
+ return possibleScopes.get(0).annotationType();
+ } else if (possibleScopes.isEmpty()) {
+ return Dependent.class;
+ } else {
+ throw new IllegalArgumentException("Client should have only one scope defined: "
+ + interfaceType + " has " + possibleScopes);
+ }
+ }
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/resources/META-INF/beans.xml b/ext/microprofile/mp-rest-client/src/main/resources/META-INF/beans.xml
new file mode 100644
index 0000000..f472ff4
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/resources/META-INF/beans.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2019 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
+
+-->
+
+<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
+ http://xmlns.jcp.org/xml/ns/javaee/beans_2_0.xsd"
+ version="2.0"
+ bean-discovery-mode="none">
+</beans>
diff --git a/ext/microprofile/mp-rest-client/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension b/ext/microprofile/mp-rest-client/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
new file mode 100644
index 0000000..bca39a9
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
@@ -0,0 +1,17 @@
+#
+# Copyright (c) 2019 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
+#
+
+org.glassfish.jersey.microprofile.restclient.RestClientExtension
\ No newline at end of file
diff --git a/ext/microprofile/mp-rest-client/src/main/resources/META-INF/services/org.eclipse.microprofile.rest.client.spi.RestClientBuilderResolver b/ext/microprofile/mp-rest-client/src/main/resources/META-INF/services/org.eclipse.microprofile.rest.client.spi.RestClientBuilderResolver
new file mode 100644
index 0000000..e23142f
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/resources/META-INF/services/org.eclipse.microprofile.rest.client.spi.RestClientBuilderResolver
@@ -0,0 +1,17 @@
+#
+# Copyright (c) 2019 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
+#
+
+org.glassfish.jersey.microprofile.restclient.JerseyRestClientBuilderResolver
\ No newline at end of file
diff --git a/ext/microprofile/mp-rest-client/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.AutoDiscoverable b/ext/microprofile/mp-rest-client/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.AutoDiscoverable
new file mode 100644
index 0000000..d9a1262
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.AutoDiscoverable
@@ -0,0 +1,17 @@
+#
+# Copyright (c) 2019 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
+#
+
+org.glassfish.jersey.microprofile.restclient.RequestHeaderAutoDiscoverable
\ No newline at end of file
diff --git a/ext/microprofile/mp-rest-client/src/test/java/org/glassfish/jersey/message/internal/StringMessageProvider.java b/ext/microprofile/mp-rest-client/src/test/java/org/glassfish/jersey/message/internal/StringMessageProvider.java
new file mode 100644
index 0000000..203ecbc
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/test/java/org/glassfish/jersey/message/internal/StringMessageProvider.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2019 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.message.internal;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+
+import javax.inject.Singleton;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+
+import org.glassfish.jersey.message.internal.AbstractMessageReaderWriterProvider;
+
+/*
+ * This class was copied from Jersey Common 2.26 due to TCK workaround reasons.
+ */
+
+/**
+ *
+ * @author Paul Sandoz
+ */
+@Produces
+@Consumes
+@Singleton
+final class StringMessageProvider extends AbstractMessageReaderWriterProvider<String> {
+
+ @Override
+ public boolean isReadable(Class<?> type, Type genericType, Annotation annotations[], MediaType mediaType) {
+ return type == String.class;
+ }
+
+ @Override
+ public String readFrom(
+ Class<String> type,
+ Type genericType,
+ Annotation annotations[],
+ MediaType mediaType,
+ MultivaluedMap<String, String> httpHeaders,
+ InputStream entityStream) throws IOException {
+ return readFromAsString(entityStream, mediaType);
+ }
+
+ @Override
+ public boolean isWriteable(Class<?> type, Type genericType, Annotation annotations[], MediaType mediaType) {
+ return type == String.class;
+ }
+
+ @Override
+ public long getSize(String s, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
+ return s.length();
+ }
+
+ @Override
+ public void writeTo(
+ String t,
+ Class<?> type,
+ Type genericType,
+ Annotation annotations[],
+ MediaType mediaType,
+ MultivaluedMap<String, Object> httpHeaders,
+ OutputStream entityStream) throws IOException {
+ writeToAsString(t, entityStream, mediaType);
+ }
+}
diff --git a/ext/microprofile/mp-rest-client/src/test/java/org/glassfish/jersey/microprofile/restclient/ApplicationResource.java b/ext/microprofile/mp-rest-client/src/test/java/org/glassfish/jersey/microprofile/restclient/ApplicationResource.java
new file mode 100644
index 0000000..caf3bb6
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/test/java/org/glassfish/jersey/microprofile/restclient/ApplicationResource.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2019 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.microprofile.restclient;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+
+/**
+ * Created by David Kral.
+ */
+
+@Path("resource")
+public interface ApplicationResource {
+
+ @GET
+ String getValue();
+
+ @POST
+ String postAppendValue(String value);
+
+ default String sayHi() {
+ return "Hi";
+ }
+
+
+}
diff --git a/ext/microprofile/mp-rest-client/src/test/java/org/glassfish/jersey/microprofile/restclient/ApplicationResourceImpl.java b/ext/microprofile/mp-rest-client/src/test/java/org/glassfish/jersey/microprofile/restclient/ApplicationResourceImpl.java
new file mode 100644
index 0000000..4194b40
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/test/java/org/glassfish/jersey/microprofile/restclient/ApplicationResourceImpl.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2019 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.microprofile.restclient;
+
+/**
+ * Created by David Kral.
+ */
+public class ApplicationResourceImpl implements ApplicationResource {
+ @Override
+ public String getValue() {
+ return "This is default value!";
+ }
+
+ @Override
+ public String postAppendValue(String value) {
+ return null;
+ }
+}
diff --git a/ext/microprofile/mp-rest-client/src/test/java/org/glassfish/jersey/microprofile/restclient/CorrectInterface.java b/ext/microprofile/mp-rest-client/src/test/java/org/glassfish/jersey/microprofile/restclient/CorrectInterface.java
new file mode 100644
index 0000000..efe6c15
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/test/java/org/glassfish/jersey/microprofile/restclient/CorrectInterface.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2019 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.microprofile.restclient;
+
+import javax.ws.rs.BeanParam;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+
+import org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam;
+
+/**
+ * Correct test interface for validation
+ *
+ * @author David Kral
+ */
+
+@Path("test/{first}")
+public interface CorrectInterface {
+
+ @GET
+ @Path("{second}")
+ @ClientHeaderParam(name = "test", value = "someValue")
+ void firstMethod(@PathParam("first") String first, @PathParam("second") String second);
+
+ @GET
+ @ClientHeaderParam(name = "test", value = "{value}")
+ void secondMethod(@PathParam("first") String first, String second);
+
+ @POST
+ @ClientHeaderParam(name = "test", value = "org.glassfish.jersey.restclient.CustomHeaderGenerator.customHeader")
+ void thirdMethod(@PathParam("first") String first);
+
+ @GET
+ @Path("{second}")
+ void fourthMethod(@PathParam("first") String first, @BeanParam BeanWithPathParam second);
+
+ default String value() {
+ return "testValue";
+ }
+
+ class CustomHeaderGenerator {
+
+ public static String customHeader() {
+ return "static";
+ }
+
+ }
+
+ class BeanWithPathParam {
+
+ @PathParam("second")
+ public String pathParam;
+
+ }
+
+}
diff --git a/ext/microprofile/mp-rest-client/src/test/java/org/glassfish/jersey/microprofile/restclient/InterfaceValidationTest.java b/ext/microprofile/mp-rest-client/src/test/java/org/glassfish/jersey/microprofile/restclient/InterfaceValidationTest.java
new file mode 100644
index 0000000..a810399
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/test/java/org/glassfish/jersey/microprofile/restclient/InterfaceValidationTest.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2019 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.microprofile.restclient;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+
+import org.junit.Test;
+
+/**
+ * @author David Kral
+ */
+public class InterfaceValidationTest {
+
+ @Test
+ public void testValidInterface() {
+ RestClientModel.from(CorrectInterface.class, new HashSet<>(), new HashSet<>(), new ArrayList<>(),
+ null);
+ }
+
+}
diff --git a/ext/microprofile/mp-rest-client/src/test/java/org/glassfish/jersey/microprofile/restclient/RestClientModelTest.java b/ext/microprofile/mp-rest-client/src/test/java/org/glassfish/jersey/microprofile/restclient/RestClientModelTest.java
new file mode 100644
index 0000000..046f528
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/test/java/org/glassfish/jersey/microprofile/restclient/RestClientModelTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2019 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.microprofile.restclient;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import org.eclipse.microprofile.rest.client.RestClientBuilder;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Created by David Kral.
+ */
+public class RestClientModelTest extends JerseyTest {
+ @Override
+ protected ResourceConfig configure() {
+ enable(TestProperties.LOG_TRAFFIC);
+ return new ResourceConfig(ApplicationResourceImpl.class);
+ }
+
+ @Test
+ public void testGetIt() throws URISyntaxException {
+ ApplicationResource app = RestClientBuilder.newBuilder()
+ .baseUri(new URI("http://localhost:9998"))
+ .build(ApplicationResource.class);
+ assertEquals("This is default value!", app.getValue());
+ assertEquals("Hi", app.sayHi());
+ }
+}
diff --git a/ext/microprofile/mp-rest-client/src/test/resources/arquillian.xml b/ext/microprofile/mp-rest-client/src/test/resources/arquillian.xml
new file mode 100644
index 0000000..e748760
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/test/resources/arquillian.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2019 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
+
+-->
+
+<arquillian xmlns="http://jboss.org/schema/arquillian"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="
+ http://jboss.org/schema/arquillian
+ http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
+
+ <engine>
+ <property name="deploymentExportPath">target/deployments</property>
+ </engine>
+</arquillian>
diff --git a/ext/microprofile/mp-rest-client/src/test/resources/server.policy b/ext/microprofile/mp-rest-client/src/test/resources/server.policy
new file mode 100644
index 0000000..edff3db
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/test/resources/server.policy
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2019 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
+ */
+
+grant {
+ permission java.util.PropertyPermission "idea.launcher.bin.path", "read";
+ permission java.lang.RuntimePermission "loadLibrary.C:\\Program Files\\JetBrains\\IntelliJ IDEA 2019.1\\bin\\breakgen64.dll";
+ permission java.lang.RuntimePermission "accessDeclaredMembers";
+ permission java.lang.RuntimePermission "getClassLoader";
+ permission java.lang.RuntimePermission "createClassLoader";
+ permission java.lang.RuntimePermission "setContextClassLoader";
+ permission java.lang.RuntimePermission "getProtectionDomain";
+ permission java.lang.RuntimePermission "getenv.*";
+ permission java.lang.RuntimePermission "setIO";
+ permission java.io.FilePermission "C:\\Program Files\\JetBrains\\IntelliJ IDEA 2019.1\\bin\\breakgen64.dll", "read";
+ permission java.util.PropertyPermission "idea.launcher.port", "read";
+ permission java.util.PropertyPermission "idea.launcher.bin.path", "read";
+ permission java.util.PropertyPermission "jcommander.debug", "read";
+ permission java.util.PropertyPermission "user.dir", "read";
+ permission java.util.PropertyPermission "debug", "read";
+ permission java.util.PropertyPermission "arquillian.debug", "read";
+ permission java.util.PropertyPermission "trace", "read";
+ permission java.util.PropertyPermission "testng.test.classpath", "read";
+ permission java.util.PropertyPermission "localscoping", "read";
+ permission java.util.PropertyPermission "outfile", "read";
+ permission java.util.PropertyPermission "org.eclipse.microprofile.*", "read";
+ permission java.util.PropertyPermission "arquillian.xml", "read";
+ permission java.util.PropertyPermission "*", "read,write";
+ permission java.net.SocketPermission "127.0.0.1:*", "connect,resolve";
+ permission java.net.SocketPermission "*", "connect,resolve";
+ permission java.io.FilePermission "<<ALL FILES>>", "read,write,delete";
+ permission java.lang.reflect.ReflectPermission "suppressAccessChecks";
+ permission java.net.NetPermission "getProxySelector";
+ permission java.net.NetPermission "specifyStreamHandler";
+ permission java.lang.management.ManagementPermission "monitor";
+};
+
+grant codebase "file:${java.home}/-" {
+ permission java.security.AllPermission;
+};
+
+grant codebase "file:${settings.localRepository}/-" {
+ permission java.security.AllPermission;
+};
+
+grant codebase "file:${project.build.directory}/test-classes/-" {
+ permission java.util.PropertyPermission "idea.launcher.bin.path", "read";
+ permission java.lang.RuntimePermission "loadLibrary.C:\\Program Files\\JetBrains\\IntelliJ IDEA 2019.1\\bin\\breakgen64.dll";
+ permission java.io.FilePermission "C:\\Program Files\\JetBrains\\IntelliJ IDEA 2019.1\\bin\\breakgen64.dll", "read";
+ permission java.util.PropertyPermission "idea.launcher.port", "read";
+ permission java.util.PropertyPermission "idea.launcher.bin.path", "read";
+
+ permission java.io.FilePermission "<<ALL FILES>>", "read,write,delete";
+ permission java.net.SocketPermission "*", "connect,resolve";
+};
+
+grant codebase "file:${project.build.directory}/classes/-" {
+ permission java.util.PropertyPermission "idea.launcher.bin.path", "read";
+ permission java.lang.RuntimePermission "loadLibrary.C:\\Program Files\\JetBrains\\IntelliJ IDEA 2019.1\\bin\\breakgen64.dll";
+ permission java.io.FilePermission "C:\\Program Files\\JetBrains\\IntelliJ IDEA 2019.1\\bin\\breakgen64.dll", "read";
+ permission java.util.PropertyPermission "idea.launcher.port", "read";
+ permission java.util.PropertyPermission "idea.launcher.bin.path", "read";
+
+ permission java.io.FilePermission "<<ALL FILES>>", "read,write,delete";
+ permission java.net.SocketPermission "*", "connect,resolve";
+};
diff --git a/ext/microprofile/mp-rest-client/tck-suite.xml b/ext/microprofile/mp-rest-client/tck-suite.xml
new file mode 100644
index 0000000..3106d69
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/tck-suite.xml
@@ -0,0 +1,42 @@
+<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
+<!--
+
+ Copyright (c) 2019 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
+
+-->
+<suite name="microprofile-rest-client-TCK" verbose="2" configfailurepolicy="continue" >
+
+ <test name="microprofile-rest-client TCK">
+ <packages>
+ <package name="org.eclipse.microprofile.rest.client.tck.*">
+ </package>
+ </packages>
+ <classes>
+ <class name="org.eclipse.microprofile.rest.client.tck.InvokeWithJsonBProviderTest">
+ <methods>
+ <exclude name="testGetExecutesForBothClients"/> <!-- Json in this test is malformed -->
+ </methods>
+ </class>
+ </classes>
+<!-- <classes>-->
+<!-- <class name="org.eclipse.microprofile.rest.client.tck.asynctests.AsyncMethodTest">-->
+<!-- <methods>-->
+<!-- <include name="testAsyncInvocationInterceptorProvider"/>-->
+<!-- </methods>-->
+<!-- </class>-->
+<!-- </classes>-->
+ </test>
+
+</suite>
\ No newline at end of file
diff --git a/ext/microprofile/pom.xml b/ext/microprofile/pom.xml
new file mode 100644
index 0000000..7a50ddf
--- /dev/null
+++ b/ext/microprofile/pom.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-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">
+ <parent>
+ <artifactId>project</artifactId>
+ <groupId>org.glassfish.jersey.ext</groupId>
+ <version>2.29-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>jersey-microprofile</artifactId>
+ <packaging>pom</packaging>
+
+ <modules>
+ <module>mp-rest-client</module>
+ </modules>
+
+
+</project>
\ No newline at end of file
diff --git a/ext/pom.xml b/ext/pom.xml
index 22245f1..df0ccd7 100644
--- a/ext/pom.xml
+++ b/ext/pom.xml
@@ -56,6 +56,7 @@
<module>spring4</module>
<module>spring5</module>
<module>wadl-doclet</module>
+ <module>microprofile</module>
</modules>
<dependencies>