All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.outbrain.ob1k.server.build.ServerBuilder Maven / Gradle / Ivy

The newest version!
package com.outbrain.ob1k.server.build;

import com.outbrain.ob1k.HttpRequestMethodType;
import com.outbrain.ob1k.Service;
import com.outbrain.ob1k.common.filters.AsyncFilter;
import com.outbrain.ob1k.common.filters.ServiceFilter;
import com.outbrain.ob1k.common.filters.StreamFilter;
import com.outbrain.ob1k.common.filters.SyncFilter;
import com.outbrain.ob1k.common.marshalling.RequestMarshallerRegistry;
import com.outbrain.ob1k.server.BeanContext;
import com.outbrain.ob1k.server.Server;
import com.outbrain.ob1k.server.StaticPathResolver;
import com.outbrain.ob1k.server.netty.NettyServer;
import com.outbrain.ob1k.server.registry.ServiceRegistry;
import com.outbrain.ob1k.server.services.EndpointMappingService;
import com.outbrain.ob1k.server.util.ObservableBlockingQueue;
import com.outbrain.ob1k.server.util.SyncRequestQueueObserver;
import com.outbrain.swinfra.metrics.api.MetricFactory;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.DefaultThreadFactory;
import io.netty.util.concurrent.GlobalEventExecutor;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;

/**
 * User: aronen
 * Date: 6/23/13
 * Time: 2:16 PM
 */
public class ServerBuilder implements InitialPhase, ChoosePortPhase, ChooseContextPathPhase, ChooseServiceCreationTypePhase,
                                      AddServiceFromContextPhase, AddRawServicePhase, StaticResourcesPhase, ExtraParamsPhase
{
  public static final int DEFAULT_MAX_CONTENT_LENGTH = 256 * 1024;
  
  private int port = 0;
  private final ServiceRegistry registry;
  private final RequestMarshallerRegistry marshallerRegistry;
  private final List staticFolders;
  private final Map staticResources;
  private String contextPath = "";
  private String appName = "";
  private final Map staticMappings;
  private boolean acceptKeepAlive = false;
  private boolean supportZip = true;
  private int maxContentLength = DEFAULT_MAX_CONTENT_LENGTH;
  private long requestTimeoutMs = -1;

  private com.outbrain.swinfra.metrics.api.MetricFactory metricFactory;

  private ThreadPoolConfig threadPoolConfig = null;
  private BeanContext ctx;
  private final List serviceDescriptors = new ArrayList<>();
  private final List listeners = new LinkedList<>();

  public static InitialPhase newBuilder() {
    return new ServerBuilder();
  }

  private ServerBuilder() {
    this.staticFolders = new ArrayList<>();
    this.staticMappings = new HashMap<>();
    this.staticResources = new HashMap<>();
    this.marshallerRegistry = new RequestMarshallerRegistry();
    this.registry = new ServiceRegistry(marshallerRegistry);
  }

  @Override
  public ChooseContextPathPhase setPort(final int port) {
    this.port = port;
    return this;
  }

  @Override
  public ChooseContextPathPhase useRandomPort() {
    this.port = 0;
    return this;
  }

  @Override
  public ChooseContextPathPhase configurePorts(final PortsProvider provider) {
    provider.configure(this);
    return this;
  }

  private static class ThreadPoolConfig {
    public final int minSize;
    public final int maxSize;

    private ThreadPoolConfig(final int minSize, final int maxSize) {
      this.minSize = minSize;
      this.maxSize = maxSize;
    }
  }

  private static Executor createExecutorService(final ThreadPoolConfig config, final SyncRequestQueueObserver queueObserver) {
    final int queueCapacity = config.maxSize * 2; // TODO make this configurable ?
    final BlockingQueue requestQueue =
            new ObservableBlockingQueue<>(new LinkedBlockingQueue(queueCapacity), queueObserver);

    return new ThreadPoolExecutor(config.minSize, config.maxSize, 30, TimeUnit.SECONDS, requestQueue,
        new DefaultThreadFactory("syncReqPool"), new ThreadPoolExecutor.AbortPolicy());
  }

  @Override
  public ServerBuilder configureExecutorService(final int minSize, final int maxSize) {
    this.threadPoolConfig = new ThreadPoolConfig(minSize, maxSize);
    return this;
  }

  @Override
  public ExtraParamsPhase setMetricFactory(final MetricFactory metricFactory) {
    this.metricFactory = metricFactory;
    return this;
  }

  @Override
  public ExtraParamsPhase addListener(final Server.Listener listener) {
    this.listeners.add(listener);
    return this;
  }

  @Override
  public AddRawServicePhase addService(final Service service, final String path, final ServiceFilter... filters) {
    return addService(service, path, true, filters);
  }

  private static class ServiceDescriptor {
    final String name;
    final Service service;
    final List asyncFilters;
    final List syncFilters;
    final List streamFilters;
    final Map> endpointsBinding;
    final boolean bindPrefix;

    private ServiceDescriptor(final String name, final Service service, final List asyncFilters,
                              final List syncFilters, final List streamFilters,
                              final Map> endpointsBinding,
                              final boolean bindPrefix) {
      this.name = name;
      this.service = service;
      this.asyncFilters = asyncFilters;
      this.syncFilters = syncFilters;
      this.streamFilters = streamFilters;
      this.endpointsBinding = endpointsBinding;
      this.bindPrefix = bindPrefix;
    }
  }

  @Override
  public AddRawServicePhase addService(final Service service, final String path, final boolean bindPrefix, final ServiceFilter... filters) {
    final List asyncFilters = new ArrayList<>();
    final List syncFilters = new ArrayList<>();
    final List streamFilters = new ArrayList<>();

    if (filters != null) {
      for (final ServiceFilter filter: filters) {
        if (filter instanceof SyncFilter) {
          syncFilters.add((SyncFilter) filter);
        }

        if (filter instanceof AsyncFilter) {
          asyncFilters.add((AsyncFilter) filter);
        }

        if (filter instanceof StreamFilter) {
          streamFilters.add((StreamFilter) filter);
        }
      }
    }

    serviceDescriptors.add(new ServiceDescriptor(path, service, asyncFilters, syncFilters, streamFilters, null, bindPrefix));
    return this;
  }

  @Override
  public ChooseServiceCreationTypePhase configureExtraParams(final ExtraParamsProvider provider) {
    provider.configureExtraParams(this);
    return this;
  }

  @Override
  public ExtraParamsPhase acceptKeepAlive(final boolean keepAlive) {
    this.acceptKeepAlive = keepAlive;
    return this;
  }

  @Override
  public ExtraParamsPhase supportZip(final boolean useZip) {
    this.supportZip = useZip;
    return this;
  }

  @Override
  public AddRawServicePhase addEndpointsMappingService(final String path) {
    return addEndpointsMappingService(path, null);
  }

  @Override
  public AddRawServicePhase addEndpointsMappingService(final String path, final AsyncFilter filter) {
    return addService(new EndpointMappingService(registry), path, filter);
  }

  @Override
  public ChooseServiceCreationTypePhase withServicesFrom(final BeanContext ctx, final ContextBasedServiceProvider provider) {
    this.ctx = ctx;
    provider.addServices(this, ctx);
    return this;
  }

  @Override
  public ChooseServiceCreationTypePhase withServices(final RawServiceProvider provider) {
    provider.addServices(this);
    return this;
  }

  @Override
  public AddServiceFromContextPhase addServiceFromContext(final String ctxName, final Class serviceType, final String path) {
    return addServiceFromContext(ctxName, serviceType, null, path);
  }

  @Override
  public final AddServiceFromContextPhase addServiceFromContext(final String ctxName, final Class serviceType,
                                                                final Class filterType, final String path)
  {
    return addServiceFromContext(ctxName, serviceType, filterType, path, true);
  }

  @Override
  public AddServiceFromContextPhase addServiceFromContext(final String ctxName, final Class serviceType,
                                                          final Class filterType,
                                                          final String path, final boolean bindPrefix)
  {
    final ArrayList> filterTypes = new ArrayList<>();
    if (filterType != null) {
      filterTypes.add(filterType);
    }

    return addServiceFromContext(ctxName, serviceType, filterTypes, path, bindPrefix);
  }

  @Override
  public AddServiceFromContextPhase addServices(final ContextBasedServiceProvider provider) {
    provider.addServices(this, ctx);
    return this;
  }

  @Override
  public AddRawServicePhase addServices(final RawServiceProvider provider) {
    provider.addServices(this);
    return this;
  }

  @Override
  public final AddServiceFromContextPhase addServiceFromContext(final String ctxName, final Class serviceType,
                                                                final List> filterTypes,
                                                                final String path, final boolean bindPrefix) {
    final List filters = new ArrayList<>();
    if (filterTypes != null) {
      for (final Class filterType : filterTypes) {
        final ServiceFilter filter = ctx.getBean(ctxName, filterType);
        filters.add(filter);
      }
    }
    final Service service = ctx.getBean(ctxName, serviceType);

    addService(service, path, bindPrefix, filters.toArray(new ServiceFilter[filters.size()]));
    return this;
  }

  private ServiceBuilder defineService(final Service service, final String path) {
    return defineService(service, path, true);
  }

  private ServiceBuilder defineService(final Service service, final String path, final boolean bindPrefix) {
    return new ServiceBuilder(service, path, bindPrefix);
  }

  @Override
  public AddRawServicePhase defineService(final Service service, final String path, final ServiceBindingProvider provider) {
    final ServiceBuilder builder = defineService(service, path);
    provider.configureService(builder);
    builder.addService();
    return this;
  }

  @Override
  public AddRawServicePhase defineService(final Service service, final String path, final boolean bindPrefix, final ServiceBindingProvider provider) {
    final ServiceBuilder builder = defineService(service, path, bindPrefix);
    provider.configureService(builder);
    builder.addService();
    return this;
  }

  @Override
  public AddServiceFromContextPhase defineServiceFromContext(final String ctxName, final Class serviceType,
                                                             final String path, final ContextBasedServiceBindingProvider provider) {
    return defineServiceFromContext(ctxName, serviceType, path, true, provider);
  }

  @Override
  public AddServiceFromContextPhase defineServiceFromContext(final String ctxName, final Class serviceType,
                                                             final String path, final boolean bindPrefix, final ContextBasedServiceBindingProvider provider) {
    final Service service = ctx.getBean(ctxName, serviceType);
    final ServiceBuilder builder = new ServiceBuilder(service, path, bindPrefix);
    provider.configureService(builder);
    builder.addService();
    return this;
  }

  @Override
  public ServerBuilder addStaticPath(final String path) {
    if (!staticFolders.contains(path)) {
      this.staticFolders.add(path);
    }

    return this;
  }

  @Override
  public ChooseServiceCreationTypePhase configureStaticResources(final StaticResourcesProvider provider) {
    provider.configureResources(this);
    return this;
  }

  @Override
  public ServerBuilder addStaticResource(final String mapping, final String location) {
    addStaticPath(location);
    this.staticResources.put(mapping, location);
    return this;
  }

  @Override
  public ServerBuilder addStaticMapping(final String virtualPath, final String realPath) {
    staticMappings.put(virtualPath, realPath);
    return this;
  }

  @Override
  public ChooseServiceCreationTypePhase setContextPath(final String contextPath, final String appName) {
    this.contextPath = contextPath;
    this.appName = appName;
    this.registry.setContextPath(contextPath);
    return this;
  }

  @Override
  public ChooseServiceCreationTypePhase setContextPath(final String contextPath) {
    return setContextPath(contextPath, extractAppName(contextPath));
  }

  private static String extractAppName(final String contextPath) {
    if (contextPath.startsWith("/"))
      return contextPath.substring(1);

    return contextPath;
  }

  @Override
  public ExtraParamsPhase setMaxContentLength(final int maxContentLength) {
    this.maxContentLength = maxContentLength;
    return this;
  }

  @Override
  public ExtraParamsPhase setRequestTimeout(final long timeout, final TimeUnit unit) {
    this.requestTimeoutMs = unit.toMillis(timeout);
    return this;
  }

  private static void registerServices(final List serviceDescriptors, final ServiceRegistry registry,
                                       final Executor executorService) {
    for (final ServiceDescriptor desc: serviceDescriptors) {
      if (desc.endpointsBinding != null) {
        registry.register(desc.name, desc.service, desc.endpointsBinding, desc.bindPrefix, executorService);
      } else {
        registry.register(desc.name, desc.service,
            desc.asyncFilters, desc.syncFilters, desc.streamFilters,
            desc.bindPrefix, executorService);
      }
    }
  }

  @Override
  public Server build() {
    final ChannelGroup activeChannels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    final SyncRequestQueueObserver queueObserver = new SyncRequestQueueObserver(activeChannels, metricFactory);
    Executor executorService = null;
    if (threadPoolConfig != null) {
      executorService = createExecutorService(threadPoolConfig, queueObserver);
    }

    registerServices(serviceDescriptors, registry, executorService);
    final StaticPathResolver staticResolver = new StaticPathResolver(contextPath, staticFolders, staticMappings, staticResources);

    final NettyServer server = new NettyServer(port, registry, marshallerRegistry, staticResolver, queueObserver, activeChannels, contextPath,
            appName, acceptKeepAlive, supportZip, metricFactory, maxContentLength, requestTimeoutMs);
    server.addListeners(listeners);
    return server;
  }

  public class ServiceBuilder implements ContextBasedServiceBuilderPhase, RawServiceBuilderPhase {
    public final Service service;
    public final String name;
    public final boolean bindPrefix;
    private final Map> endpointsBinding;

    public ServiceBuilder(final Service service, final String name, final boolean bindPrefix) {
      this.service = service;
      this.name = name;
      this.bindPrefix = bindPrefix;
      this.endpointsBinding = new HashMap<>();
    }

    public ServerBuilder addService() {
      serviceDescriptors.add(new ServiceDescriptor(name, service,
          null, null, null, endpointsBinding, bindPrefix));

      return ServerBuilder.this;
    }

    @Override
    public ServiceBuilder addEndpoint(final String methodName, final String path, final ServiceFilter... filters) {
      return addEndpoint(HttpRequestMethodType.ANY, methodName, path, filters);
    }

    @Override
    public ServiceBuilder addEndpoint(final HttpRequestMethodType requestMethodType, final String methodName, final String path, final ServiceFilter... filters) {
      final List filtersList = filters != null ? Arrays.asList(filters) : null;
      return addEndpoint(requestMethodType, methodName, path, filtersList);
    }

    private ServiceBuilder addEndpoint(final HttpRequestMethodType requestMethodType, final String methodName, final String path, final List filters) {
      final Method[] methods = service.getClass().getDeclaredMethods();

      Method method = null;

      for (final Method m : methods) {
        if (Modifier.isPublic(m.getModifiers()) && !Modifier.isStatic(m.getModifiers())) {
          if (m.getName().equals(methodName)) {
            method = m;
            break;
          }
        }
      }

      if (method == null) {
        throw new RuntimeException("Method: " + methodName + " was not found or is not a proper service method");
      }

      final Map endpointDescriptors;

      if (endpointsBinding.containsKey(path)) {
        endpointDescriptors = endpointsBinding.get(path);
      } else {
        endpointDescriptors = new HashMap<>();
        endpointsBinding.put(path, endpointDescriptors);
      }

      endpointDescriptors.put(requestMethodType, new ServiceRegistry.EndpointDescriptor(method, filters, requestMethodType));

      return this;
    }

    @Override
    public ContextBasedServiceBuilderPhase addEndpoint(final String methodName, final String path) {
      return addEndpoint(HttpRequestMethodType.ANY, methodName, path);
    }

    @Override
    public ContextBasedServiceBuilderPhase addEndpoint(final HttpRequestMethodType requestMethodType, final String methodName, final String path) {
      return addEndpoint(requestMethodType, methodName, path, new ServiceFilter[0]);
    }

    @Override
    public ContextBasedServiceBuilderPhase addEndpoint(final String methodName, final String path, final String ctxName, final Class filterType) {
      return addEndpoint(HttpRequestMethodType.ANY, methodName, path, ctxName, filterType);
    }

    @Override
    public ContextBasedServiceBuilderPhase addEndpoint(final HttpRequestMethodType requestMethodType, final String methodName, final String path, final String ctxName,
                                                       final Class filterType) {
      final ServiceFilter filter = ctx.getBean(ctxName, filterType);
      return addEndpoint(requestMethodType, methodName, path, filter);
    }

    @Override
    public ContextBasedServiceBuilderPhase addEndpoint(final String methodName, final String path, final String ctxName,
                                                       final List> filtersType) {
      return addEndpoint(HttpRequestMethodType.ANY, methodName, path, ctxName, filtersType);
    }

    @Override
    public ContextBasedServiceBuilderPhase addEndpoint(final HttpRequestMethodType requestMethodType, final String methodName, final String path, final String ctxName,
                                                       final List> filtersType) {
      final List filters = new ArrayList<>();
      for (final Class filterType : filtersType) {
        final ServiceFilter filter = ctx.getBean(ctxName, filterType);
        filters.add(filter);
      }

      return addEndpoint(requestMethodType, methodName, path, filters);
    }
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy