net.e6tech.elements.network.restful.RestfulProxy Maven / Gradle / Ivy
/*
Copyright 2015-2019 Futeh Kao
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package net.e6tech.elements.network.restful;
import com.fasterxml.jackson.databind.type.CollectionType;
import com.fasterxml.jackson.databind.type.MapType;
import com.fasterxml.jackson.databind.type.TypeFactory;
import net.e6tech.elements.common.interceptor.CallFrame;
import net.e6tech.elements.common.interceptor.Interceptor;
import net.e6tech.elements.common.interceptor.InterceptorHandler;
import net.e6tech.elements.common.interceptor.InterceptorListener;
import net.e6tech.elements.common.reflection.Reflection;
import net.e6tech.elements.common.util.ExceptionMapper;
import net.e6tech.elements.common.util.datastructure.Pair;
import net.e6tech.elements.jmx.stat.Gauge;
import org.ehcache.impl.internal.concurrent.ConcurrentHashMap;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import java.io.PrintWriter;
import java.lang.reflect.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
/**
* Created by futeh.
*/
@SuppressWarnings("unchecked")
public class RestfulProxy {
private static final long GAUGE_PERIOD = 15 * 60 * 1000L;
private RestfulClient client;
private Interceptor interceptor;
private Map requestProperties = new LinkedHashMap<>();
private PrintWriter printer;
private Response lastResponse;
private Gauge gauge;
public RestfulProxy(String hostAddress) {
client = new RestfulClient(hostAddress);
interceptor = Interceptor.getInstance();
}
public RestfulProxy(RestfulClient client) {
this.client = client;
interceptor = Interceptor.getInstance();
}
public ExceptionMapper getExceptionMapper() {
return client.getExceptionMapper();
}
public void setExceptionMapper(ExceptionMapper exceptionMapper) {
client.setExceptionMapper(exceptionMapper);
}
public PrintWriter getPrinter() {
return printer;
}
public void setPrinter(PrintWriter printer) {
this.printer = printer;
client.setPrinter(printer);
}
public String getHostAddress() {
return client.getAddress();
}
public boolean isSkipHostnameCheck() {
return client.isSkipHostnameCheck();
}
public void setSkipHostnameCheck(boolean skipHostnameCheck) {
client.setSkipHostnameCheck(skipHostnameCheck);
}
public boolean isSkipCertCheck() {
return client.isSkipCertCheck();
}
public void setSkipCertCheck(boolean skipCertCheck) {
client.setSkipCertCheck(skipCertCheck);
}
public T newProxy(Class serviceClass) {
client.setPrinter(printer);
return interceptor.newInstance(serviceClass, new InvocationHandler(this, serviceClass, null));
}
public T newProxy(Class serviceClass, Presentation presentation) {
client.setPrinter(printer);
return interceptor.newInstance(serviceClass, new InvocationHandler(this, serviceClass, presentation));
}
public T newProxy(Class serviceClass, InterceptorListener listener) {
client.setPrinter(printer);
return interceptor.instanceBuilder(serviceClass, new InvocationHandler(this, serviceClass, null))
.listener(listener).build();
}
public T newProxy(Class serviceClass, Presentation presentation, InterceptorListener listener) {
client.setPrinter(printer);
return interceptor.instanceBuilder(serviceClass, new InvocationHandler(this, serviceClass, presentation))
.listener(listener)
.build();
}
public Gauge getGauge() {
return gauge;
}
public void setGauge(Gauge gauge) {
this.gauge = gauge;
}
public void enableMeasurement(boolean enable) {
if (enable) {
if (gauge == null) {
gauge = new Gauge();
gauge.setPeriod(GAUGE_PERIOD);
gauge.initialize(null);
}
} else {
gauge.cancel();
gauge = null;
}
}
public Gauge getGauge(Object service) {
InterceptorHandler handler = Interceptor.getInterceptorHandler(service);
if (handler instanceof InvocationHandler)
return ((InvocationHandler) handler).gauge;
return null;
}
public void enableGauge(Object service) {
InterceptorHandler handler = Interceptor.getInterceptorHandler(service);
if (handler instanceof InvocationHandler)
((InvocationHandler) handler).enableGauge();
}
public void disableGauge(Object service) {
InterceptorHandler handler = Interceptor.getInterceptorHandler(service);
if (handler instanceof InvocationHandler)
((InvocationHandler) handler).disableGauge();
}
public Map getRequestProperties() {
return Collections.unmodifiableMap(requestProperties);
}
public void setRequestProperties(Map map) {
requestProperties.putAll(map);
}
public void setRequestProperty(String key, String value) {
requestProperties.put(key, value);
}
public void clearRequestProperty(String key) {
requestProperties.remove(key);
}
public void clearAllRequestProperties() {
requestProperties.clear();
}
public Response getLastResponse() {
return lastResponse;
}
public RestfulClient getClient() {
return client;
}
public static class InvocationHandler implements InterceptorHandler {
private Gauge gauge;
private final RestfulProxy proxy;
private String context;
private Map methodForwarders = new ConcurrentHashMap<>();
private Map methodSignatures = new ConcurrentHashMap<>();
private Presentation presentation;
private String serviceClass;
InvocationHandler(RestfulProxy proxy, Class> serviceClass, Presentation presentation) {
this.proxy = proxy;
this.presentation = presentation;
this.serviceClass = serviceClass.getName();
Path path = serviceClass.getAnnotation(Path.class);
if (path != null) {
this.context = path.value();
if (!context.endsWith("/"))
this.context = path.value() + "/";
} else {
context = "/";
}
if (proxy.gauge != null) {
gauge = new Gauge();
Reflection.copyInstance(gauge, proxy.gauge);
gauge.setFormat((k, m) -> String.format("Restful Service %s path=%s: %s", this.serviceClass, k, m));
gauge.initialize(null);
}
}
void enableGauge() {
if (gauge == null) {
gauge = new Gauge();
if (proxy.gauge != null)
Reflection.copyInstance(gauge, proxy.gauge);
gauge.setFormat((k, m) -> String.format("Restful Service %s path=%s: %s", this.serviceClass, k, m));
gauge.initialize(null);
}
}
void disableGauge() {
if (gauge != null) {
gauge.cancel();
gauge = null;
}
}
@Override
public Object invoke(CallFrame frame) throws Throwable {
Request request = proxy.client.create();
if (presentation != null)
request.setPresentation(presentation);
for (Map.Entry entry : proxy.requestProperties.entrySet()) {
request.setRequestProperty(entry.getKey(), entry.getValue());
}
MethodForwarder forwarder;
try {
forwarder = methodForwarders.get(frame.getMethod());
if (forwarder == null) {
forwarder = new MethodForwarder(this, frame.getMethod());
methodForwarders.put(frame.getMethod(), forwarder);
}
printRequest(frame);
} catch (IllegalArgumentException ex) {
// this is clearly not a restful method
if (frame.getTarget() != null)
return frame.invoke(frame.getTarget());
return null;
}
long start = System.currentTimeMillis();
Pair pair = forwarder.forward(request, frame.getArguments());
Gauge g = gauge;
if (g != null) {
g.add(forwarder.destination, System.currentTimeMillis() - start);
}
synchronized (proxy) {
proxy.lastResponse = pair.key();
}
return pair.value();
}
private void printRequest(CallFrame frame) {
if (proxy.printer != null) {
proxy.printer.println("CALLING RESTFUL METHOD -------------");
String signature = methodSignatures.computeIfAbsent(frame.getMethod(), this::methodSignature);
String caller = Reflection.mapCallingStackTrace(e -> {
if (e.state().isPresent()) return e.get().toString(); // previous element match.
if (e.get().getMethodName().equals(frame.getMethod().getName())) e.state(Boolean.TRUE); // match, but we are interested in the next one.
return null;
}).orElse("Cannot detect caller");
proxy.printer.println("Called by: " + caller);
proxy.printer.println("Method: " + signature);
proxy.printer.println();
}
}
public RestfulProxy getProxy() {
return proxy;
}
public Presentation getPresentation() {
return presentation;
}
public void setPresentation(Presentation presentation) {
this.presentation = presentation;
}
String methodSignature(Method method) {
try {
StringBuilder sb = new StringBuilder();
sb.append(method.getReturnType().getSimpleName()).append(' ');
sb.append(method.getDeclaringClass().getTypeName()).append('.');
sb.append(method.getName());
sb.append('(');
separateWithCommas(method.getParameterTypes(), sb);
sb.append(')');
return sb.toString();
} catch (Exception e) {
return "<" + e + ">";
}
}
void separateWithCommas(Class>[] types, StringBuilder sb) {
for (int j = 0; j < types.length; j++) {
sb.append(types[j].getSimpleName());
if (j < (types.length - 1))
sb.append(",");
}
}
}
private static class MethodForwarder {
boolean get;
boolean post;
boolean put;
boolean patch;
boolean delete;
Class returnType;
ParameterizedType parameterizedReturnType;
Class[] paramTypes;
Parameter[] params;
String destination;
String context;
QueryParam[] queryParams;
PathParam[] pathParams;
BeanParam[] beanParams;
FormParam[] formParams;
HeaderParam[] headerParams;
MethodForwarder(InvocationHandler handler, Method method) {
initContext(handler, method);
returnType = method.getReturnType();
if (method.getGenericReturnType() instanceof ParameterizedType)
parameterizedReturnType = (ParameterizedType) method.getGenericReturnType();
paramTypes = method.getParameterTypes();
this.context = context;
queryParams = new QueryParam[paramTypes.length];
pathParams = new PathParam[paramTypes.length];
beanParams = new BeanParam[paramTypes.length];
formParams = new FormParam[paramTypes.length];
headerParams = new HeaderParam[paramTypes.length];
int idx = 0;
params = method.getParameters();
for (Parameter param : params) {
QueryParam queryParam = param.getAnnotation(QueryParam.class);
PathParam pathParam = param.getAnnotation(PathParam.class);
BeanParam beanParam = param.getAnnotation(BeanParam.class);
FormParam formParam = param.getAnnotation(FormParam.class);
HeaderParam headerParam = param.getAnnotation(HeaderParam.class);
if (queryParam != null) {
queryParams[idx] = queryParam;
}
if(pathParam != null) {
pathParams[idx] = pathParam;
}
if(beanParam != null) {
beanParams[idx] = beanParam;
}
if(formParam != null) {
formParams[idx] = formParam;
}
if(headerParams != null) {
headerParams[idx] = headerParam;
}
idx++;
}
if (method.getAnnotation(POST.class) != null) {
post = true;
} else if (method.getAnnotation(PUT.class) != null) {
put = true;
} else if (method.getAnnotation(PATCH.class) != null) {
patch = true;
} else if (method.getAnnotation(GET.class) != null) {
get = true;
} else if (method.getAnnotation(DELETE.class) != null) {
delete = true;
} else {
throw new IllegalArgumentException("Method " + method + " is not annotated with GET, PUT, POST or DELETE.");
}
}
private void initContext(InvocationHandler handler, Method method) {
String fullContext = handler.context;
Path path = method.getAnnotation(Path.class);
if (path != null) {
String subctx = path.value();
while (subctx.startsWith("/"))
subctx = subctx.substring(1);
fullContext = handler.context + subctx;
}
context = fullContext;
destination = handler.proxy.client.getAddress();
if (!destination.endsWith("/"))
destination = destination + "/";
if (fullContext != null) {
while (fullContext.startsWith("/"))
fullContext = fullContext.substring(1);
}
destination = destination + fullContext;
}
private Optional
© 2015 - 2025 Weber Informatics LLC | Privacy Policy