blob: 59d742c2704c3f50f225f62b795f188cf928e281 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2018 The Eclipse Foundation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* The Eclipse Foundation - initial API and implementation
*******************************************************************************/
package org.eclipse.epp.mpc.rest.client.internal.resteasy;
import static java.util.Objects.requireNonNull;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.URI;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import javax.ws.rs.client.WebTarget;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.epp.mpc.rest.client.IRestClient;
import org.eclipse.epp.mpc.rest.client.internal.util.RuntimeCoreException;
import org.jboss.resteasy.client.jaxrs.ProxyBuilder;
public class ApacheHttpClientRestClientImpl<E> implements IRestClient<E> {
public static final String CONTEXT_PROVIDER_KEY = ApacheHttpClientRestClientImpl.class + ".httpContextProvider";
public static final String CONTEXT_MONITOR_KEY = ApacheHttpClientRestClientImpl.class
+ ".httpContext.progressMonitor";
private final HttpContext defaultContext;
private final WebTarget webTarget;
private final Function<IProgressMonitor, E> monitoredEndpointSupplier;
private final E endpoint;
public ApacheHttpClientRestClientImpl(Class<E> endpointClass, WebTarget target, HttpContext defaultContext) {
this.defaultContext = defaultContext == null ? new BasicHttpContext() : defaultContext;
this.endpoint = ProxyBuilder.builder(endpointClass, target).build();
this.webTarget = target;
this.monitoredEndpointSupplier = createMonitoredEndpointSupplier(endpointClass, target, this.defaultContext,
this.endpoint);
}
private static <E> Function<IProgressMonitor, E> createMonitoredEndpointSupplier(Class<E> endpointClass,
WebTarget target, HttpContext defaultContext, E endpoint) {
HttpContextInjector contextProvider = (HttpContextInjector) target.getConfiguration()
.getProperty(CONTEXT_PROVIDER_KEY);
if (contextProvider == null) {
return null;
}
@SuppressWarnings("unchecked")
Class<? extends E> proxyClass = (Class<? extends E>) Proxy.getProxyClass(endpointClass.getClassLoader(),
endpointClass);
Constructor<? extends E> constructor;
try {
constructor = proxyClass.getConstructor(InvocationHandler.class);
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
InvocationHandler endpointInvocationHandler = Proxy.getInvocationHandler(endpoint);
return monitor -> {
InvocationHandler monitoredInvocationHandler = new MonitoredInvocationHandler(endpointInvocationHandler,
contextProvider, monitor, defaultContext);
try {
return constructor.newInstance(monitoredInvocationHandler);
} catch (IllegalAccessException | InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
}
};
}
@Override
public URI getBaseUri() {
return webTarget.getUri();
}
@Override
public E call() {
return endpoint;
}
@Override
public E call(IProgressMonitor monitor) throws UnsupportedOperationException, RuntimeCoreException {
if (monitoredEndpointSupplier == null) {
throw new UnsupportedOperationException();
}
return monitoredEndpointSupplier.apply(monitor);
}
private static final class MonitoredInvocationHandler implements InvocationHandler {
private final InvocationHandler delegate;
private final AtomicReference<IProgressMonitor> monitorReference;
private final HttpContextInjector contextProvider;
private final HttpContext parentContext;
public MonitoredInvocationHandler(InvocationHandler delegate, HttpContextInjector contextProvider,
IProgressMonitor monitor, HttpContext parentContext) {
requireNonNull(delegate, "delegate");
requireNonNull(delegate, "contextProvider");
requireNonNull(monitor, "monitor");
requireNonNull(monitor, "parentContext");
this.delegate = delegate;
this.contextProvider = contextProvider;
this.monitorReference = new AtomicReference<>(monitor);
this.parentContext = parentContext;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
IProgressMonitor monitor = monitorReference.getAndSet(null);
if (monitor == null) {
throw new IllegalStateException("monitor not set or already consumed"); //$NON-NLS-1$
}
HttpContext monitoredContext = new BasicHttpContext(parentContext);
monitoredContext.setAttribute(CONTEXT_MONITOR_KEY, monitor);
Callable<?> delegateInvoke = () -> {
try {
return delegate.invoke(proxy, method, args);
} catch (Exception ex) {
throw ex;
} catch (Throwable t) {
throw (Error) t;
}
};
return contextProvider.withContext(monitoredContext, delegateInvoke);
}
}
}