fish.payara.ejb.http.client.EjbHttpProxyHandler Maven / Gradle / Ivy
package fish.payara.ejb.http.client;
import static javax.naming.Context.SECURITY_CREDENTIALS;
import static javax.naming.Context.SECURITY_PRINCIPAL;
import static jakarta.ws.rs.HttpMethod.POST;
import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.Future;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.client.Invocation;
import jakarta.ws.rs.client.WebTarget;
import jakarta.ws.rs.core.Cookie;
import jakarta.ws.rs.core.GenericType;
import jakarta.ws.rs.core.MultivaluedHashMap;
import jakarta.ws.rs.core.MultivaluedMap;
/**
* This class handles invocations (method calls) on a proxy generated by {@link EjbRestProxyFactory}.
*
*
* This proxy uses REST calls to an endpoint in a given remote Payara server.
*
* @author Arjan Tijms
* @since Payara 5.191
*
*/
class EjbHttpProxyHandler implements InvocationHandler {
private final WebTarget target;
private final MultivaluedMap headers;
private final List cookies;
private final String lookup;
private final Map jndiOptions;
EjbHttpProxyHandler(WebTarget target, MultivaluedMap headers, List cookies, String lookup, Map jndiOptions) {
this.target = target;
this.headers = headers;
this.cookies = cookies;
this.lookup = lookup;
this.jndiOptions = jndiOptions;
}
@Override
public Object invoke(Object proxy, Method method, Object[] argValues) throws Throwable {
// Check for methods we should not proxy first
if (argValues == null && method.getName().equals("toString")) {
return toString();
}
if (argValues == null && method.getName().equals("hashCode")) {
// unique instance in the JVM, and no need to override
return hashCode();
}
if (argValues != null && argValues.length == 1 && method.getName().equals("equals")) {
// unique instance in the JVM, and no need to override
return equals(argValues[0]);
}
// Valid method, do invoke it remotely
return doRemoteInvoke(proxy, method, argValues);
}
public Object doRemoteInvoke(Object proxy, Method method, Object[] argValues) throws Throwable {
// HTTP method name; we're always using POST to invoke remote EJBs
String httpMethod = POST;
// The bare payload being sent
Map payload = new HashMap<>();
payload.put("lookup", lookup);
payload.put("method", method.getName());
payload.put("argTypes", Arrays.stream(method.getParameterTypes()).map(Class::getName).toArray());
payload.put("argValues", argValues == null? new Object[0] : argValues);
if (jndiOptions.containsKey(SECURITY_PRINCIPAL)) {
payload.put(SECURITY_PRINCIPAL, Lookup.base64Encode(jndiOptions.get(SECURITY_PRINCIPAL)));
}
if (jndiOptions.containsKey(SECURITY_CREDENTIALS)) {
payload.put(SECURITY_CREDENTIALS, Lookup.base64Encode(jndiOptions.get(SECURITY_CREDENTIALS)));
}
// Payload wrapped as entity so it'll be encoded in JSON
Entity> entity = Entity.entity(payload, APPLICATION_JSON);
// Response type
Class> responseType = method.getReturnType();
GenericType> responseGenericType = new GenericType<>(method.getGenericReturnType());
// Create a new UriBuilder appending the name from the method
WebTarget newTarget = addPathFromMethod(method, target);
// Start request
Invocation.Builder builder = newTarget.request();
// Set optional headers and cookies
builder.headers(new MultivaluedHashMap(this.headers));
for (Cookie cookie : new LinkedList<>(this.cookies)) {
builder = builder.cookie(cookie);
}
// Call remote server
if (responseType.isAssignableFrom(CompletionStage.class)) {
// Reactive call - the actual response type is T from CompletionStage
return builder.rx().method(httpMethod, entity, getResponseParameterizedType(method, responseGenericType));
} else if (responseType.isAssignableFrom(Future.class)) {
// Asynchronous call - the actual response type is T from Future
return builder.async().method(httpMethod, entity, getResponseParameterizedType(method, responseGenericType));
}
// Synchronous call
return builder.method(httpMethod, entity, responseGenericType);
}
private GenericType> getResponseParameterizedType(Method method, GenericType> responseGenericType) {
if (method.getGenericReturnType() instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) method.getGenericReturnType();
return new GenericType<>(parameterizedType.getActualTypeArguments()[0]);
}
return responseGenericType;
}
private static WebTarget addPathFromMethod(Method method, WebTarget target) {
return target.path(method.getName());
}
@Override
public String toString() {
return target.toString();
}
}