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.
com.outbrain.ob1k.server.registry.ServiceRegistry Maven / Gradle / Ivy
package com.outbrain.ob1k.server.registry;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.concurrent.Executor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import com.google.common.collect.Maps;
import com.outbrain.ob1k.HttpRequestMethodType;
import com.outbrain.ob1k.Request;
import com.outbrain.ob1k.common.concurrent.ComposableFutureHelper;
import com.outbrain.ob1k.common.filters.ServiceFilter;
import com.outbrain.ob1k.common.filters.StreamFilter;
import com.outbrain.ob1k.Service;
import com.outbrain.ob1k.common.marshalling.RequestMarshallerRegistry;
import com.outbrain.ob1k.common.marshalling.TypeHelper;
import com.outbrain.ob1k.common.filters.AsyncFilter;
import com.outbrain.ob1k.server.MethodParamNamesExtractor;
import com.outbrain.ob1k.common.filters.SyncFilter;
import com.outbrain.ob1k.server.registry.endpoints.AbstractServerEndpoint;
import com.outbrain.ob1k.server.registry.endpoints.AsyncServerEndpoint;
import com.outbrain.ob1k.server.registry.endpoints.StreamServerEndpoint;
import com.outbrain.ob1k.server.registry.endpoints.SyncServerEndpoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rx.Observable;
/**
* User: aronen
* Date: 6/23/13
* Time: 11:25 AM
*/
public class ServiceRegistry {
private static final Logger logger = LoggerFactory.getLogger(ServiceRegistry.class);
private final PathTrie> endpoints;
private String contextPath;
private final RequestMarshallerRegistry marshallerRegistry;
public ServiceRegistry(final RequestMarshallerRegistry marshallerRegistry) {
this.endpoints = new PathTrie<>();
this.marshallerRegistry = marshallerRegistry;
}
public void setContextPath(final String contextPath) {
this.contextPath = contextPath;
}
public AbstractServerEndpoint findEndpoint(final String path, final HttpRequestMethodType requestMethodType, final Map pathParams) {
final Map serviceEndpoints = endpoints.retrieve(path, pathParams);
if (serviceEndpoints == null) {
return null;
}
if (serviceEndpoints.containsKey(HttpRequestMethodType.ANY)) {
return serviceEndpoints.get(HttpRequestMethodType.ANY);
}
return serviceEndpoints.get(requestMethodType);
}
public void register(final String name, final Service service, final boolean bindPrefix,
final Executor executorService) {
register(name, service, null, null, null, bindPrefix, executorService);
}
public static class EndpointDescriptor {
public final Method method;
public final List extends ServiceFilter> filters;
public final HttpRequestMethodType requestMethodType;
public EndpointDescriptor(final Method method, final List extends ServiceFilter> filters, final HttpRequestMethodType requestMethodType) {
this.method = method;
this.filters = filters;
this.requestMethodType = requestMethodType;
}
}
public void register(final String name, final Service service, final Map> descriptors,
final boolean bindPrefix, final Executor executorService) {
if (contextPath == null) {
throw new RuntimeException("Can't add service before context path is set.");
}
final Map> methodsParams;
try {
methodsParams = MethodParamNamesExtractor.extract(service.getClass(), getMethods(descriptors));
} catch (final Exception e) {
throw new RuntimeException("Service " + name + " can't be analyzed", e);
}
/**
* Building full path
*/
for (final String methodBind: descriptors.keySet()) {
final StringBuilder path = new StringBuilder();
path.append(contextPath);
if (!contextPath.endsWith("/")) {
path.append('/');
}
if (name.startsWith("/")) {
path.append(name.substring(1));
} else {
path.append(name);
}
if (!name.endsWith("/")) {
path.append('/');
}
if (methodBind.startsWith("/")) {
path.append(methodBind.substring(1));
} else {
path.append(methodBind);
}
final Map endpointDescriptors = descriptors.get(methodBind);
final Map endpointsMap = new HashMap<>();
if (endpointDescriptors.containsKey(HttpRequestMethodType.ANY) && endpointDescriptors.size() > 1) {
throw new RuntimeException("Cannot add more request methods for the path after defining an ANY (all) endpoint path");
}
for (final Map.Entry endpointDescriptorEntry : endpointDescriptors.entrySet()) {
final EndpointDescriptor endpointDesc = endpointDescriptorEntry.getValue();
final Method method = endpointDesc.method;
final List methodParamNames = methodsParams.get(method);
marshallerRegistry.registerTypes(TypeHelper.extractTypes(method));
validateMethodParams(methodBind, endpointDesc, method, methodParamNames);
final String[] params = methodParamNames.toArray(new String[methodParamNames.size()]);
final AbstractServerEndpoint endpoint = isAsyncMethod(method) ?
new AsyncServerEndpoint(service, getAsyncFilters(endpointDesc.filters, methodBind), method, endpointDesc.requestMethodType, params) :
isStreamingMethod(method) ?
new StreamServerEndpoint(service, getStreamFilters(endpointDesc.filters, methodBind), method, endpointDesc.requestMethodType, params) :
new SyncServerEndpoint(service, getSyncFilters(endpointDesc.filters, methodBind), method, endpointDesc.requestMethodType, params, executorService);
endpointsMap.put(endpointDescriptorEntry.getKey(), endpoint);
}
endpoints.insert(path.toString(), endpointsMap, bindPrefix);
}
}
private void validateMethodParams(final String methodBind, final EndpointDescriptor endpointDesc, final Method method, final List methodParamNames) {
final Class>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length == 1 && parameterTypes[0] == Request.class) {
return;
}
if (Arrays.asList(parameterTypes).contains(Request.class)) {
throw new RuntimeException("Request object must be the only param in the method signature");
}
if (methodBind.contains("{")) {
int index = methodBind.indexOf('{');
int methodParamPos = 0;
while (index >= 0) {
final int endIndex = methodBind.indexOf('}', index);
final String pathParameter = methodBind.substring(index + 1, endIndex);
if (!methodParamNames.contains(pathParameter)) {
throw new RuntimeException("Parameter " + pathParameter + " does not exists in method signature");
}
if (endpointDesc.requestMethodType == HttpRequestMethodType.POST || endpointDesc.requestMethodType == HttpRequestMethodType.PUT) {
if (methodParamNames.indexOf(pathParameter) != methodParamPos) {
throw new RuntimeException("Path parameters should be in prefix when using method binding, i.e. methodName(id, name) => {id}/{name} or {id} with body of [name]");
}
methodParamPos++;
}
final Class paramType = parameterTypes[methodParamNames.indexOf(pathParameter)];
if (!paramType.isPrimitive() && !String.class.isAssignableFrom(paramType)) {
throw new RuntimeException("Path parameter " + paramType + " can be only primitive or String type");
}
index = methodBind.indexOf('{', endIndex);
}
}
}
private static List getMethods(final Map> descriptors) {
final List methods = new ArrayList<>();
for (final Map descMap : descriptors.values()) {
for (final EndpointDescriptor desc : descMap.values()) {
methods.add(desc.method);
}
}
return methods;
}
private static AsyncFilter[] getAsyncFilters(final List extends ServiceFilter> filters, final String methodName) {
if (filters == null)
return null;
final AsyncFilter[] result = new AsyncFilter[filters.size()];
int index = 0;
for (final ServiceFilter filter : filters) {
if (filter instanceof AsyncFilter) {
result[index++] = (AsyncFilter) filter;
} else {
throw new RuntimeException("method " + methodName + " can only receive async filters");
}
}
return result;
}
private static SyncFilter[] getSyncFilters(final List extends ServiceFilter> filters, final String methodName) {
if (filters == null)
return null;
final SyncFilter[] result = new SyncFilter[filters.size()];
int index = 0;
for (final ServiceFilter filter : filters) {
if (filter instanceof SyncFilter) {
result[index++] = (SyncFilter) filter;
} else {
throw new RuntimeException("method " + methodName + " can only receive sync filters");
}
}
return result;
}
private static StreamFilter[] getStreamFilters(final List extends ServiceFilter> filters, final String methodName) {
if (filters == null)
return null;
final StreamFilter[] result = new StreamFilter[filters.size()];
int index = 0;
for (final ServiceFilter filter : filters) {
if (filter instanceof StreamFilter) {
result[index++] = (StreamFilter) filter;
} else {
throw new RuntimeException("method " + methodName + " can only receive stream filters");
}
}
return result;
}
public void register(final String name, final Service service, final List asyncFilters,
final List syncFilters, final List streamFilters,
final boolean bindPrefix, final Executor executorService) {
final Map> descriptors = getEndpointsDescriptor(service,
executorService, asyncFilters, syncFilters, streamFilters);
register(name, service, descriptors, bindPrefix, executorService);
}
private Map> getEndpointsDescriptor(final Service service,
final Executor executorService,
final List asyncFilters,
final List syncFilters,
final List streamFilters) {
final Method[] methods = service.getClass().getDeclaredMethods();
final Map> result = new HashMap<>();
for (final Method m : methods) {
final int modifiers = m.getModifiers();
if (Modifier.isPublic(modifiers) && !Modifier.isStatic(modifiers)) {
if (isAsyncMethod(m)) {
result.put(m.getName(), singleEndpointDescToMap(new EndpointDescriptor(m, asyncFilters, HttpRequestMethodType.ANY)));
} else if (isStreamingMethod(m)) {
result.put(m.getName(), singleEndpointDescToMap(new EndpointDescriptor(m, streamFilters, HttpRequestMethodType.ANY)));
} else if (executorService != null) {
// A sync method with a defined executor service.
result.put(m.getName(), singleEndpointDescToMap(new EndpointDescriptor(m, syncFilters, HttpRequestMethodType.ANY)));
} else {
logger.info("Method " + m.getName() + " wasn't bounded. Sync method needs a configured executor service");
}
}
}
// in case of a single method service (e.g. SelfTestService) we don't want to include the method name as a path
if (result.size() == 1) {
result.put("", result.values().iterator().next());
}
return result;
}
private Map singleEndpointDescToMap(final EndpointDescriptor endpointDescriptor) {
final Map endpointDescriptorMap = Maps.newHashMap();
endpointDescriptorMap.put(HttpRequestMethodType.ANY, endpointDescriptor);
return endpointDescriptorMap;
}
public SortedMap> getRegisteredEndpoints() {
return endpoints.getPathToValueMapping();
}
public void logRegisteredEndpoints() {
for (final Map.Entry> pathEndpointsMap : getRegisteredEndpoints().entrySet()) {
for (final Map.Entry endpointEntry : pathEndpointsMap.getValue().entrySet()) {
final AbstractServerEndpoint endpointValue = endpointEntry.getValue();
logger.info("Registered endpoint [{} ==> {}, via method: {}]", pathEndpointsMap.getKey(), endpointValue.getTargetAsString(), endpointValue.requestMethodType);
}
}
}
private boolean isAsyncMethod(final Method m) {
return ComposableFutureHelper.isComposableFuture(m.getReturnType());
}
private boolean isStreamingMethod(final Method m) {
return m.getReturnType() == Observable.class;
}
}