Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.glassfish.jersey.microprofile.restclient.RestClientBuilderImpl Maven / Gradle / Ivy
/*
* Copyright (c) 2019, 2024 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2021 Payara Foundation and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package org.glassfish.jersey.microprofile.restclient;
import java.io.Closeable;
import java.lang.reflect.Proxy;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.AccessController;
import java.security.KeyStore;
import java.util.ArrayList;
import java.util.Comparator;
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.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import jakarta.ws.rs.Priorities;
import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.ClientBuilder;
import jakarta.ws.rs.client.WebTarget;
import jakarta.ws.rs.core.Configuration;
import jakarta.ws.rs.core.Feature;
import jakarta.ws.rs.core.FeatureContext;
import jakarta.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.QueryParamStyle;
import org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper;
import org.eclipse.microprofile.rest.client.spi.RestClientListener;
import org.glassfish.jersey.JerseyPriorities;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.client.ClientProperties;
import org.glassfish.jersey.client.Initializable;
import org.glassfish.jersey.client.spi.ConnectorProvider;
import org.glassfish.jersey.ext.cdi1x.internal.CdiUtil;
import org.glassfish.jersey.innate.VirtualThreadUtil;
import org.glassfish.jersey.internal.ServiceFinder;
import org.glassfish.jersey.internal.inject.InjectionManager;
import org.glassfish.jersey.internal.inject.InjectionManagerSupplier;
import org.glassfish.jersey.internal.util.ReflectionHelper;
import org.glassfish.jersey.uri.JerseyQueryParamStyle;
/**
* Rest client builder implementation. Creates proxy instance of requested interface.
*
* @author David Kral
* @author Patrik Dudits
* @author Tomas Langer
*/
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> responseExceptionMappers;
private final Set paramConverterProviders;
private final Set inboundHeaderProviders;
private final List asyncInterceptorFactories;
private final Config config;
private final ConfigWrapper configWrapper;
private URI uri;
private ClientBuilder clientBuilder;
private Supplier executorService;
private HostnameVerifier sslHostnameVerifier;
private SSLContext sslContext;
private KeyStore sslTrustStore;
private KeyStore sslKeyStore;
private char[] sslKeyStorePassword;
private ConnectorProvider connector;
private boolean followRedirects;
RestClientBuilderImpl() {
clientBuilder = ClientBuilder.newBuilder();
responseExceptionMappers = new HashSet<>();
paramConverterProviders = new HashSet<>();
inboundHeaderProviders = new HashSet<>();
asyncInterceptorFactories = new ArrayList<>();
config = ConfigProvider.getConfig();
configWrapper = new ConfigWrapper(clientBuilder.getConfiguration());
executorService = () -> VirtualThreadUtil.withConfig(configWrapper).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 build(Class interfaceClass) throws IllegalStateException, RestClientDefinitionException {
for (RestClientListener restClientListener : ServiceFinder.find(RestClientListener.class)) {
restClientListener.onNewClient(interfaceClass, this);
}
if (uri == null) {
throw new IllegalStateException("Base uri/url cannot be null!");
}
//Provider registration part
processProviders(interfaceClass);
InjectionManagerExposer injectionManagerExposer = new InjectionManagerExposer();
register(injectionManagerExposer);
register(SseMessageBodyReader.class);
//We need to check first if default exception mapper was not disabled by property on builder.
registerExceptionMapper();
//sort all AsyncInvocationInterceptorFactory by priority
asyncInterceptorFactories.sort(Comparator.comparingInt(AsyncInvocationInterceptorFactoryPriorityWrapper::getPriority));
if (connector != null) {
ClientConfig config = new ClientConfig();
config.loadFrom(getConfiguration());
config.connectorProvider(connector);
clientBuilder = clientBuilder.withConfig(config); // apply config...
}
// override ClientConfig with values that have been set explicitly
clientBuilder.executorService(new ExecutorServiceWrapper(executorService.get()));
if (null != sslContext) {
clientBuilder.sslContext(sslContext);
}
if (null != sslHostnameVerifier) {
clientBuilder.hostnameVerifier(sslHostnameVerifier);
}
if (null != sslTrustStore) {
clientBuilder.trustStore(sslTrustStore);
}
if (null != sslKeyStore) {
clientBuilder.keyStore(sslKeyStore, sslKeyStorePassword);
}
Client client = clientBuilder.build();
if (client instanceof Initializable) {
((Initializable) client).preInitialize();
}
WebTarget webTarget = client.target(this.uri);
webTarget.property(ClientProperties.FOLLOW_REDIRECTS, followRedirects);
RestClientContext context = RestClientContext.builder(interfaceClass)
.responseExceptionMappers(responseExceptionMappers)
.paramConverterProviders(paramConverterProviders)
.inboundHeadersProviders(inboundHeaderProviders)
.asyncInterceptorFactories(new ArrayList<>(asyncInterceptorFactories))
.injectionManager(injectionManagerExposer.injectionManager)
.beanManager(CdiUtil.getBeanManager())
.build();
RestClientModel restClientModel = RestClientModel.from(context);
return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(),
new Class[] {interfaceClass, AutoCloseable.class, Closeable.class},
new ProxyInvocationHandler(client, webTarget, restClientModel)
);
}
@Override
public RestClientBuilder sslContext(SSLContext sslContext) {
this.sslContext = sslContext;
return this;
}
@Override
public RestClientBuilder trustStore(KeyStore keyStore) {
this.sslTrustStore = keyStore;
return this;
}
@Override
public RestClientBuilder keyStore(KeyStore keyStore, String password) {
this.sslKeyStore = keyStore;
this.sslKeyStorePassword = ((null == password) ? new char[0] : password.toCharArray());
return this;
}
@Override
public RestClientBuilder hostnameVerifier(HostnameVerifier hostnameVerifier) {
this.sslHostnameVerifier = hostnameVerifier;
return this;
}
private void registerExceptionMapper() {
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 disableDefaultMapperConfig = config.getOptionalValue(CONFIG_DISABLE_DEFAULT_MAPPER, boolean.class);
if (!disableDefaultMapperConfig.isPresent() || !disableDefaultMapperConfig.get()) {
register(new DefaultResponseExceptionMapper());
}
}
}
private void processProviders(Class interfaceClass) {
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 providersFromConfig = config.getOptionalValue(interfaceClass.getName() + CONFIG_PROVIDERS, String.class);
providersFromConfig.ifPresent(providers -> {
if (!providers.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());
}
}
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 providerPriorityMP = config.getOptionalValue(property, int.class);
if (providerPriorityMP.isPresent()) {
return providerPriorityMP.get();
}
} else if (providerPriorityJersey instanceof Integer) {
return (int) providerPriorityJersey;
}
return JerseyPriorities.getPriorityValue(providerClass, -1);
}
@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> componentClass) {
if (isSupportedCustomProvider(componentClass)) {
register(ReflectionUtil.createInstance(componentClass));
} else {
clientBuilder.register(componentClass);
}
return this;
}
@Override
public RestClientBuilder register(Class> componentClass, int priority) {
if (isSupportedCustomProvider(componentClass)) {
register(ReflectionUtil.createInstance(componentClass), priority);
} else {
clientBuilder.register(componentClass, priority);
}
return this;
}
@Override
public RestClientBuilder register(Class> componentClass, Class>... contracts) {
if (isSupportedCustomProvider(componentClass)) {
register(ReflectionUtil.createInstance(componentClass), contracts);
} else {
clientBuilder.register(componentClass, contracts);
}
return this;
}
@Override
public RestClientBuilder register(Class> componentClass, Map, Integer> contracts) {
if (isSupportedCustomProvider(componentClass)) {
register(ReflectionUtil.createInstance(componentClass), contracts);
} else {
clientBuilder.register(componentClass, contracts);
}
return this;
}
@Override
public RestClientBuilder register(Object component) {
if (component instanceof ResponseExceptionMapper) {
ResponseExceptionMapper mapper = (ResponseExceptionMapper) component;
registerCustomProvider(component, mapper.getPriority());
clientBuilder.register(mapper, mapper.getPriority());
} else {
clientBuilder.register(component);
registerCustomProvider(component, null);
}
return this;
}
@Override
public RestClientBuilder register(Object component, int priority) {
clientBuilder.register(component, priority);
registerCustomProvider(component, priority);
return this;
}
@Override
public RestClientBuilder register(Object component, Class>... contracts) {
for (Class> contract : contracts) {
if (isSupportedCustomProvider(contract)) {
register(component);
}
}
clientBuilder.register(component, contracts);
return this;
}
@Override
public RestClientBuilder register(Object component, Map, Integer> contracts) {
if (isSupportedCustomProvider(component.getClass())) {
if (component instanceof ResponseExceptionMapper) {
registerCustomProvider(component, contracts.get(ResponseExceptionMapper.class));
} else if (component instanceof ParamConverterProvider) {
registerCustomProvider(component, contracts.get(ParamConverterProvider.class));
}
}
clientBuilder.register(component, contracts);
return this;
}
private boolean isSupportedCustomProvider(Class> providerClass) {
return ResponseExceptionMapper.class.isAssignableFrom(providerClass)
|| ParamConverterProvider.class.isAssignableFrom(providerClass)
|| AsyncInvocationInterceptorFactory.class.isAssignableFrom(providerClass)
|| ConnectorProvider.class.isAssignableFrom(providerClass)
|| InboundHeadersProvider.class.isAssignableFrom(providerClass);
}
private void registerCustomProvider(Object instance, Integer 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, 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(new AsyncInvocationInterceptorFactoryPriorityWrapper((AsyncInvocationInterceptorFactory) instance,
priority));
}
if (instance instanceof ConnectorProvider) {
connector = (ConnectorProvider) instance;
}
if (instance instanceof InboundHeadersProvider) {
inboundHeaderProviders.add((InboundHeadersProvider) instance);
}
}
@Override
public RestClientBuilder followRedirects(boolean followRedirects) {
this.followRedirects = followRedirects;
return this;
}
@Override
public RestClientBuilder proxyAddress(String proxyHost, int proxyPort) {
if (proxyHost == null) {
throw new IllegalArgumentException("Proxy host must not be null");
}
if (proxyPort <= 0 || proxyPort > 65535) {
throw new IllegalArgumentException("Invalid proxy port");
}
// If proxyString is something like "localhost:8765" we need to add a scheme since the connectors expect one
String proxyString = createProxyString(proxyHost, proxyPort);
property(ClientProperties.PROXY_URI, proxyString);
return this;
}
static String createProxyString(String proxyHost, int proxyPort) {
boolean prependScheme = false;
String proxyString = proxyHost + ":" + proxyPort;
if (proxyString.split(":").length == 2) {
// Check if first character is a number to account for if proxyHost is given as an IP rather than a name
// URI.create("127.0.0.1:8765") will lead to an IllegalArgumentException
if (proxyString.matches("\\d.*")) {
prependScheme = true;
} else {
// "localhost:8765" will set the scheme as "localhost" and the host as "null"
URI proxyURI = URI.create(proxyString);
if (proxyURI.getHost() == null && proxyURI.getScheme().equals(proxyHost)) {
prependScheme = true;
}
}
}
if (prependScheme) {
proxyString = "http://" + proxyString;
Logger.getLogger(RestClientBuilderImpl.class.getName()).log(Level.FINE,
"No scheme provided with proxyHost: " + proxyHost + ". Defaulting to HTTP, proxy address = "
+ proxyString);
}
return proxyString;
}
@Override
public RestClientBuilder queryParamStyle(QueryParamStyle queryParamStyle) {
if (queryParamStyle != null) {
property(ClientProperties.QUERY_PARAM_STYLE,
JerseyQueryParamStyle.valueOf(queryParamStyle.toString()));
}
return this;
}
private static class InjectionManagerExposer implements Feature {
InjectionManager injectionManager;
@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");
}
}
}
private static class AsyncInvocationInterceptorFactoryPriorityWrapper
implements AsyncInvocationInterceptorFactory {
private AsyncInvocationInterceptorFactory factory;
private Integer priority;
AsyncInvocationInterceptorFactoryPriorityWrapper(AsyncInvocationInterceptorFactory factory, Integer priority) {
this.factory = factory;
this.priority = priority;
}
@Override
public AsyncInvocationInterceptor newInterceptor() {
return factory.newInterceptor();
}
Integer getPriority() {
if (priority == null) {
priority = JerseyPriorities.getPriorityValue(factory.getClass(), Priorities.USER);
}
return priority;
}
}
}