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

com.caucho.v5.web.webapp.WebAppBuilder Maven / Gradle / Ivy

There is a newer version: 1.0.1
Show newest version
/*
 * Copyright (c) 1998-2015 Caucho Technology -- all rights reserved
 *
 * This file is part of Baratine(TM)
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Baratine is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Baratine is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Baratine; if not, write to the
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.v5.web.webapp;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.inject.Provider;

import com.caucho.v5.amp.Amp;
import com.caucho.v5.amp.ServiceRefAmp;
import com.caucho.v5.amp.ServicesAmp;
import com.caucho.v5.amp.journal.JournalFactoryAmp;
import com.caucho.v5.amp.manager.InjectAutoBindService;
import com.caucho.v5.amp.spi.InboxAmp;
import com.caucho.v5.amp.spi.OutboxAmp;
import com.caucho.v5.amp.spi.ServiceManagerBuilderAmp;
import com.caucho.v5.amp.vault.StubGeneratorVault;
import com.caucho.v5.config.Configs;
import com.caucho.v5.config.inject.BaratineProducer;
import com.caucho.v5.http.dispatch.InvocationRouter;
import com.caucho.v5.http.websocket.WebSocketManager;
import com.caucho.v5.inject.InjectorAmp;
import com.caucho.v5.inject.InjectorAmp.InjectBuilderAmp;
import com.caucho.v5.inject.type.TypeRef;
import com.caucho.v5.loader.EnvironmentClassLoader;
import com.caucho.v5.util.L10N;
import com.caucho.v5.web.builder.IncludeWebAmp;
import com.caucho.v5.web.builder.WebBuilderAmp;
import com.caucho.v5.web.webapp.FilterFactory.BeanFactoryAnn;
import com.caucho.v5.web.webapp.FilterFactory.BeanFactoryClass;

import io.baratine.config.Config;
import io.baratine.config.Config.ConfigBuilder;
import io.baratine.convert.Convert;
import io.baratine.inject.Binding;
import io.baratine.inject.InjectionPoint;
import io.baratine.inject.Injector;
import io.baratine.inject.Injector.BindingBuilder;
import io.baratine.inject.Injector.InjectAutoBind;
import io.baratine.inject.Injector.InjectorBuilder;
import io.baratine.inject.Key;
import io.baratine.service.Service;
import io.baratine.service.ServiceRef;
import io.baratine.service.Services;
import io.baratine.vault.Vault;
import io.baratine.web.CrossOrigin;
import io.baratine.web.HttpMethod;
import io.baratine.web.IncludeWeb;
import io.baratine.web.InstanceBuilder;
import io.baratine.web.OutBuilder;
import io.baratine.web.RequestWeb;
import io.baratine.web.RouteBuilder;
import io.baratine.web.ServiceWeb;
import io.baratine.web.ServiceWebSocket;
import io.baratine.web.ViewWeb;
import io.baratine.web.WebBuilder;
import io.baratine.web.WebSocket;
import io.baratine.web.WebSocketBuilder;
import io.baratine.web.WebSocketClose;

/**
 * Baratine's web-app instance builder
 */
public class WebAppBuilder
  implements WebBuilderAmp
{
  private static final L10N L = new L10N(WebAppBuilder.class);
  private static final Logger log
    = Logger.getLogger(WebAppBuilder.class.getName());

  private static final Predicate TRUE = x->true;

  private static final Map> _methodMap;

  private final HttpBaratine _http;
  private EnvironmentClassLoader _classLoader;

  private ArrayList _routes = new ArrayList<>();
  private ArrayList> _views = new ArrayList<>();

  private Throwable _configException;

  //private ServiceManagerBuilder _serviceBuilder;

  private InjectBuilderAmp _injectBuilder;

  private WebAppFactory _factory;

  private ServiceManagerBuilderAmp _serviceBuilder;
  private ConfigBuilder _configBuilder;
  private WebAppAutoBind _autoBind;
  private WebApp _webApp;
  private WebSocketManager _wsManager;


  /**
   * Creates the host with its environment loader.
   */
  public WebAppBuilder(WebAppFactory factory)
  {
    Objects.requireNonNull(factory);

    _factory = factory;

    _http = factory.http();

    _classLoader = EnvironmentClassLoader.create(_http.classLoader(),
                                                 factory.id());

    Thread thread = Thread.currentThread();
    ClassLoader loader = thread.getContextClassLoader();

    OutboxAmp outbox = OutboxAmp.current();
    Object oldContext = null;

    if (outbox != null) {
      oldContext = outbox.context();
    }

    try {
      thread.setContextClassLoader(classLoader());

      _configBuilder = Configs.config();
      _configBuilder.add(factory.config());

      _injectBuilder = InjectorAmp.manager(classLoader());

      _injectBuilder.include(BaratineProducer.class);

      _serviceBuilder = ServicesAmp.newManager();
      _serviceBuilder.name("webapp");
      _serviceBuilder.autoServices(true);
      _serviceBuilder.injectManager(()->_injectBuilder.get());
      //_serviceBuilder.setJournalFactory(new JournalFactoryImpl());
      addJournalFactory(_serviceBuilder);
      addStubVault(_serviceBuilder);
      _serviceBuilder.contextManager(true);

      ServicesAmp serviceManager = _serviceBuilder.get();
      Amp.contextManager(serviceManager);

      _injectBuilder.autoBind(new InjectAutoBindService(serviceManager));

      if (outbox != null) {
        InboxAmp inbox = serviceManager.inboxSystem();
        // XXX: should set the inbox
        outbox.getAndSetContext(inbox);
        //System.out.println("OUTBOX-a: " + inbox + " " + serviceManager);
      }

      _wsManager = webSocketManager();

      new WebApp(this);
    } catch (Throwable e) {
      e.printStackTrace();
      log.log(Level.WARNING, e.toString(), e);

      configException(e);

      _webApp = new WebAppBaratineError(this);
    } finally {
      thread.setContextClassLoader(loader);

      if (outbox != null) {
        outbox.getAndSetContext(oldContext);
      }
    }
  }

  private static void addJournalFactory(ServiceManagerBuilderAmp builder)
  {
    try {
      JournalFactoryAmp factory;

      Class journal = Class.forName("com.caucho.v5.amp.journal.JournalFactoryImpl");

      factory = (JournalFactoryAmp) journal.newInstance();

      builder.journalFactory(factory);
    } catch (Exception e) {
      log.finer(e.toString());
    }
  }

  protected void addStubVault(ServiceManagerBuilderAmp builder)
  {
    try {
      StubGeneratorVault gen = new StubGeneratorVault();

      builder.stubGenerator(gen);
    } catch (Exception e) {
      log.finer(e.toString());
    }
  }

  public WebSocketManager webSocketManager()
  {
    return _wsManager;
  }

  // @Override
  private void build()
  {
    Thread thread = Thread.currentThread();
    ClassLoader loader = thread.getContextClassLoader();
    OutboxAmp outbox = OutboxAmp.current();
    Object context = outbox.getAndSetContext(null);

    try {
      thread.setContextClassLoader(classLoader());
      
      /*
      if (configException() == null) {
        return new WebAppBaratine(this).start();
      }
      else {
        return new WebAppBaratineError(this).start();
      }
      */
    } catch (Throwable e) {
      log.log(Level.WARNING, e.toString(), e);

      configException(e);

      _webApp = new WebAppBaratineError(this);
    } finally {
      thread.setContextClassLoader(loader);
      outbox.getAndSetContext(context);
    }
  }

  void build(WebApp webApp)
  {
    Objects.requireNonNull(webApp);

    _webApp = webApp;

    _autoBind = new WebAppAutoBind(webApp);
    _injectBuilder.autoBind(_autoBind);
    
    _injectBuilder.provider(()->webApp.config()).to(Config.class);
    _injectBuilder.provider(()->webApp.inject()).to(Injector.class);
    _injectBuilder.provider(()->webApp.serviceManager()).to(Services.class);

    generateFromFactory();

    // defaults
    get("/**").to(WebStaticFile.class);
    
    _injectBuilder.get();
    ServicesAmp serviceManager = _serviceBuilder.start();
  }

  //@Override
  public String id()
  {
    return _factory.id();
  }

  public String path()
  {
    return _factory.path();
  }

  public EnvironmentClassLoader classLoader()
  {
    return _classLoader;
  }
  
  /*
  public WebAppBaratineHttp getWebAppHttp()
  {
    return _webAppHttp;
  }
  */

  //
  // deployment
  //

  Config config()
  {
    return _configBuilder.get();
  }

  InjectBuilderAmp injectBuilder()
  {
    return _injectBuilder;
  }

  ServiceManagerBuilderAmp serviceBuilder()
  {
    ServiceManagerBuilderAmp builder = _serviceBuilder;

    return builder;
  }

  private void configException(Throwable e)
  {
    if (_configException == null) {
      log.log(Level.FINER, e.toString(), e);

      _configException = e;
    }
    else {
      log.log(Level.FINER, e.toString(), e);
    }
  }

  //@Override
  public Throwable configException()
  {
    return _configException;
  }

  private void generateFromFactory()
  {
    for (IncludeWebAmp include : _factory.includes()) {
      include.build(this);
    }
  }

  /**
   * Builds the web-app's router
   */
  public InvocationRouter buildRouter(WebApp webApp)
  {
    // find views

    InjectorAmp inject = webApp.inject();

    for (Binding binding : inject.bindings(ViewWeb.class)) {
      try {
        ViewWeb view = (ViewWeb) binding.provider().get();

        Key> key = (Key) binding.key();

        view(view, key);
      } catch (Exception e) {
        log.log(Level.FINE, e.toString(), e);
      }
    }

    ArrayList mapList = new ArrayList<>();

    ServicesAmp manager = webApp.serviceManager();

    ServiceRefAmp serviceRef = manager.newService(new RouteService()).ref();
    
    while (_routes.size() > 0) {
      ArrayList routes = new ArrayList<>(_routes);
      _routes.clear();

      for (RouteWebApp route : routes) {
        mapList.addAll(route.toMap(inject, serviceRef));
      }
    }

    /*
    for (RouteConfig config : _routeList) {
      RouteBaratine route = config.buildRoute(); 
      
      mapList.add(new RouteMap("", route));
    }
    */

    RouteMap []routeArray = new RouteMap[mapList.size()];

    mapList.toArray(routeArray);

    return new InvocationRouterWebApp(webApp, routeArray);
  }

  /**
   * Dummy to own the service.
   */
  private class RouteService
  {
  }

  @Override
  public WebBuilder include(Class type)
  {
    System.out.println("ROUTER: " + type);
    IncludeWeb gen = (IncludeWeb) injector().instance(type);

    System.out.println("GEN: " + gen);

    return this;
  }

  //
  // inject
  //

  @Override
  public  BindingBuilder bean(Class type)
  {
    return _injectBuilder.bean(type);
  }

  @Override
  public  BindingBuilder bean(T bean)
  {
    return _injectBuilder.bean(bean);
  }

  @Override
  public  WebBuilderAmp bean(Key keyParent, Method method)
  {
    // XXX: should be key instead of supplier?
    
    _injectBuilder.include(keyParent, method);

    return this;
  }

  @Override
  public  BindingBuilder beanProvider(Provider provider)
  {
    return _injectBuilder.provider(provider);
  }

  /*
  @Override
  public  BindingBuilder beanFunction(Function function)
  {
    return _injectBuilder.function(function);
  }
  */

  /*
  @Override
  public  BindingBuilder provider(Key parent, Method m)
  {
    return _injectBuilder.provider(parent, m);
  }
  */

  /*
  @Override
  public  BindingBuilder bean(T bean)
  {
    return _injectBuilder.bean(bean);
  }
  */

  @Override
  public  Convert converter(Class source, Class target)
  {
    return _injectBuilder.get().converter(source, target);
  }

  @Override
  public  ServiceRef.ServiceBuilder service(Class type)
  {
    if (Vault.class.isAssignableFrom(type)) {
      addAssetConverter(type);
    }

    ServiceRef.ServiceBuilder builder;
    
    /*
    if (_webApp != null && _webApp.serviceManager() != null) {
      builder = _webApp.serviceManager().newService(type).addressAuto();
    }
    else {
      builder = _serviceBuilder.service(type);
    }
    */
    builder = _serviceBuilder.service(type);

    return builder;
  }

  @Override
  public ServiceRef.ServiceBuilder service(Key key, Class api)
  {
    ServiceRef.ServiceBuilder builder = _serviceBuilder.service(key, api);

    if (Vault.class.isAssignableFrom(api)) {
      addAssetConverter(api);
    }

    return builder;
  }

  private void addAssetConverter(Class api)
  {
    TypeRef resourceRef =  TypeRef.of(api).to(Vault.class);
    Class idType = resourceRef.param(0).rawClass();
    Class itemType = resourceRef.param(1).rawClass();

    Service service = api.getAnnotation(Service.class);

    String address = "";

    if (service != null) {
      address = service.value();
    }

    if (address.isEmpty()) {
      address = "/" + itemType.getSimpleName();
    }

    TypeRef convertRef = TypeRef.of(Convert.class, String.class, itemType);

    Convert convert
       = new ConvertAsset(address, itemType);

    bean(convert).to(Key.of(convertRef.type()));
  }

  @Override
  public  ServiceRef.ServiceBuilder service(Class type,
                                               Supplier supplier)
  {
    ServiceRef.ServiceBuilder builder = _serviceBuilder.service(type, supplier);

    return builder;
  }

  @Override
  public RouteBuilder route(HttpMethod method, String path)
  {
    RoutePath route = new RoutePath(method, path);

    _routes.add(route);

    return route;
  }

  @Override
  public WebSocketBuilder websocket(String path)
  {
    WebSocketPath route = new WebSocketPath(path);

    _routes.add(route);

    return route;
  }

  //
  // views
  //

  @Override
  public  WebBuilder view(ViewWeb view)
  {
    _views.add(new ViewRef<>(view));

    return this;
  }

  @Override
  public  WebBuilder view(Class> viewType)
  {
    ViewWeb view = injector().instance(viewType);

    return view(view, Key.of(viewType));
  }

  private  WebBuilder view(ViewWeb view, Key key)
  {
    _views.add(new ViewRef<>(view, key.type()));

    return this;
  }

  List> views()
  {
    return _views;
  }

  BodyResolver bodyResolver()
  {
    return new BodyResolverBase();
  }

  @Override
  public InjectorAmp injector()
  {
    return _injectBuilder.get();
  }

  @Override
  public InjectorBuilder autoBind(InjectAutoBind autoBind)
  {
    throw new UnsupportedOperationException();
  }

  // @Override
  public WebApp get()
  {
    Thread thread = Thread.currentThread();
    ClassLoader loader = thread.getContextClassLoader();
    OutboxAmp outbox = OutboxAmp.current();
    Object context = outbox.getAndSetContext(null);

    try {
      thread.setContextClassLoader(classLoader());

      if (configException() == null) {
        return _webApp.start();
      }
      else {
        return new WebAppBaratineError(this).start();
      }

    } catch (Throwable e) {
      log.log(Level.WARNING, e.toString(), e);

      configException(e);

      return new WebAppBaratineError(this);
    } finally {
      thread.setContextClassLoader(loader);
      outbox.getAndSetContext(context);
    }
  }

  @Override
  public String toString()
  {
    return getClass().getSimpleName() + "[" + id() + "]";
  }

  private class WebAppAutoBind implements InjectAutoBind
  {
    private WebApp _webApp;

    WebAppAutoBind(WebApp webApp)
    {
      _webApp = webApp;
    }

    @Override
    public  Provider provider(Injector manager, Key key)
    {
      Class rawClass = key.rawClass();

      return null;
    }

  }

  /**
   * RouteWebApp is a program for routes in the web-app.
   *
   */
  static interface RouteWebApp
  {
    List toMap(InjectorAmp inject, ServiceRefAmp serviceRef);
  }

  /**
   * RoutePath has a path pattern for a route.
   */
  class RoutePath implements RouteBuilderAmp, RouteWebApp, OutBuilder
  {
    private HttpMethod _method;
    private String _path;
    private ServiceWeb _service;
    
    private ArrayList> _filtersBefore
      = new ArrayList<>();
    private ArrayList> _filtersAfter
    = new ArrayList<>();
    private Class _serviceClass;
    
    private ViewRef _viewRef;

    RoutePath(HttpMethod method, String path)
    {
      _method = method;
      _path = path;
    }
    
    @Override
    public WebBuilderAmp webBuilder()
    {
      return WebAppBuilder.this;
    }
    
    @Override
    public String path()
    {
      return _path;
    }
    
    @Override
    public HttpMethod method()
    {
      return _method;
    }

    @Override
    public RoutePath after(Class filterClass)
    {
      Objects.requireNonNull(filterClass);

      _filtersAfter.add(new BeanFactoryClass<>(filterClass));

      return this;
    }

    @Override
    public 
    RoutePath after(X ann, InjectionPoint ip)
    {
      Objects.requireNonNull(ann);
      Objects.requireNonNull(ip);
      
      _filtersAfter.add(new BeanFactoryAnn<>(ServiceWeb.class, ann, ip));

      return this;
    }

    @Override
    public RoutePath before(Class filterClass)
    {
      Objects.requireNonNull(filterClass);

      _filtersBefore.add(new BeanFactoryClass<>(filterClass));

      return this;
    }

    @Override
    public 
    RoutePath before(X ann, InjectionPoint ip)
    {
      Objects.requireNonNull(ann);
      Objects.requireNonNull(ip);
      
      _filtersBefore.add(new BeanFactoryAnn<>(ServiceWeb.class, ann, ip));

      return this;
    }

    @Override
    public OutBuilder to(ServiceWeb service)
    {
      Objects.requireNonNull(service);

      _service = service;

      return this;
    }

    @Override
    public OutBuilder to(Class serviceClass)
    {
      Objects.requireNonNull(serviceClass);

      _serviceClass = serviceClass;

      return this;
    }

    @Override
    public  OutBuilder view(ViewWeb view)
    {
      Objects.requireNonNull(view);

      _viewRef = new ViewRef(view);

      return this;
    }

    @Override
    public List toMap(InjectorAmp injector,
                          ServiceRefAmp serviceRef)
    {
      ArrayList> views = new ArrayList<>();

      if (_viewRef != null) {
        views.add(_viewRef);
      }

      views.addAll(views());
      
      ArrayList filtersBefore = new ArrayList<>();
      
      for (FilterFactory filterFactory : _filtersBefore) {
        ServiceWeb filter = filterFactory.apply(this);

        if (filter != null) {
          filtersBefore.add(filter);
        }
        else {
          log.warning(L.l("{0} is an unknown filter", filterFactory));
        }
      }
      
      ArrayList filtersAfter = new ArrayList<>();
      
      for (FilterFactory filterFactory : _filtersAfter) {
        ServiceWeb filter = filterFactory.apply(this);

        if (filter != null) {
          filtersAfter.add(filter);
        }
        else {
          log.warning(L.l("{0} is an unknown filter", filterFactory));
        }
      }

      if (_viewRef != null) {
        views.add(_viewRef);
      }

      views.addAll(views());

      ServiceWeb service;

      if (_service != null) {
        service = _service;
      }
      else if (_serviceClass != null) {
        service = injector.instance(_serviceClass);
      }
      else {
        throw new IllegalStateException();
      }

      RouteApply routeApply;

      HttpMethod method = _method;

      if (method == null) {
        method = HttpMethod.UNKNOWN;
      }

      Predicate test = _methodMap.get(method);

      routeApply = new RouteApply(service, filtersBefore, filtersAfter, serviceRef, test, views);

      List list = new ArrayList<>();
      list.add(new RouteMap(_path, routeApply));

      /*
      CrossOrigin crossOrigin = service.getCrossOrigin();

      if (crossOrigin != null) {
        list.add(crossOriginRouteMap(crossOrigin));
      }
      */

      return list;
    }

    /*
    private RouteMap crossOriginRouteMap(CrossOrigin crossOrigin)
    {
      Predicate options = _methodMap.get(HttpMethod.OPTIONS);

      RouteCrossOrigin corsRoute
        = new RouteCrossOrigin(options, _method, crossOrigin);

      return new RouteMap(_path, corsRoute);
    }
  */
  }

  /**
   * WebSocketPath is a route to a websocket service.
   */
  class WebSocketPath implements WebSocketBuilder, RouteWebApp
  {
    private String _path;

    private Supplier> _serviceFactory;
    private Class> _serviceType;

    WebSocketPath(String path)
    {
      _path = path;
    }

    @Override
    public  void to(ServiceWebSocket service)
    {
      _serviceFactory = ()->service;
    }

    @Override
    public  InstanceBuilder>
    to(Class> type)
    {
      _serviceType = type;

      return null;
    }

    @Override
    public  void to(Supplier> supplier)
    {
      _serviceFactory = supplier;
    }

    @Override
    public List toMap(InjectorAmp inject,
                                ServiceRefAmp serviceRef)
    {
      Function> fun = null;
      Supplier> supplier = _serviceFactory;
      
      ServiceWebSocket service = null;
      
      Class type = null;

      if (supplier != null) {
        service = supplier.get();
        Objects.requireNonNull(service);
      }
      else if (_serviceType == null) {
        throw new IllegalStateException();
      }
      else {
        Service serviceAnn = _serviceType.getAnnotation(Service.class);
        
        type = itemType(_serviceType);
        
        if (serviceAnn == null) {
          service = inject.instance(_serviceType);
        }
        else {
          ServiceRef ref = service(_serviceType).addressAuto().ref();

          if (serviceAnn.value().startsWith("session:")) {
            fun = req->req.session(_serviceType);
          }
          else {
            fun = req->req.service(_serviceType);
          }
        }
      }
      
      if (fun == null) {
        Objects.requireNonNull(service);
      
        type = itemType(service.getClass());
        
        ServiceWebSocket serviceWs;

        /*
        serviceWs = serviceRef.pin(new WebSocketWrapper<>(service))
                             .as(ServiceWebSocket.class);
                             */
        serviceWs = serviceRef.pin(service).as(ServiceWebSocket.class);
      
        fun = req->serviceWs;
      }

      WebSocketApply routeApply = new WebSocketApply(fun, type);

      List list = new ArrayList<>();
      list.add(new RouteMap(_path, routeApply));

      return list;
    }
    
    private Class itemType(Class serviceClass)
    {
      TypeRef typeRef = TypeRef.of(serviceClass);
      TypeRef typeRefService = typeRef.to(ServiceWebSocket.class);
      TypeRef typeService = typeRefService.param(0);

      Class type;
      if (typeService != null) {
        type = typeService.rawClass();
      }
      else {
        type = String.class;
      }
      
      return type;
    }
  }

  private static class MethodPredicate implements Predicate {
    private HttpMethod _method;

    MethodPredicate(HttpMethod method)
    {
      Objects.requireNonNull(method);

      _method = method;
    }

    public boolean test(RequestWeb request)
    {
      return _method.name().equals(request.method());
    }
  }

  private static class MethodGet implements Predicate {
    @Override
    public boolean test(RequestWeb request)
    {
      return "GET".equals(request.method()) || "HEAD".equals(request.method());
    }
  }

  static class ConvertAsset implements Convert
  {
    private Services _manager;
    private String _address;
    private Class _itemType;

    ConvertAsset(String address, Class itemType)
    {
      if (! address.endsWith("/")) {
        address = address + "/";
      }

      _address = address;
      _itemType = itemType;
    }

    @Override
    public T convert(String key)
    {
      return manager().service(_address + key).as(_itemType);
    }

    private Services manager()
    {
      if (_manager == null) {
        _manager = Services.current();
      }

      return _manager;
    }
  }
  
  static final class WebSocketWrapper
    implements ServiceWebSocket
  {
    private ServiceWebSocket _service;
    
    /*
    WebSocketWrapper(ServiceWebSocket service)
    {
      _service = service;
    }
    */

    @Override
    public void open(WebSocket webSocket)
    {
      try {
        // XXX: convert to async
        _service.open(webSocket); 
      } catch (Throwable e) {
        e.printStackTrace();
        System.out.println("FAIL: " + e + " " + webSocket);
        webSocket.fail(e);
      }
    }
    
    @Override
    public void next(T value, WebSocket webSocket)
      throws IOException
    {
      _service.next(value, webSocket);
    }
    
    @Override
    public void ping(String value, WebSocket webSocket)
      throws IOException
    {
      _service.ping(value, webSocket);
    }
    
    @Override
    public void pong(String value, WebSocket webSocket)
      throws IOException
    {
      _service.pong(value, webSocket);
    }
    
    @Override
    public void close(WebSocketClose code, String msg, 
                      WebSocket webSocket)
      throws IOException
    {
      _service.close(code, msg, webSocket);
    }
  }

  static {
    _methodMap = new HashMap<>();

    for (HttpMethod method : HttpMethod.values()) {
      _methodMap.put(method, new MethodPredicate(method));
    }

    _methodMap.put(HttpMethod.GET, new MethodGet());
    _methodMap.put(HttpMethod.UNKNOWN, TRUE);
  }
}