/**
 * Client app, which tests deployed ADD-service.
 *
 * @author Alexey Stashok
 */
@SuppressWarnings("unchecked")
public class AddClient {
  private static final Logger LOGGER = Grizzly.logger(PUServer.class);

  public static void main(String[] args) throws Exception {
    Connection connection = null;

    // Construct the client filter chain
    final FilterChainBuilder puFilterChainBuilder =
        FilterChainBuilder.stateless()
            // Add TransportFilter
            .add(new TransportFilter())
            // Add ADD-service message parser/serializer
            .add(new AddClientMessageFilter())
            // Add Result reporter Filter
            .add(new ResultFilter());

    // Construct TCPNIOTransport
    final TCPNIOTransport transport = TCPNIOTransportBuilder.newInstance().build();
    transport.setProcessor(puFilterChainBuilder.build());

    try {
      // Start
      transport.start();

      // Create the client connection
      final Future<Connection> connectFuture = transport.connect("localhost", PUServer.PORT);
      connection = connectFuture.get(10, TimeUnit.SECONDS);

      LOGGER.info("Enter 2 numbers separated by space (<value1> <value2>) end press <enter>.");
      LOGGER.info("Type q and enter to exit.");

      // Read user input and communicate the ADD-service
      String line;
      BufferedReader reader =
          new BufferedReader(new InputStreamReader(System.in, Charsets.ASCII_CHARSET));
      while ((line = reader.readLine()) != null) {
        if ("q".equals(line)) {
          break;
        }

        // Parse user input
        final int value1;
        final int value2;
        try {
          final String[] values = line.split(" ");

          value1 = Integer.parseInt(values[0].trim());
          value2 = Integer.parseInt(values[1].trim());
        } catch (Exception e) {
          LOGGER.warning("Bad format, repeat pls");
          continue;
        }

        // send the request to ADD-service
        final GrizzlyFuture<WriteResult> writeFuture =
            connection.write(new AddRequestMessage(value1, value2));

        final WriteResult result = writeFuture.get(10, TimeUnit.SECONDS);
        assert result != null;
      }

    } finally {
      // Close the client connection
      if (connection != null) {
        connection.closeSilently();
      }

      // Shutdown the transport
      transport.shutdownNow();
    }
  }

  // Simple reporting Filter
  private static final class ResultFilter extends BaseFilter {

    @Override
    public NextAction handleRead(FilterChainContext ctx) throws IOException {
      // Take ADD-service response
      final AddResponseMessage addResponseMessage = ctx.getMessage();

      // do output
      LOGGER.log(Level.INFO, "Result={0}", addResponseMessage.getResult());

      return ctx.getStopAction();
    }
  }
}
/**
 * Generic {@link GrizzlyListener} implementation, which is not HTTP dependent, and can support any
 * Transport configuration, based on {@link FilterChain}.
 *
 * @author Alexey Stashok
 */
public class GenericGrizzlyListener implements GrizzlyListener {
  /** The logger to use for logging messages. */
  private static final Logger LOGGER = Grizzly.logger(GenericGrizzlyListener.class);

  protected volatile String name;
  protected volatile InetAddress address;
  protected volatile int port;
  protected NIOTransport transport;
  protected FilterChain rootFilterChain;
  private volatile ExecutorService workerExecutorService;
  private volatile ExecutorService auxExecutorService;
  private volatile DelayedExecutor delayedExecutor;
  private volatile long transactionTimeoutMillis = -1;

  // ajp enabled flag
  protected volatile boolean isAjpEnabled;
  // spdy enabled flag
  protected volatile boolean isSpdyEnabled;
  // websocket enabled flag
  protected volatile boolean isWebSocketEnabled;
  // comet enabled flag
  protected volatile boolean isCometEnabled;

  @Override
  public String getName() {
    return name;
  }

  protected final void setName(String name) {
    this.name = name;
  }

  @Override
  public InetAddress getAddress() {
    return address;
  }

  protected final void setAddress(InetAddress inetAddress) {
    address = inetAddress;
  }

  @Override
  public int getPort() {
    return port;
  }

  protected void setPort(int port) {
    this.port = port;
  }

  @Override
  public void start() throws IOException {
    startDelayedExecutor();
    ((SocketBinder) transport).bind(new InetSocketAddress(address, port));
    transport.start();
  }

  @Override
  public void stop() throws IOException {
    stopDelayedExecutor();
    final NIOTransport localTransport = transport;
    transport = null;
    if (localTransport != null) {
      localTransport.shutdownNow();
    }

    if (workerExecutorService != null) {
      final ExecutorService localExecutorService = workerExecutorService;
      workerExecutorService = null;
      localExecutorService.shutdownNow();
    }
    rootFilterChain = null;
  }

  @Override
  public void destroy() {}

  @Override
  public void processDynamicConfigurationChange(
      ServiceLocator habitat, PropertyChangeEvent[] events) {}

  @Override
  public <T> T getAdapter(Class<T> adapterClass) {
    return null;
  }

  public <E> List<E> getFilters(Class<E> clazz) {
    return getFilters(clazz, rootFilterChain, new ArrayList<E>(2));
  }

  public org.glassfish.grizzly.Transport getTransport() {
    return transport;
  }

  /**
   * Returns <tt>true</tt> if AJP (or JK) is enabled for this listener, or <tt>false</tt> otherwise.
   */
  public boolean isAjpEnabled() {
    return isAjpEnabled;
  }

  /** Returns <tt>true</tt> if SPDY is enabled for this listener, or <tt>false</tt> otherwise. */
  public boolean isSpdyEnabled() {
    return isSpdyEnabled;
  }

  /**
   * Returns <tt>true</tt> if WebSocket is enabled for this listener, or <tt>false</tt> otherwise.
   */
  public boolean isWebSocketEnabled() {
    return isWebSocketEnabled;
  }

  /** Returns <tt>true</tt> if Comet is enabled for this listener, or <tt>false</tt> otherwise. */
  public boolean isCometEnabled() {
    return isCometEnabled;
  }

  @SuppressWarnings({"unchecked"})
  public static <E> List<E> getFilters(Class<E> clazz, FilterChain filterChain, List<E> filters) {
    for (final Filter filter : filterChain) {
      if (clazz.isAssignableFrom(filter.getClass())) {
        filters.add((E) filter);
      }
      if (PUFilter.class.isAssignableFrom(filter.getClass())) {
        final Set<PUProtocol> puProtocols = ((PUFilter) filter).getProtocols();
        for (PUProtocol puProtocol : puProtocols) {
          getFilters(clazz, puProtocol.getFilterChain(), filters);
        }
      }
    }
    return filters;
  }

  /*
   * Configures the given grizzlyListener.
   *
   * @param networkListener The NetworkListener to configure
   */
  // TODO: Must get the information from domain.xml Config objects.
  // TODO: Pending Grizzly issue 54
  @Override
  public void configure(final ServiceLocator habitat, final NetworkListener networkListener)
      throws IOException {
    setName(networkListener.getName());
    setAddress(InetAddress.getByName(networkListener.getAddress()));
    setPort(Integer.parseInt(networkListener.getPort()));

    final FilterChainBuilder filterChainBuilder = FilterChainBuilder.stateless();

    configureTransport(networkListener, networkListener.findTransport(), filterChainBuilder);

    configureProtocol(habitat, networkListener, networkListener.findProtocol(), filterChainBuilder);

    configureThreadPool(habitat, networkListener, networkListener.findThreadPool());

    rootFilterChain = filterChainBuilder.build();
    transport.setProcessor(rootFilterChain);
  }

  protected void configureTransport(
      final NetworkListener networkListener,
      final Transport transportConfig,
      final FilterChainBuilder filterChainBuilder) {

    final String transportClassName = transportConfig.getClassname();
    if (TCPNIOTransport.class.getName().equals(transportClassName)) {
      transport = configureTCPTransport(transportConfig);
    } else if (UDPNIOTransport.class.getName().equals(transportClassName)) {
      transport = configureUDPTransport();
    } else {
      throw new GrizzlyConfigException("Unsupported transport type " + transportConfig.getName());
    }

    String selectorName = transportConfig.getSelectionKeyHandler();
    if (selectorName != null) {
      if (getSelectionKeyHandlerByName(selectorName, transportConfig) != null) {
        if (LOGGER.isLoggable(Level.INFO)) {
          LOGGER.warning(
              "Element, selection-key-handler, has been deprecated and is effectively ignored by the runtime.");
        }
      }
    }

    if (!Transport.BYTE_BUFFER_TYPE.equalsIgnoreCase(transportConfig.getByteBufferType())) {
      transport.setMemoryManager(
          new ByteBufferManager(
              true,
              AbstractMemoryManager.DEFAULT_MAX_BUFFER_SIZE,
              ByteBufferManager.DEFAULT_SMALL_BUFFER_SIZE));
    }

    final int acceptorThreads = Integer.parseInt(transportConfig.getAcceptorThreads());
    transport.setSelectorRunnersCount(acceptorThreads);

    final int readSize = Integer.parseInt(transportConfig.getSocketReadBufferSize());
    if (readSize > 0) {
      transport.setReadBufferSize(readSize);
    }

    final int writeSize = Integer.parseInt(transportConfig.getSocketWriteBufferSize());
    if (writeSize > 0) {
      transport.setWriteBufferSize(writeSize);
    }

    final ThreadPoolConfig kernelThreadPoolConfig = transport.getKernelThreadPoolConfig();

    kernelThreadPoolConfig.setPoolName(networkListener.getName() + "-kernel");
    if (acceptorThreads > 0) {
      kernelThreadPoolConfig.setCorePoolSize(acceptorThreads).setMaxPoolSize(acceptorThreads);
    }

    transport.setIOStrategy(loadIOStrategy(transportConfig.getIoStrategy()));
    transport.setNIOChannelDistributor(
        new RoundRobinConnectionDistributor(
            transport, Boolean.parseBoolean(transportConfig.getDedicatedAcceptorEnabled())));

    filterChainBuilder.add(new TransportFilter());
  }

  protected NIOTransport configureTCPTransport(final Transport transportConfig) {

    final TCPNIOTransport tcpTransport =
        configureDefaultThreadPoolConfigs(TCPNIOTransportBuilder.newInstance().build());
    tcpTransport.setTcpNoDelay(Boolean.parseBoolean(transportConfig.getTcpNoDelay()));
    tcpTransport.setLinger(Integer.parseInt(transportConfig.getLinger()));
    tcpTransport.setWriteTimeout(
        Long.parseLong(transportConfig.getWriteTimeoutMillis()), TimeUnit.MILLISECONDS);
    tcpTransport.setReadTimeout(
        Long.parseLong(transportConfig.getReadTimeoutMillis()), TimeUnit.MILLISECONDS);
    tcpTransport.setServerConnectionBackLog(
        Integer.parseInt(transportConfig.getMaxConnectionsCount()));
    return tcpTransport;
  }

  protected NIOTransport configureUDPTransport() {
    return configureDefaultThreadPoolConfigs(UDPNIOTransportBuilder.newInstance().build());
  }

  protected <T extends NIOTransport> T configureDefaultThreadPoolConfigs(final T transport) {
    transport.setKernelThreadPoolConfig(ThreadPoolConfig.defaultConfig());
    transport.setWorkerThreadPoolConfig(ThreadPoolConfig.defaultConfig());

    return transport;
  }

  protected void configureProtocol(
      final ServiceLocator habitat,
      final NetworkListener networkListener,
      final Protocol protocol,
      final FilterChainBuilder filterChainBuilder) {

    if (Boolean.valueOf(protocol.getSecurityEnabled())) {
      configureSsl(habitat, getSsl(protocol), filterChainBuilder);
    }
    configureSubProtocol(habitat, networkListener, protocol, filterChainBuilder);
  }

  protected void configureSubProtocol(
      final ServiceLocator habitat,
      final NetworkListener networkListener,
      final Protocol protocol,
      final FilterChainBuilder filterChainBuilder) {

    if (protocol.getHttp() != null) {
      final Http http = protocol.getHttp();
      configureHttpProtocol(
          habitat,
          networkListener,
          http,
          filterChainBuilder,
          Boolean.valueOf(protocol.getSecurityEnabled()));

    } else if (protocol.getPortUnification() != null) {
      // Port unification
      final PortUnification pu = protocol.getPortUnification();
      final String puFilterClassname = pu.getClassname();
      PUFilter puFilter = null;
      if (puFilterClassname != null) {
        try {
          puFilter =
              Utils.newInstance(habitat, PUFilter.class, puFilterClassname, puFilterClassname);
          configureElement(habitat, networkListener, pu, puFilter);
        } catch (Exception e) {
          LOGGER.log(
              Level.WARNING,
              "Can not initialize port unification filter: "
                  + puFilterClassname
                  + " default filter will be used instead",
              e);
        }
      }
      if (puFilter == null) {
        puFilter = new PUFilter();
      }
      List<org.glassfish.grizzly.config.dom.ProtocolFinder> findersConfig = pu.getProtocolFinder();
      for (org.glassfish.grizzly.config.dom.ProtocolFinder finderConfig : findersConfig) {
        final String finderClassname = finderConfig.getClassname();
        try {
          final ProtocolFinder protocolFinder =
              Utils.newInstance(habitat, ProtocolFinder.class, finderClassname, finderClassname);
          configureElement(habitat, networkListener, finderConfig, protocolFinder);
          final Protocol subProtocol = finderConfig.findProtocol();
          final FilterChainBuilder subProtocolFilterChainBuilder =
              puFilter.getPUFilterChainBuilder();
          // If subprotocol is secured - we need to wrap it under SSLProtocolFinder
          if (Boolean.valueOf(subProtocol.getSecurityEnabled())) {
            final PUFilter extraSslPUFilter = new PUFilter();

            final Filter addedSSLFilter =
                configureSsl(habitat, getSsl(subProtocol), subProtocolFilterChainBuilder);

            subProtocolFilterChainBuilder.add(extraSslPUFilter);
            final FilterChainBuilder extraSslPUFilterChainBuilder =
                extraSslPUFilter.getPUFilterChainBuilder();

            try {
              // temporary add SSL Filter, so subprotocol
              // will see it
              extraSslPUFilterChainBuilder.add(addedSSLFilter);
              configureSubProtocol(
                  habitat, networkListener, subProtocol, extraSslPUFilterChainBuilder);
            } finally {
              // remove SSL Filter
              extraSslPUFilterChainBuilder.remove(addedSSLFilter);
            }

            extraSslPUFilter.register(protocolFinder, extraSslPUFilterChainBuilder.build());

            puFilter.register(
                new SSLProtocolFinder(new SSLConfigurator(habitat, subProtocol.getSsl())),
                subProtocolFilterChainBuilder.build());
          } else {
            configureSubProtocol(
                habitat, networkListener, subProtocol, subProtocolFilterChainBuilder);
            puFilter.register(protocolFinder, subProtocolFilterChainBuilder.build());
          }
        } catch (Exception e) {
          LOGGER.log(
              Level.WARNING, "Can not initialize sub protocol. Finder: " + finderClassname, e);
        }
      }
      filterChainBuilder.add(puFilter);
    } else if (protocol.getHttpRedirect() != null) {
      filterChainBuilder.add(createHttpServerCodecFilter());
      final HttpRedirectFilter filter = new HttpRedirectFilter();
      filter.configure(habitat, networkListener, protocol.getHttpRedirect());
      filterChainBuilder.add(filter);
    } else {
      ProtocolChainInstanceHandler pcihConfig = protocol.getProtocolChainInstanceHandler();
      if (pcihConfig == null) {
        LOGGER.log(Level.WARNING, "Empty protocol declaration");
        return;
      }
      ProtocolChain filterChainConfig = pcihConfig.getProtocolChain();
      for (ProtocolFilter filterConfig : filterChainConfig.getProtocolFilter()) {
        final String filterClassname = filterConfig.getClassname();
        try {
          final Filter filter = loadFilter(habitat, filterConfig.getName(), filterClassname);
          configureElement(habitat, networkListener, filterConfig, filter);
          filterChainBuilder.add(filter);
        } catch (Exception e) {
          LOGGER.log(Level.WARNING, "Can not initialize protocol filter: " + filterClassname, e);
          throw new IllegalStateException("Can not initialize protocol filter: " + filterClassname);
        }
      }
    }
  }

  protected static Filter configureSsl(
      final ServiceLocator habitat, final Ssl ssl, final FilterChainBuilder filterChainBuilder) {
    final SSLEngineConfigurator serverConfig = new SSLConfigurator(habitat, ssl);
    //        final SSLEngineConfigurator clientConfig = new SSLConfigurator(habitat, ssl);
    //        clientConfig.setClientMode(true);
    final SSLBaseFilter sslFilter =
        new SSLBaseFilter(
            serverConfig,
            //                                             clientConfig,
            isRenegotiateOnClientAuthWant(ssl));
    sslFilter.setHandshakeTimeout(
        Long.parseLong(ssl.getHandshakeTimeoutMillis()), TimeUnit.MILLISECONDS);

    filterChainBuilder.add(sslFilter);
    return sslFilter;
  }

  private static boolean isRenegotiateOnClientAuthWant(final Ssl ssl) {
    return ssl == null || Boolean.parseBoolean(ssl.getRenegotiateOnClientAuthWant());
  }

  @SuppressWarnings({"unchecked"})
  private static boolean configureElement(
      ServiceLocator habitat,
      NetworkListener networkListener,
      ConfigBeanProxy configuration,
      Object instance) {

    if (instance instanceof ConfigAwareElement) {
      ((ConfigAwareElement) instance).configure(habitat, networkListener, configuration);

      return true;
    }

    return false;
  }

  protected void configureThreadPool(
      final ServiceLocator habitat,
      final NetworkListener networkListener,
      final ThreadPool threadPool) {

    final String classname = threadPool.getClassname();
    if (classname != null && !ThreadPool.DEFAULT_THREAD_POOL_CLASS_NAME.equals(classname)) {

      // Use custom thread pool
      try {
        final ExecutorService customThreadPool =
            Utils.newInstance(habitat, ExecutorService.class, classname, classname);

        if (customThreadPool != null) {
          if (!configureElement(habitat, networkListener, threadPool, customThreadPool)) {
            LOGGER.log(
                Level.INFO,
                "The ThreadPool configuration bean can not be "
                    + "passed to the custom thread-pool: {0}"
                    + " instance, because it's not {1}.",
                new Object[] {classname, ConfigAwareElement.class.getName()});
          }

          workerExecutorService = customThreadPool;
          transport.setWorkerThreadPool(customThreadPool);
          return;
        }

        LOGGER.log(Level.WARNING, "Can not initalize custom thread pool: {0}", classname);

      } catch (Throwable t) {
        LOGGER.log(Level.WARNING, "Can not initalize custom thread pool: " + classname, t);
      }
    }

    try {
      // Use standard Grizzly thread pool
      workerExecutorService =
          GrizzlyExecutorService.createInstance(
              configureThreadPoolConfig(networkListener, threadPool));
      transport.setWorkerThreadPool(workerExecutorService);
    } catch (NumberFormatException ex) {
      LOGGER.log(Level.WARNING, "Invalid thread-pool attribute", ex);
    }
  }

  protected ThreadPoolConfig configureThreadPoolConfig(
      final NetworkListener networkListener, final ThreadPool threadPool) {

    final int maxQueueSize =
        threadPool.getMaxQueueSize() == null
            ? Integer.MAX_VALUE
            : Integer.parseInt(threadPool.getMaxQueueSize());
    final int minThreads = Integer.parseInt(threadPool.getMinThreadPoolSize());
    final int maxThreads = Integer.parseInt(threadPool.getMaxThreadPoolSize());
    final int timeout = Integer.parseInt(threadPool.getIdleThreadTimeoutSeconds());
    final ThreadPoolConfig poolConfig = ThreadPoolConfig.defaultConfig();
    poolConfig.setPoolName(networkListener.getName());
    poolConfig.setCorePoolSize(minThreads);
    poolConfig.setMaxPoolSize(maxThreads);
    poolConfig.setQueueLimit(maxQueueSize);

    // we specify the classloader that loaded this class to ensure
    // we present the same initial classloader no matter what mode
    // GlassFish is being run in.
    // See http://java.net/jira/browse/GLASSFISH-19639
    poolConfig.setInitialClassLoader(this.getClass().getClassLoader());

    poolConfig.setKeepAliveTime(timeout < 0 ? Long.MAX_VALUE : timeout, TimeUnit.SECONDS);
    if (transactionTimeoutMillis > 0 && !Utils.isDebugVM()) {
      poolConfig.setTransactionTimeout(
          obtainDelayedExecutor(), transactionTimeoutMillis, TimeUnit.MILLISECONDS);
    }

    return poolConfig;
  }

  private DelayedExecutor obtainDelayedExecutor() {
    if (delayedExecutor != null) {
      return delayedExecutor;
    }

    final AtomicInteger threadCounter = new AtomicInteger();
    auxExecutorService =
        Executors.newCachedThreadPool(
            new ThreadFactory() {
              @Override
              public Thread newThread(Runnable r) {
                final Thread newThread =
                    new DefaultWorkerThread(
                        transport.getAttributeBuilder(),
                        getName() + "-expirer(" + threadCounter.incrementAndGet() + ")",
                        null,
                        r);
                newThread.setDaemon(true);
                return newThread;
              }
            });
    delayedExecutor = new DelayedExecutor(auxExecutorService);
    return delayedExecutor;
  }

  protected void startDelayedExecutor() {
    if (delayedExecutor != null) {
      delayedExecutor.start();
    }
  }

  protected void stopDelayedExecutor() {
    if (delayedExecutor != null) {
      final DelayedExecutor localDelayedExecutor = delayedExecutor;
      delayedExecutor = null;
      if (localDelayedExecutor != null) {
        localDelayedExecutor.stop();
        localDelayedExecutor.destroy();
      }
      final ExecutorService localThreadPool = auxExecutorService;
      auxExecutorService = null;
      if (localThreadPool != null) {
        localThreadPool.shutdownNow();
      }
    }
  }

  @SuppressWarnings({"deprecation"})
  protected void configureHttpProtocol(
      final ServiceLocator habitat,
      final NetworkListener networkListener,
      final Http http,
      final FilterChainBuilder filterChainBuilder,
      boolean secure) {
    transactionTimeoutMillis = Long.parseLong(http.getRequestTimeoutSeconds()) * 1000;
    filterChainBuilder.add(
        new IdleTimeoutFilter(
            obtainDelayedExecutor(), Integer.parseInt(http.getTimeoutSeconds()), TimeUnit.SECONDS));
    final org.glassfish.grizzly.http.HttpServerFilter httpServerFilter =
        createHttpServerCodecFilter(http);
    final Set<ContentEncoding> contentEncodings = configureContentEncodings(http);
    for (ContentEncoding contentEncoding : contentEncodings) {
      httpServerFilter.addContentEncoding(contentEncoding);
    }
    //        httpServerFilter.getMonitoringConfig().addProbes(
    //                serverConfig.getMonitoringConfig().getHttpConfig().getProbes());
    filterChainBuilder.add(httpServerFilter);
    final FileCache fileCache = configureHttpFileCache(http.getFileCache());
    fileCache.initialize(obtainDelayedExecutor());
    final FileCacheFilter fileCacheFilter = new FileCacheFilter(fileCache);
    //        fileCache.getMonitoringConfig().addProbes(
    //                serverConfig.getMonitoringConfig().getFileCacheConfig().getProbes());
    filterChainBuilder.add(fileCacheFilter);
    final HttpServerFilter webServerFilter =
        new HttpServerFilter(getHttpServerFilterConfiguration(http), obtainDelayedExecutor());

    final HttpHandler httpHandler = getHttpHandler();
    httpHandler.setAllowEncodedSlash(GrizzlyConfig.toBoolean(http.getEncodedSlashEnabled()));
    webServerFilter.setHttpHandler(httpHandler);
    //        webServerFilter.getMonitoringConfig().addProbes(
    //                serverConfig.getMonitoringConfig().getWebServerConfig().getProbes());
    filterChainBuilder.add(webServerFilter);

    configureSpdySupport(habitat, networkListener, http.getSpdy(), filterChainBuilder, secure);

    // TODO: evaluate comet/websocket support over SPDY.
    configureCometSupport(habitat, networkListener, http, filterChainBuilder);

    configureWebSocketSupport(habitat, networkListener, http, filterChainBuilder);

    configureAjpSupport(habitat, networkListener, http, filterChainBuilder);
  }

  protected void configureSpdySupport(
      final ServiceLocator locator,
      final NetworkListener listener,
      final Spdy spdyElement,
      final FilterChainBuilder builder,
      final boolean secure) {
    if (spdyElement != null && spdyElement.getEnabled()) {

      boolean isNpnMode =
          spdyElement.getMode() == null || "npn".equalsIgnoreCase(spdyElement.getMode());

      // Spdy without NPN is supported, but warn that there may
      // be consequences to this configuration.
      if (!secure && isNpnMode) {
        LOGGER.log(
            Level.WARNING,
            "SSL is not enabled for listener {0}.  SPDY support will be enabled, but will not be secured.  Some clients may not be able to use SPDY in this configuration.",
            listener.getName());
      }

      // first try to lookup a service appropriate for the mode
      // that has been configured.
      AddOn spdyAddon = locator.getService(AddOn.class, "spdy");

      // if no service was found, attempt to load via reflection.
      if (spdyAddon == null) {
        Class<?> spdyMode;
        try {
          spdyMode = Utils.loadClass("org.glassfish.grizzly.spdy.SpdyMode");
        } catch (ClassNotFoundException cnfe) {
          if (LOGGER.isLoggable(Level.FINE)) {
            LOGGER.fine(
                "Unable to load class org.glassfish.grizzly.spdy.SpdyMode.  SPDY support cannot be enabled");
          }
          return;
        }
        Object[] enumConstants = spdyMode.getEnumConstants();
        Object mode = ((isNpnMode) ? enumConstants[1] : enumConstants[0]);
        spdyAddon = loadAddOn("org.glassfish.grizzly.spdy.SpdyAddOn", new Class[] {spdyMode}, mode);
      }

      if (spdyAddon != null) {
        // Configure SpdyAddOn
        configureElement(locator, listener, spdyElement, spdyAddon);

        // Spdy requires access to more information compared to the other addons
        // that are currently leveraged.  As such, we'll need to mock out a
        // Grizzly NetworkListener to pass to the addon.  This mock object will
        // only provide the information necessary for the addon to operate.
        // It will be important to keep this mock in sync with the details the
        // addon requires.
        spdyAddon.setup(createMockListener(), builder);
        isSpdyEnabled = true;
      }
    }
  }

  protected org.glassfish.grizzly.http.server.NetworkListener createMockListener() {
    final TCPNIOTransport transportLocal = (TCPNIOTransport) transport;
    return new org.glassfish.grizzly.http.server.NetworkListener("mock") {
      @Override
      public TCPNIOTransport getTransport() {
        return transportLocal;
      }

      @Override
      public boolean isSecure() {
        return true;
      }
    };
  }

  protected void configureCometSupport(
      final ServiceLocator habitat,
      final NetworkListener networkListener,
      final Http http,
      final FilterChainBuilder filterChainBuilder) {

    if (GrizzlyConfig.toBoolean(http.getCometSupportEnabled())) {
      final AddOn cometAddOn =
          loadAddOn(habitat, "comet", "org.glassfish.grizzly.comet.CometAddOn");
      if (cometAddOn != null) {
        configureElement(habitat, networkListener, http, cometAddOn);
        cometAddOn.setup(null, filterChainBuilder);
        isCometEnabled = true;
      }
    }
  }

  protected void configureWebSocketSupport(
      final ServiceLocator habitat,
      final NetworkListener listener,
      final Http http,
      final FilterChainBuilder filterChainBuilder) {
    final boolean websocketsSupportEnabled =
        Boolean.parseBoolean(http.getWebsocketsSupportEnabled());
    if (websocketsSupportEnabled) {
      AddOn wsAddOn =
          loadAddOn(habitat, "websocket", "org.glassfish.grizzly.websockets.WebSocketAddOn");
      if (wsAddOn != null) {
        if (!configureElement(habitat, listener, http, wsAddOn)) {
          // Dealing with a WebSocketAddOn created by reflection vs
          // an HK2 service.  We need to pass the configuration data
          // manually via reflection.
          try {
            Method m = wsAddOn.getClass().getDeclaredMethod("setTimeoutInSeconds", Long.TYPE);
            m.invoke(wsAddOn, Long.parseLong(http.getWebsocketsTimeoutSeconds()));
          } catch (Exception e) {
            if (LOGGER.isLoggable(Level.WARNING)) {
              LOGGER.log(Level.WARNING, e.toString(), e);
            }
          }
        }
        wsAddOn.setup(null, filterChainBuilder);
        isWebSocketEnabled = true;
      }
    }
  }

  protected void configureAjpSupport(
      final ServiceLocator habitat,
      final NetworkListener networkListener,
      final Http http,
      final FilterChainBuilder filterChainBuilder) {

    final boolean jkSupportEnabled =
        http.getJkEnabled() != null
            ? Boolean.parseBoolean(http.getJkEnabled())
            : Boolean.parseBoolean(networkListener.getJkEnabled());

    if (jkSupportEnabled) {
      final AddOn ajpAddOn = loadAddOn(habitat, "ajp", "org.glassfish.grizzly.http.ajp.AjpAddOn");
      if (ajpAddOn != null) {
        configureElement(habitat, networkListener, http, ajpAddOn);
        ajpAddOn.setup(null, filterChainBuilder);
        isAjpEnabled = true;
      }
    }
  }

  /** Load {@link AddOn} with the specific service name and classname. */
  private AddOn loadAddOn(ServiceLocator habitat, String name, String addOnClassName) {
    return Utils.newInstance(habitat, AddOn.class, name, addOnClassName);
  }

  /** Load {@link AddOn} with the specific service name and classname. */
  private AddOn loadAddOn(String addOnClassName, Class[] argTypes, Object... args) {
    return Utils.newInstance(null, AddOn.class, name, addOnClassName, argTypes, args);
  }

  /** Load {@link Filter} with the specific service name and classname. */
  private Filter loadFilter(ServiceLocator habitat, String name, String filterClassName) {
    return Utils.newInstance(habitat, Filter.class, name, filterClassName);
  }

  //    private Filter loadFilter(ServiceLocator habitat,
  //                              String name,
  //                              String filterClassName,
  //                              Class<?>[] ctorArgTypes,
  //                              Object[] ctorArgs) {
  //        return Utils.newInstance(habitat, Filter.class, name, filterClassName, ctorArgTypes,
  // ctorArgs);
  //    }

  private org.glassfish.grizzly.http.HttpServerFilter createHttpServerCodecFilter() {
    return createHttpServerCodecFilter(null);
  }

  private org.glassfish.grizzly.http.HttpServerFilter createHttpServerCodecFilter(final Http http) {

    int maxRequestHeaders = MimeHeaders.MAX_NUM_HEADERS_DEFAULT;
    int maxResponseHeaders = MimeHeaders.MAX_NUM_HEADERS_DEFAULT;
    boolean isChunkedEnabled = true;
    int headerBufferLengthBytes =
        org.glassfish.grizzly.http.HttpServerFilter.DEFAULT_MAX_HTTP_PACKET_HEADER_SIZE;

    String defaultResponseType = null;

    if (http != null) {
      isChunkedEnabled = Boolean.parseBoolean(http.getChunkingEnabled());

      headerBufferLengthBytes = Integer.parseInt(http.getHeaderBufferLengthBytes());

      defaultResponseType = http.getDefaultResponseType();

      maxRequestHeaders = Integer.parseInt(http.getMaxRequestHeaders());

      maxResponseHeaders = Integer.parseInt(http.getMaxResponseHeaders());
    }

    return createHttpServerCodecFilter(
        http,
        isChunkedEnabled,
        headerBufferLengthBytes,
        defaultResponseType,
        configureKeepAlive(http),
        obtainDelayedExecutor(),
        maxRequestHeaders,
        maxResponseHeaders);
  }

  protected org.glassfish.grizzly.http.HttpServerFilter createHttpServerCodecFilter(
      final Http http,
      final boolean isChunkedEnabled,
      final int headerBufferLengthBytes,
      final String defaultResponseType,
      final KeepAlive keepAlive,
      final DelayedExecutor delayedExecutor,
      final int maxRequestHeaders,
      final int maxResponseHeaders) {
    final org.glassfish.grizzly.http.HttpServerFilter httpCodecFilter =
        new org.glassfish.grizzly.http.HttpServerFilter(
            isChunkedEnabled,
            headerBufferLengthBytes,
            defaultResponseType,
            keepAlive,
            delayedExecutor,
            maxRequestHeaders,
            maxResponseHeaders);

    if (http != null) { // could be null for HTTP redirect
      httpCodecFilter.setMaxPayloadRemainderToSkip(
          Integer.parseInt(http.getMaxSwallowingInputBytes()));
    }

    return httpCodecFilter;
  }

  protected ServerFilterConfiguration getHttpServerFilterConfiguration(Http http) {
    final ServerFilterConfiguration serverFilterConfiguration = new ServerFilterConfiguration();
    serverFilterConfiguration.setScheme(http.getScheme());
    serverFilterConfiguration.setPassTraceRequest(true);
    serverFilterConfiguration.setTraceEnabled(Boolean.valueOf(http.getTraceEnabled()));
    int maxRequestParameters;
    try {
      maxRequestParameters = Integer.parseInt(http.getMaxRequestParameters());
    } catch (NumberFormatException nfe) {
      maxRequestParameters = Http.MAX_REQUEST_PARAMETERS;
    }
    serverFilterConfiguration.setMaxRequestParameters(maxRequestParameters);
    serverFilterConfiguration.setMaxPostSize(Integer.parseInt(http.getMaxPostSizeBytes()));
    serverFilterConfiguration.setMaxFormPostSize(Integer.parseInt(http.getMaxFormPostSizeBytes()));
    serverFilterConfiguration.setMaxBufferedPostSize(
        Integer.parseInt(http.getMaxSavePostSizeBytes()));
    return serverFilterConfiguration;
  }

  protected HttpHandler getHttpHandler() {
    return new StaticHttpHandler(".");
  }

  /** Configure the Grizzly HTTP FileCache mechanism */
  protected FileCache configureHttpFileCache(org.glassfish.grizzly.config.dom.FileCache cache) {
    final FileCache fileCache = new FileCache();
    if (cache != null) {
      fileCache.setEnabled(GrizzlyConfig.toBoolean(cache.getEnabled()));
      fileCache.setSecondsMaxAge(Integer.parseInt(cache.getMaxAgeSeconds()));
      fileCache.setMaxCacheEntries(Integer.parseInt(cache.getMaxFilesCount()));
      fileCache.setMaxLargeFileCacheSize(Integer.parseInt(cache.getMaxCacheSizeBytes()));
    } else {
      fileCache.setEnabled(false);
    }
    return fileCache;
  }

  protected KeepAlive configureKeepAlive(final Http http) {
    int timeoutInSeconds = 60;
    int maxConnections = 256;
    if (http != null) {
      try {
        timeoutInSeconds = Integer.parseInt(http.getTimeoutSeconds());
      } catch (NumberFormatException ex) {
        //                String msg = _rb.getString("pewebcontainer.invalidKeepAliveTimeout");
        String msg = "pewebcontainer.invalidKeepAliveTimeout";
        msg =
            MessageFormat.format(msg, http.getTimeoutSeconds(), Integer.toString(timeoutInSeconds));
        LOGGER.log(Level.WARNING, msg, ex);
      }
      try {
        maxConnections = Integer.parseInt(http.getMaxConnections());
      } catch (NumberFormatException ex) {
        //                String msg =
        // _rb.getString("pewebcontainer.invalidKeepAliveMaxConnections");
        String msg = "pewebcontainer.invalidKeepAliveMaxConnections";
        msg = MessageFormat.format(msg, http.getMaxConnections(), Integer.toString(maxConnections));
        LOGGER.log(Level.WARNING, msg, ex);
      }
    }
    final KeepAlive keepAlive = new KeepAlive();
    keepAlive.setIdleTimeoutInSeconds(timeoutInSeconds);
    keepAlive.setMaxRequestsCount(maxConnections);
    return keepAlive;
  }

  protected Set<ContentEncoding> configureContentEncodings(final Http http) {
    return configureCompressionEncodings(http);
  }

  protected Set<ContentEncoding> configureCompressionEncodings(Http http) {
    final String mode = http.getCompression();
    int compressionMinSize = Integer.parseInt(http.getCompressionMinSizeBytes());
    CompressionLevel compressionLevel;
    try {
      compressionLevel = CompressionLevel.getCompressionLevel(mode);
    } catch (IllegalArgumentException e) {
      try {
        // Try to parse compression as an int, which would give the
        // minimum compression size
        compressionLevel = CompressionLevel.ON;
        compressionMinSize = Integer.parseInt(mode);
      } catch (Exception ignore) {
        compressionLevel = CompressionLevel.OFF;
      }
    }
    final String compressableMimeTypesString = http.getCompressableMimeType();
    final String noCompressionUserAgentsString = http.getNoCompressionUserAgents();
    final String[] compressableMimeTypes =
        ((compressableMimeTypesString != null)
            ? compressableMimeTypesString.split(",")
            : new String[0]);
    final String[] noCompressionUserAgents =
        ((noCompressionUserAgentsString != null)
            ? noCompressionUserAgentsString.split(",")
            : new String[0]);
    final ContentEncoding gzipContentEncoding =
        new GZipContentEncoding(
            GZipContentEncoding.DEFAULT_IN_BUFFER_SIZE,
            GZipContentEncoding.DEFAULT_OUT_BUFFER_SIZE,
            new CompressionEncodingFilter(
                compressionLevel,
                compressionMinSize,
                compressableMimeTypes,
                noCompressionUserAgents,
                GZipContentEncoding.getGzipAliases()));
    final ContentEncoding lzmaEncoding =
        new LZMAContentEncoding(
            new CompressionEncodingFilter(
                compressionLevel,
                compressionMinSize,
                compressableMimeTypes,
                noCompressionUserAgents,
                LZMAContentEncoding.getLzmaAliases()));
    final Set<ContentEncoding> set = new HashSet<ContentEncoding>(2);
    set.add(gzipContentEncoding);
    set.add(lzmaEncoding);
    return set;
  }

  @SuppressWarnings("unchecked")
  private static IOStrategy loadIOStrategy(final String classname) {
    Class<? extends IOStrategy> strategy;
    if (classname == null) {
      strategy = WorkerThreadIOStrategy.class;
    } else {
      try {
        strategy = Utils.loadClass(classname);
      } catch (Exception e) {
        strategy = WorkerThreadIOStrategy.class;
      }
    }

    try {
      final Method m = strategy.getMethod("getInstance");
      return (IOStrategy) m.invoke(null);
    } catch (Exception e) {
      throw new IllegalStateException(
          "Can not initialize IOStrategy: " + strategy + ". Error: " + e);
    }
  }

  private static Ssl getSsl(Protocol protocol) {
    Ssl ssl = protocol.getSsl();
    if (ssl == null) {
      ssl = (Ssl) DefaultProxy.createDummyProxy(protocol, Ssl.class);
    }
    return ssl;
  }

  private static SelectionKeyHandler getSelectionKeyHandlerByName(
      final String name, final Transport transportConfig) {
    Transports transports = transportConfig.getParent();
    List<SelectionKeyHandler> handlers = transports.getSelectionKeyHandler();
    if (!handlers.isEmpty()) {
      for (SelectionKeyHandler handler : handlers) {
        if (handler.getName().equals(name)) {
          return handler;
        }
      }
    }
    return null;
  }
}
/**
 * This example demonstrates the use of a {@link HttpHandler} to echo <code>HTTP</code> <code>POST
 * </code> data sent by the client, back to the client using non-blocking streams introduced in
 * Grizzly 2.0.
 *
 * <p>The is composed of two main parts (as nested classes of <code>BlockingHttpHandlerSample</code>
 * )
 *
 * <ul>
 *   <li>Client: This is a simple <code>HTTP</code> based on the Grizzly {@link
 *       org.glassfish.grizzly.http.HttpClientFilter}. The client uses a custom {@link
 *       org.glassfish.grizzly.filterchain.Filter} on top of the {@link
 *       org.glassfish.grizzly.http.HttpClientFilter} to send the <code>POST</code> and read, and
 *       ultimately display, the response from the server. To better demonstrate the callbacks
 *       defined by {@link ReadHandler}, the client will send each data chunk two seconds apart.
 *   <li>NoneBlockingEchoHandler: This {@link HttpHandler} is installed to the {@link
 *       org.glassfish.grizzly.http.server.HttpServer} instance and associated with the path <code>
 *       /echo</code>. The handler uses the {@link org.glassfish.grizzly.http.io.NIOReader} returned
 *       by {@link org.glassfish.grizzly.http.server.Request#getReader()}. As data is received
 *       asynchronously, the {@link ReadHandler} callbacks are invoked at this time data is then
 *       written to the response.
 * </ul>
 */
public class NonBlockingHttpHandlerSample {

  private static final Logger LOGGER = Grizzly.logger(NonBlockingHttpHandlerSample.class);

  public static void main(String[] args) {

    // create a basic server that listens on port 8080.
    final HttpServer server = HttpServer.createSimpleServer();

    final ServerConfiguration config = server.getServerConfiguration();

    // Map the path, /echo, to the NonBlockingEchoHandler
    config.addHttpHandler(new NonBlockingEchoHandler(), "/echo");

    try {
      server.start();
      Client client = new Client();
      client.run();
    } catch (IOException ioe) {
      LOGGER.log(Level.SEVERE, ioe.toString(), ioe);
    } finally {
      server.shutdownNow();
    }
  }

  // ---------------------------------------------------------- Nested Classes

  private static final class Client {

    private static final String HOST = "localhost";
    private static final int PORT = 8080;

    public void run() throws IOException {
      final FutureImpl<String> completeFuture = SafeFutureImpl.create();

      // Build HTTP client filter chain
      FilterChainBuilder clientFilterChainBuilder = FilterChainBuilder.newInstance();
      // Add transport filter
      clientFilterChainBuilder.add(new TransportFilter());

      // Add HttpClientFilter, which transforms Buffer <-> HttpContent
      clientFilterChainBuilder.add(new HttpClientFilter());
      // Add ClientFilter
      clientFilterChainBuilder.add(new ClientFilter(completeFuture));

      // Initialize Transport
      final TCPNIOTransport transport = TCPNIOTransportBuilder.newInstance().build();
      // Set filterchain as a Transport Processor
      transport.setFilterChain(clientFilterChainBuilder.build());

      try {
        // start the transport
        transport.start();

        Connection connection = null;

        // Connecting to a remote Web server
        Future<Connection> connectFuture = transport.connect(HOST, PORT);
        try {
          // Wait until the client connect operation will be completed
          // Once connection has been established, the POST will
          // be sent to the server.
          connection = connectFuture.get(10, TimeUnit.SECONDS);

          // Wait no longer than 30 seconds for the response from the
          // server to be complete.
          String result = completeFuture.get(30, TimeUnit.SECONDS);

          // Display the echoed content
          System.out.println("\nEchoed POST Data: " + result + '\n');
        } catch (Exception e) {
          if (connection == null) {
            LOGGER.log(Level.WARNING, "Connection failed.  Server is not listening.");
          } else {
            LOGGER.log(Level.WARNING, "Unexpected error communicating with the server.");
          }
        } finally {
          // Close the client connection
          if (connection != null) {
            connection.closeSilently();
          }
        }
      } finally {
        // shutdownNow the transport
        transport.shutdownNow();
      }
    }

    // ------------------------------------------------------ Nested Classes

    private static final class ClientFilter extends BaseFilter {
      private static final HeaderValue HOST_HEADER_VALUE =
          HeaderValue.newHeaderValue(HOST + ':' + PORT).prepare();

      private static final String[] CONTENT = {"contentA-", "contentB-", "contentC-", "contentD"};

      private FutureImpl<String> future;

      private StringBuilder sb = new StringBuilder();

      // ---------------------------------------------------- Constructors

      private ClientFilter(FutureImpl<String> future) {
        this.future = future;
      }

      // ----------------------------------------- Methods from BaseFilter

      @SuppressWarnings({"unchecked"})
      @Override
      public NextAction handleConnect(FilterChainContext ctx) throws IOException {
        System.out.println("\nClient connected!\n");

        HttpRequestPacket request = createRequest();
        System.out.println("Writing request:\n");
        System.out.println(request.toString());
        ctx.write(request); // write the request

        // for each of the content parts in CONTENT, wrap in a Buffer,
        // create the HttpContent to wrap the buffer and write the
        // content.
        MemoryManager mm = ctx.getMemoryManager();
        for (int i = 0, len = CONTENT.length; i < len; i++) {
          HttpContent.Builder contentBuilder = request.httpContentBuilder();
          Buffer b = Buffers.wrap(mm, CONTENT[i]);
          contentBuilder.content(b);
          HttpContent content = contentBuilder.build();
          System.out.printf("(Client writing: %s)\n", b.toStringContent());
          ctx.write(content);
          try {
            Thread.sleep(2000);
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }

        // since the request created by createRequest() is chunked,
        // we need to write the trailer to signify the end of the
        // POST data
        ctx.write(request.httpTrailerBuilder().build());

        System.out.println("\n");

        return ctx.getStopAction(); // discontinue filter chain execution
      }

      @Override
      public NextAction handleRead(FilterChainContext ctx) throws IOException {

        HttpContent c = (HttpContent) ctx.getMessage();
        Buffer b = c.getContent();
        if (b.hasRemaining()) {
          sb.append(b.toStringContent());
        }

        // Last content from the server, set the future result so
        // the client can display the result and gracefully exit.
        if (c.isLast()) {
          future.result(sb.toString());
        }
        return ctx.getStopAction(); // discontinue filter chain execution
      }

      // ------------------------------------------------- Private Methods

      private HttpRequestPacket createRequest() {

        HttpRequestPacket.Builder builder = HttpRequestPacket.builder();
        builder.method("POST");
        builder.protocol("HTTP/1.1");
        builder.uri("/echo");
        builder.chunked(true);
        HttpRequestPacket packet = builder.build();
        packet.addHeader(Header.Host, HOST_HEADER_VALUE);
        return packet;
      }
    }
  } // END Client

  /** This handler using non-blocking streams to read POST data and echo it back to the client. */
  private static class NonBlockingEchoHandler extends HttpHandler {

    // -------------------------------------------- Methods from HttpHandler

    @Override
    public void service(final Request request, final Response response) throws Exception {

      final char[] buf = new char[128];
      final NIOReader in = request.getReader(); // put the stream in non-blocking mode
      final NIOWriter out = response.getWriter();

      response.suspend();

      // If we don't have more data to read - onAllDataRead() will be called
      in.notifyAvailable(
          new ReadHandler() {

            @Override
            public void onDataAvailable() throws Exception {
              System.out.printf("[onDataAvailable] echoing %d bytes\n", in.readyData());
              echoAvailableData(in, out, buf);
              in.notifyAvailable(this);
            }

            @Override
            public void onError(Throwable t) {
              System.out.println("[onError]" + t);
              response.resume();
            }

            @Override
            public void onAllDataRead() throws Exception {
              System.out.printf("[onAllDataRead] length: %d\n", in.readyData());
              try {
                echoAvailableData(in, out, buf);
              } finally {
                try {
                  in.close();
                } catch (IOException ignored) {
                }

                try {
                  out.close();
                } catch (IOException ignored) {
                }

                response.resume();
              }
            }
          });
    }

    private void echoAvailableData(NIOReader in, NIOWriter out, char[] buf) throws IOException {

      while (in.isReady()) {
        int len = in.read(buf);
        out.write(buf, 0, len);
      }
    }
  } // END NonBlockingEchoHandler
}
Beispiel #4
0
/** Utils for introspection and reflection */
public final class IntrospectionUtils {

  private static final Logger LOGGER = Grizzly.logger(IntrospectionUtils.class);

  /**
   * Find a method with the right name If found, call the method ( if param is int or boolean we'll
   * convert value to the right type before) - that means you can have setDebug(1).
   */
  public static boolean setProperty(Object o, String name, String value) {
    if (dbg > 1) {
      d("setProperty(" + o.getClass() + " " + name + "=" + value + ")");
    }

    String setter = "set" + capitalize(name);

    try {
      Method methods[] = findMethods(o.getClass());
      Method setPropertyMethodVoid = null;
      Method setPropertyMethodBool = null;

      // First, the ideal case - a setFoo( String ) method
      for (int i = 0; i < methods.length; i++) {
        Class<?> paramT[] = methods[i].getParameterTypes();
        if (setter.equals(methods[i].getName())
            && paramT.length == 1
            && "java.lang.String".equals(paramT[0].getName())) {

          methods[i].invoke(o, value);
          return true;
        }
      }

      // Try a setFoo ( int ) or ( boolean )
      for (int i = 0; i < methods.length; i++) {
        boolean ok = true;
        if (setter.equals(methods[i].getName()) && methods[i].getParameterTypes().length == 1) {

          // match - find the type and invoke it
          Class<?> paramType = methods[i].getParameterTypes()[0];
          Object params[] = new Object[1];

          // Try a setFoo ( int )
          if ("java.lang.Integer".equals(paramType.getName())
              || "int".equals(paramType.getName())) {
            try {
              params[0] = new Integer(value);
            } catch (NumberFormatException ex) {
              ok = false;
            }
            // Try a setFoo ( long )
          } else if ("java.lang.Long".equals(paramType.getName())
              || "long".equals(paramType.getName())) {
            try {
              params[0] = new Long(value);
            } catch (NumberFormatException ex) {
              ok = false;
            }

            // Try a setFoo ( boolean )
          } else if ("java.lang.Boolean".equals(paramType.getName())
              || "boolean".equals(paramType.getName())) {
            params[0] = Boolean.valueOf(value);

            // Try a setFoo ( InetAddress )
          } else if ("java.net.InetAddress".equals(paramType.getName())) {
            try {
              params[0] = InetAddress.getByName(value);
            } catch (UnknownHostException exc) {
              d("Unable to resolve host name:" + value);
              ok = false;
            }

            // Unknown type
          } else {
            d("Unknown type " + paramType.getName());
          }

          if (ok) {
            methods[i].invoke(o, params);
            return true;
          }
        }

        // save "setProperty" for later
        if ("setProperty".equals(methods[i].getName())) {
          if (methods[i].getReturnType() == Boolean.TYPE) {
            setPropertyMethodBool = methods[i];
          } else {
            setPropertyMethodVoid = methods[i];
          }
        }
      }

      // Ok, no setXXX found, try a setProperty("name", "value")
      if (setPropertyMethodBool != null || setPropertyMethodVoid != null) {
        Object params[] = new Object[2];
        params[0] = name;
        params[1] = value;
        if (setPropertyMethodBool != null) {
          try {
            return (Boolean) setPropertyMethodBool.invoke(o, params);
          } catch (IllegalArgumentException biae) {
            // the boolean method had the wrong
            // parameter types. lets try the other
            if (setPropertyMethodVoid != null) {
              setPropertyMethodVoid.invoke(o, params);
              return true;
            } else {
              throw biae;
            }
          }
        } else {
          setPropertyMethodVoid.invoke(o, params);
          return true;
        }
      }

    } catch (IllegalArgumentException ex2) {
      LOGGER.log(Level.INFO, "IAE " + o + " " + name + " " + value, ex2);
    } catch (SecurityException ex1) {
      if (dbg > 0) {
        d("SecurityException for " + o.getClass() + " " + name + "=" + value + ")");
      }
      if (dbg > 1) {
        LOGGER.log(Level.WARNING, "", ex1);
      }
    } catch (IllegalAccessException iae) {
      if (dbg > 0) {
        d("IllegalAccessException for " + o.getClass() + " " + name + "=" + value + ")");
      }
      if (dbg > 1) {
        LOGGER.log(Level.WARNING, "", iae);
      }
    } catch (InvocationTargetException ie) {
      if (dbg > 0) {
        d("InvocationTargetException for " + o.getClass() + " " + name + "=" + value + ")");
      }
      if (dbg > 1) {
        LOGGER.log(Level.WARNING, "", ie);
      }
    }
    return false;
  }

  /** Reverse of Introspector.decapitalize */
  public static String capitalize(String name) {
    if (name == null || name.length() == 0) {
      return name;
    }
    char chars[] = name.toCharArray();
    chars[0] = Character.toUpperCase(chars[0]);
    return new String(chars);
  }

  // -------------------- other utils --------------------

  @SuppressWarnings("UnusedDeclaration")
  public static void clear() {
    objectMethods.clear();
  }

  static Map<Class<?>, Method[]> objectMethods = new HashMap<Class<?>, Method[]>();

  @SuppressWarnings("unchecked")
  public static Method[] findMethods(Class<?> c) {
    Method methods[] = objectMethods.get(c);
    if (methods != null) {
      return methods;
    }

    methods = c.getMethods();
    objectMethods.put(c, methods);
    return methods;
  }

  // debug --------------------
  static final int dbg = 0;

  static void d(String s) {
    if (LOGGER.isLoggable(Level.FINE)) {
      LOGGER.log(Level.FINE, "IntrospectionUtils: {0}", s);
    }
  }
}
/**
 * Test simple Ajp communication usecases.
 *
 * @author Alexey Stashok
 */
public class BasicAjpTest extends AjpTestBase {
  private static final Logger LOGGER = Grizzly.logger(BasicAjpTest.class);

  @Test
  public void test100ContinuePost() throws IOException, InstantiationException, Exception {
    HttpHandler httpHanlder =
        new HttpHandler() {

          @Override
          public void service(Request request, Response response) throws Exception {
            if (request.getHeader("Expect") != null) {
              response.sendAcknowledgement();

              final int length = request.getContentLength();
              final InputStream is = request.getInputStream();

              for (int i = 0; i < length; i++) {
                final int c = is.read();
                final int expected = (i % 'Z' - 'A') + 'A';
                if (c != expected) {
                  response.sendError(
                      400,
                      "Unexpected char["
                          + i
                          + "]. Expected: "
                          + ((char) expected)
                          + " but was: "
                          + ((char) c)
                          + "("
                          + c
                          + ")");
                  return;
                }
              }

              response.setStatus(200, "FINE");
            } else {
              response.sendError(500, "100-continue header has been lost?");
            }
          }
        };

    final int size = 1024;

    startHttpServer(httpHanlder);

    final AjpForwardRequestPacket headersPacket =
        new AjpForwardRequestPacket("POST", "/myresource", 80, PORT);
    headersPacket.addHeader("Content-Length", String.valueOf(size));
    headersPacket.addHeader("Host", "localhost:80");
    headersPacket.addHeader("Expect", "100-continue");

    send(headersPacket.toByteArray());

    byte[] postBody = new byte[size];
    for (int i = 0; i < postBody.length; i++) {
      postBody[i] = (byte) ((i % 'Z' - 'A') + 'A');
    }

    final AjpDataPacket dataPacket = new AjpDataPacket(postBody);
    send(dataPacket.toByteArray());

    AjpResponse ajpResponse = Utils.parseResponse(readAjpMessage());
    Assert.assertEquals(ajpResponse.getResponseMessage(), 200, ajpResponse.getResponseCode());
    Assert.assertEquals("FINE", ajpResponse.getResponseMessage());
  }

  @Test
  public void testStabilityAfterWrongMagic() throws Exception {
    HttpHandler a =
        new HttpHandler() {

          @Override
          public void service(Request request, Response response) throws Exception {
            response.setStatus(200, "FINE");
          }
        };

    startHttpServer(a);

    // Send junk data
    final Socket s = new Socket("localhost", PORT);
    try {
      s.getOutputStream().write("junkjunkjunk".getBytes());
      s.getOutputStream().flush();
      while (s.getInputStream().read() != -1) {}
    } finally {
      s.close();
    }

    AjpForwardRequestPacket forward = new AjpForwardRequestPacket("GET", "/bob", PORT, 0);

    for (int i = 0; i < 1024; i++) {
      send(forward.toByteArray());
      AjpResponse ajpResponse = Utils.parseResponse(readAjpMessage());

      Assert.assertEquals(200, ajpResponse.getResponseCode());
      Assert.assertEquals("FINE", ajpResponse.getResponseMessage());
      closeClient();
    }
  }

  @Test
  public void testPingPong() throws Exception {
    startHttpServer(
        new HttpHandler() {

          @Override
          public void service(Request request, Response response) throws Exception {
            response.setStatus(200, "FINE");
          }
        },
        "/");

    final MemoryManager mm =
        httpServer.getListener(LISTENER_NAME).getTransport().getMemoryManager();
    final Buffer request = mm.allocate(512);
    request.put((byte) 0x12);
    request.put((byte) 0x34);
    request.putShort((short) 1);
    request.put(AjpConstants.JK_AJP13_CPING_REQUEST);
    request.flip();

    final Future<Buffer> responseFuture = send("localhost", PORT, request);
    Buffer response = responseFuture.get(10, TimeUnit.SECONDS);

    assertEquals('A', response.get());
    assertEquals('B', response.get());
    assertEquals((short) 1, response.getShort());
    assertEquals(AjpConstants.JK_AJP13_CPONG_REPLY, response.get());

    final AjpForwardRequestPacket headersPacket =
        new AjpForwardRequestPacket("GET", "/TestServlet/normal", 80, PORT);
    headersPacket.addHeader("Host", "localhost:80");
    send(headersPacket.toByteArray());

    AjpResponse ajpResponse = Utils.parseResponse(readAjpMessage());
    Assert.assertEquals("FINE", ajpResponse.getResponseMessage());
  }

  @Test
  public void testShutdownHandler() throws Exception {
    final FutureImpl<Boolean> shutdownFuture = SafeFutureImpl.create();
    final ShutdownHandler shutDownHandler =
        new ShutdownHandler() {

          @Override
          public void onShutdown(Connection initiator) {
            shutdownFuture.result(true);
          }
        };

    AjpAddOn myAjpAddon =
        new AjpAddOn() {

          @Override
          protected AjpHandlerFilter createAjpHandlerFilter() {
            final AjpHandlerFilter filter = new AjpHandlerFilter();
            filter.addShutdownHandler(shutDownHandler);
            return filter;
          }
        };

    final NetworkListener listener = httpServer.getListener(LISTENER_NAME);

    listener.deregisterAddOn(ajpAddon);
    listener.registerAddOn(myAjpAddon);

    startHttpServer(
        new HttpHandler() {

          @Override
          public void service(Request request, Response response) throws Exception {}
        },
        "/");

    final MemoryManager mm = listener.getTransport().getMemoryManager();
    final Buffer request = mm.allocate(512);
    request.put((byte) 0x12);
    request.put((byte) 0x34);
    request.putShort((short) 1);
    request.put(AjpConstants.JK_AJP13_SHUTDOWN);
    request.flip();

    send("localhost", PORT, request);
    final Boolean b = shutdownFuture.get(10, TimeUnit.SECONDS);
    assertTrue(b);
  }

  @Test
  public void testNullAttribute() throws Exception {
    final NetworkListener listener = httpServer.getListener(LISTENER_NAME);

    startHttpServer(
        new HttpHandler() {

          @Override
          public void service(Request request, Response response) throws Exception {
            final Set<String> attributeNames = request.getAttributeNames();
            final boolean isOk =
                attributeNames.contains("JK_LB_ACTIVATION")
                    && request.getAttribute("JK_LB_ACTIVATION") == null
                    && attributeNames.contains("AJP_REMOTE_PORT")
                    && "60955".equals(request.getAttribute("AJP_REMOTE_PORT"));

            if (isOk) {
              response.setStatus(200, "FINE");
            } else {
              response.setStatus(500, "Attributes don't match");
            }
          }
        },
        "/SimpleWebApp/SimpleServlet");

    final MemoryManager mm = listener.getTransport().getMemoryManager();
    final Buffer request = Buffers.wrap(mm, Utils.loadResourceFile("null-attr-payload.dat"));

    Buffer responseBuffer = send("localhost", PORT, request).get(10, TimeUnit.SECONDS);

    // Successful response length is 37 bytes.  This includes the status
    // line and a content-length
    boolean isFailure = responseBuffer.remaining() != 37;

    if (isFailure) {
      byte[] response = new byte[responseBuffer.remaining()];
      responseBuffer.get(response);
      String hex = toHexString(response);
      fail("unexpected response length=" + response.length + " content=[" + hex + "]");
    }
  }

  @Test
  public void testFormParameters() throws Exception {
    final Map<String, String[]> patternMap = new HashMap<String, String[]>();
    patternMap.put("title", new String[] {"Developing PaaS Components"});
    patternMap.put("authors", new String[] {"Shalini M"});
    patternMap.put("price", new String[] {"100$"});

    final NetworkListener listener = httpServer.getListener(LISTENER_NAME);

    startHttpServer(
        new HttpHandler() {

          @Override
          public void service(Request request, Response response) throws Exception {
            final Map<String, String[]> paramMap = request.getParameterMap();
            boolean isOk = paramMap.size() == patternMap.size();

            if (isOk) {
              // if sizes are equal - compare content
              for (Map.Entry<String, String[]> patternEntry : patternMap.entrySet()) {
                final String key = patternEntry.getKey();
                final String[] value = patternEntry.getValue();
                isOk = paramMap.containsKey(key) && Arrays.equals(value, paramMap.get(key));

                if (!isOk) break;
              }
            }

            if (isOk) {
              response.setStatus(200, "FINE");
            } else {
              response.setStatus(500, "Attributes don't match");
            }
          }
        },
        "/bookstore/BookStoreServlet");

    final MemoryManager mm = listener.getTransport().getMemoryManager();
    final Buffer requestPart1 =
        Buffers.wrap(mm, Utils.loadResourceFile("form-params-payload1.dat"));
    final Buffer requestPart2 =
        Buffers.wrap(mm, Utils.loadResourceFile("form-params-payload2.dat"));

    Buffer responseBuffer =
        send("localhost", PORT, Buffers.appendBuffers(mm, requestPart1, requestPart2))
            .get(10, TimeUnit.SECONDS);

    // Successful response length is 37 bytes.  This includes the status
    // line and a content-length
    boolean isFailure = responseBuffer.remaining() != 37;

    if (isFailure) {
      byte[] response = new byte[responseBuffer.remaining()];
      responseBuffer.get(response);
      String hex = toHexString(response);
      fail("unexpected response length=" + response.length + " content=[" + hex + "]");
    }
  }

  @Test
  public void testSslParams() throws Exception {
    final NetworkListener listener = httpServer.getListener(LISTENER_NAME);

    startHttpServer(
        new HttpHandler() {

          @Override
          public void service(Request request, Response response) throws Exception {
            boolean isOk = request.isSecure();
            String error = "unknown";

            if (isOk) {
              try {
                assertEquals(
                    (Integer) 256, (Integer) request.getAttribute(SSLSupport.KEY_SIZE_KEY));
                assertNotNull(request.getAttribute(SSLSupport.SESSION_ID_KEY));
                assertNotNull(request.getAttribute(SSLSupport.CIPHER_SUITE_KEY));
                assertNotNull(request.getAttribute(SSLSupport.CERTIFICATE_KEY));
              } catch (Exception e) {
                error = e.getClass().getName() + ": " + e.getMessage();
                isOk = false;
              }
            }

            if (isOk) {
              response.setStatus(200, "FINE");
            } else {
              response.setStatus(500, error);
            }
          }
        });

    final MemoryManager mm = listener.getTransport().getMemoryManager();
    final Buffer request = Buffers.wrap(mm, Utils.loadResourceFile("get-secured.dat"));

    Buffer responseBuffer = send("localhost", PORT, request).get(10, TimeUnit.SECONDS);

    // Successful response length is 37 bytes.  This includes the status
    // line and a content-length
    boolean isFailure = responseBuffer.remaining() != 37;

    if (isFailure) {
      byte[] response = new byte[responseBuffer.remaining()];
      responseBuffer.get(response);
      String hex = toHexString(response);
      fail("unexpected response length=" + response.length + " content=[" + hex + "]");
    }
  }

  @Test
  public void testAddresses() throws Exception {
    final String expectedRemoteAddr = "10.163.27.8";
    final String expectedLocalAddr = "10.163.25.1";
    final NetworkListener listener = httpServer.getListener(LISTENER_NAME);

    startHttpServer(
        new HttpHandler() {

          @Override
          public void service(Request request, Response response) throws Exception {
            boolean isOk = false;
            final StringBuilder errorBuilder = new StringBuilder();
            try {
              String result = request.getRemoteAddr();
              isOk = expectedRemoteAddr.equals(result);
              if (!isOk) {
                errorBuilder
                    .append("Remote host don't match. Expected ")
                    .append(expectedRemoteAddr)
                    .append(" but was ")
                    .append(result)
                    .append('\n');
              }

              String localName = request.getLocalName();
              String localAddr = request.getLocalAddr();
              isOk = expectedLocalAddr.equals(localName) && localName.equals(localAddr);
              if (!isOk) {
                errorBuilder
                    .append("Local address and host don't match. Expected=")
                    .append(expectedLocalAddr)
                    .append(" Addr=")
                    .append(localAddr)
                    .append(" name=")
                    .append(localName)
                    .append('\n');
              }
            } catch (Exception e) {
              errorBuilder.append(e.toString());
            }

            if (isOk) {
              response.setStatus(200, "FINE");
            } else {
              LOGGER.warning(errorBuilder.toString());
              response.setStatus(500, "ERROR");
            }
          }
        });

    final MemoryManager mm = listener.getTransport().getMemoryManager();
    final Buffer request = Buffers.wrap(mm, Utils.loadResourceFile("peer-addr-check.dat"));

    Buffer responseBuffer = send("localhost", PORT, request).get(60, TimeUnit.SECONDS);

    // Successful response length is 37 bytes.  This includes the status
    // line and a content-length
    boolean isFailure = responseBuffer.remaining() != 37;

    if (isFailure) {
      byte[] response = new byte[responseBuffer.remaining()];
      responseBuffer.get(response);
      String hex = toHexString(response);
      fail("unexpected response length=" + response.length + " content=[" + hex + "]");
    }
  }

  @SuppressWarnings({"unchecked"})
  private Future<Buffer> send(String host, int port, Buffer request) throws Exception {
    final FutureImpl<Buffer> future = SafeFutureImpl.create();

    final FilterChainBuilder builder = FilterChainBuilder.newInstance();
    builder.add(new TransportFilter());

    builder.add(new AjpClientMessageFilter());
    builder.add(new ResultFilter(future));

    SocketConnectorHandler connectorHandler =
        TCPNIOConnectorHandler.builder(
                (TCPNIOTransport) httpServer.getListener(LISTENER_NAME).getTransport())
            .filterChain(builder.build())
            .build();

    Future<Connection> connectFuture = connectorHandler.connect(host, port);
    final Connection connection = connectFuture.get(10, TimeUnit.SECONDS);

    connection.write(request);

    return future;
  }

  private String toHexString(byte[] response) {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < response.length; i++) {
      sb.append(Integer.toHexString(response[i] & 0xFF));

      if (i != response.length - 1) {
        sb.append(' ');
      }
    }

    return sb.toString();
  }

  private static class ResultFilter extends BaseFilter {

    private final FutureImpl<Buffer> future;

    public ResultFilter(FutureImpl<Buffer> future) {
      this.future = future;
    }

    @Override
    public NextAction handleRead(FilterChainContext ctx) throws IOException {
      final Buffer content = ctx.getMessage();
      try {
        future.result(content);
      } catch (Exception e) {
        future.failure(e);
        e.printStackTrace();
      }

      return ctx.getStopAction();
    }
  }
}
/**
 * This class implements a file caching mechanism used to cache static resources.
 *
 * @author Jeanfrancois Arcand
 * @author Scott Oaks
 */
public class FileCache implements MonitoringAware<FileCacheProbe> {
  private static final File TMP_DIR = new File(System.getProperty("java.io.tmpdir"));

  static final String[] COMPRESSION_ALIASES = {"gzip"};

  public enum CacheType {
    HEAP,
    MAPPED,
    FILE,
    TIMESTAMP
  }

  public enum CacheResult {
    OK_CACHED,
    OK_CACHED_TIMESTAMP,
    FAILED_CACHE_FULL,
    FAILED_ENTRY_EXISTS,
    FAILED
  }

  private static final Logger LOGGER = Grizzly.logger(FileCache.class);

  /** Cache size. */
  private final AtomicInteger cacheSize = new AtomicInteger();

  /** A {@link ByteBuffer} cache of static pages. */
  private final ConcurrentMap<FileCacheKey, FileCacheEntry> fileCacheMap =
      DataStructures.<FileCacheKey, FileCacheEntry>getConcurrentMap();

  private final FileCacheEntry NULL_CACHE_ENTRY = new FileCacheEntry(this);

  /** Specifies the maximum time in seconds a resource may be cached. */
  private int secondsMaxAge = -1;

  /** The maximum entries in the {@link FileCache} */
  private volatile int maxCacheEntries = 1024;

  /** The maximum size of a cached resource. */
  private long minEntrySize = Long.MIN_VALUE;

  /** The maximum size of a cached resource. */
  private long maxEntrySize = Long.MAX_VALUE;

  /** The maximum memory mapped bytes. */
  private volatile long maxLargeFileCacheSize = Long.MAX_VALUE;

  /** The maximum cached bytes */
  private volatile long maxSmallFileCacheSize = 1048576;

  /** The current cache size in bytes */
  private AtomicLong mappedMemorySize = new AtomicLong();

  /** The current cache size in bytes */
  private AtomicLong heapSize = new AtomicLong();

  /** Is the file cache enabled. */
  private boolean enabled = true;

  private DelayedExecutor.DelayQueue<FileCacheEntry> delayQueue;

  /** Folder to store compressed cached files */
  private volatile File compressedFilesFolder = TMP_DIR;
  /** Compression configuration, used to decide if cached resource has to be compressed or not */
  private final CompressionConfig compressionConfig = new CompressionConfig();

  /** <tt>true</tt>, if zero-copy file-send feature could be used, or <tt>false</tt> otherwise. */
  private boolean fileSendEnabled;

  /** File cache probes */
  protected final DefaultMonitoringConfig<FileCacheProbe> monitoringConfig =
      new DefaultMonitoringConfig<FileCacheProbe>(FileCacheProbe.class) {

        @Override
        public Object createManagementObject() {
          return createJmxManagementObject();
        }
      };

  // ---------------------------------------------------- Methods ----------//

  public void initialize(final DelayedExecutor delayedExecutor) {
    delayQueue = delayedExecutor.createDelayQueue(new EntryWorker(), new EntryResolver());
  }

  /**
   * Add a resource to the cache. Unlike the {@link
   * #add(org.glassfish.grizzly.http.HttpRequestPacket, java.io.File)} this method adds a resource
   * to a cache but is not able to send the resource content to a client if client doesn't have the
   * latest version of this resource.
   */
  public CacheResult add(final HttpRequestPacket request, final long lastModified) {
    return add(request, null, lastModified);
  }

  /**
   * Add a {@link File} resource to the cache. If a client comes with not the latest version of this
   * resource - the {@link FileCache} will return it the latest resource version.
   */
  public CacheResult add(final HttpRequestPacket request, final File cacheFile) {
    return add(request, cacheFile, cacheFile.lastModified());
  }

  /** Add a resource to the cache. */
  protected CacheResult add(
      final HttpRequestPacket request, final File cacheFile, final long lastModified) {

    final String requestURI = request.getRequestURI();

    if (requestURI == null) {
      return CacheResult.FAILED;
    }

    final String host = request.getHeader(Header.Host);
    final FileCacheKey key = new FileCacheKey(host, requestURI);
    if (fileCacheMap.putIfAbsent(key, NULL_CACHE_ENTRY) != null) {
      key.recycle();
      return CacheResult.FAILED_ENTRY_EXISTS;
    }

    final int size = cacheSize.incrementAndGet();
    // cache is full.
    if (size > getMaxCacheEntries()) {
      cacheSize.decrementAndGet();
      fileCacheMap.remove(key);
      key.recycle();
      return CacheResult.FAILED_CACHE_FULL;
    }

    final HttpResponsePacket response = request.getResponse();
    final MimeHeaders headers = response.getHeaders();

    final String contentType = response.getContentType();

    final FileCacheEntry entry;
    if (cacheFile != null) { // If we have a file - try to create File-aware cache resource
      entry = createEntry(cacheFile);
      entry.setCanBeCompressed(canBeCompressed(cacheFile, contentType));
    } else {
      entry = new FileCacheEntry(this);
      entry.type = CacheType.TIMESTAMP;
    }

    entry.key = key;
    entry.requestURI = requestURI;

    entry.lastModified = lastModified;
    entry.contentType = ContentType.newContentType(contentType);
    entry.xPoweredBy = headers.getHeader(Header.XPoweredBy);
    entry.date = headers.getHeader(Header.Date);
    entry.lastModifiedHeader = headers.getHeader(Header.LastModified);
    entry.host = host;
    entry.Etag = headers.getHeader(Header.ETag);
    entry.server = headers.getHeader(Header.Server);

    fileCacheMap.put(key, entry);

    notifyProbesEntryAdded(this, entry);

    final int secondsMaxAgeLocal = getSecondsMaxAge();
    if (secondsMaxAgeLocal > 0) {
      delayQueue.add(entry, secondsMaxAgeLocal, TimeUnit.SECONDS);
    }

    return ((entry.type == CacheType.TIMESTAMP)
        ? CacheResult.OK_CACHED_TIMESTAMP
        : CacheResult.OK_CACHED);
  }

  /**
   * Returns {@link FileCacheEntry}. If {@link FileCacheEntry} has been found - this method also
   * sets correspondent {@link HttpResponsePacket} status code and reason phrase.
   */
  public FileCacheEntry get(final HttpRequestPacket request) {
    // It should be faster than calculating the key hash code
    if (cacheSize.get() == 0) return null;

    final LazyFileCacheKey key = LazyFileCacheKey.create(request);
    final FileCacheEntry entry = fileCacheMap.get(key);
    key.recycle();
    try {
      if (entry != null && entry != NULL_CACHE_ENTRY) {
        // determine if we need to send the cache entry bytes
        // to the user-agent
        final HttpStatus httpStatus = checkIfHeaders(entry, request);

        final boolean flushBody = (httpStatus == null);
        if (flushBody && entry.type == CacheType.TIMESTAMP) {
          return null; // this will cause control to be passed to the static handler
        }

        request.getResponse().setStatus(httpStatus != null ? httpStatus : HttpStatus.OK_200);

        notifyProbesEntryHit(this, entry);
        return entry;
      }

      notifyProbesEntryMissed(this, request);
    } catch (Exception e) {
      notifyProbesError(this, e);
      // If an unexpected exception occurs, try to serve the page
      // as if it wasn't in a cache.
      LOGGER.log(
          Level.WARNING, LogMessages.WARNING_GRIZZLY_HTTP_SERVER_FILECACHE_GENERAL_ERROR(), e);
    }

    return null;
  }

  protected void remove(final FileCacheEntry entry) {
    if (fileCacheMap.remove(entry.key) != null) {
      cacheSize.decrementAndGet();
    }

    if (entry.type == FileCache.CacheType.MAPPED) {
      subMappedMemorySize(entry.bb.remaining());
    } else if (entry.type == FileCache.CacheType.HEAP) {
      subHeapSize(entry.bb.remaining());
    }

    notifyProbesEntryRemoved(this, entry);
  }

  protected Object createJmxManagementObject() {
    return MonitoringUtils.loadJmxObject(
        "org.glassfish.grizzly.http.server.filecache.jmx.FileCache", this, FileCache.class);
  }

  /** Creates {@link FileCacheEntry}. */
  private FileCacheEntry createEntry(final File file) {
    FileCacheEntry entry = tryMapFileToBuffer(file);
    if (entry == null) {
      entry = new FileCacheEntry(this);
      entry.type = CacheType.FILE;
    }

    entry.plainFile = file;
    entry.plainFileSize = file.length();

    return entry;
  }

  /**
   * Map the file to a {@link ByteBuffer}
   *
   * @return the preinitialized {@link FileCacheEntry}
   */
  private FileCacheEntry tryMapFileToBuffer(final File file) {

    final long size = file.length();
    if (size > getMaxEntrySize()) {
      return null;
    }

    final CacheType type;
    final ByteBuffer bb;
    FileChannel fileChannel = null;
    FileInputStream stream = null;
    try {
      if (size > getMinEntrySize()) {
        if (addMappedMemorySize(size) > getMaxLargeFileCacheSize()) {
          // Cache full
          subMappedMemorySize(size);
          return null;
        }

        type = CacheType.MAPPED;
      } else {
        if (addHeapSize(size) > getMaxSmallFileCacheSize()) {
          // Cache full
          subHeapSize(size);
          return null;
        }

        type = CacheType.HEAP;
      }

      stream = new FileInputStream(file);
      fileChannel = stream.getChannel();

      bb = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, size);

      if (type == CacheType.HEAP) {
        ((MappedByteBuffer) bb).load();
      }
    } catch (Exception e) {
      notifyProbesError(this, e);
      return null;
    } finally {
      if (stream != null) {
        try {
          stream.close();
        } catch (IOException ignored) {
          notifyProbesError(this, ignored);
        }
      }
      if (fileChannel != null) {
        try {
          fileChannel.close();
        } catch (IOException ignored) {
          notifyProbesError(this, ignored);
        }
      }
    }

    final FileCacheEntry entry = new FileCacheEntry(this);
    entry.type = type;
    entry.plainFileSize = size;
    entry.bb = bb;

    return entry;
  }

  /** Checks if the {@link File} with the given content-type could be compressed. */
  private boolean canBeCompressed(final File cacheFile, final String contentType) {
    switch (compressionConfig.getCompressionMode()) {
      case FORCE:
        return true;
      case OFF:
        return false;
      case ON:
        {
          if (cacheFile.length() < compressionConfig.getCompressionMinSize()) {
            return false;
          }

          return compressionConfig.checkMimeType(contentType);
        }

      default:
        throw new IllegalStateException("Unknown mode");
    }
  }

  // ------------------------------------------------ Configuration Properties

  /** @return the maximum time, in seconds, a file may be cached. */
  public int getSecondsMaxAge() {
    return secondsMaxAge;
  }

  /**
   * Sets the maximum time, in seconds, a file may be cached.
   *
   * @param secondsMaxAge max age of a cached file, in seconds.
   */
  public void setSecondsMaxAge(int secondsMaxAge) {
    this.secondsMaxAge = secondsMaxAge;
  }

  /** @return the maximum number of files that may be cached. */
  public int getMaxCacheEntries() {
    return maxCacheEntries;
  }

  /**
   * Sets the maximum number of files that may be cached.
   *
   * @param maxCacheEntries the maximum number of files that may be cached.
   */
  public void setMaxCacheEntries(int maxCacheEntries) {
    this.maxCacheEntries = maxCacheEntries;
  }

  /** @return the minimum size, in bytes, a file must be in order to be cached in the heap cache. */
  public long getMinEntrySize() {
    return minEntrySize;
  }

  /**
   * The maximum size, in bytes, a file must be in order to be cached in the heap cache.
   *
   * @param minEntrySize the maximum size, in bytes, a file must be in order to be cached in the
   *     heap cache.
   */
  public void setMinEntrySize(long minEntrySize) {
    this.minEntrySize = minEntrySize;
  }

  /**
   * @return the maximum size, in bytes, a resource may be before it can no longer be considered
   *     cacheable.
   */
  public long getMaxEntrySize() {
    return maxEntrySize;
  }

  /**
   * The maximum size, in bytes, a resource may be before it can no longer be considered cacheable.
   *
   * @param maxEntrySize the maximum size, in bytes, a resource may be before it can no longer be
   *     considered cacheable.
   */
  public void setMaxEntrySize(long maxEntrySize) {
    this.maxEntrySize = maxEntrySize;
  }

  /** @return the maximum size of the memory mapped cache for large files. */
  public long getMaxLargeFileCacheSize() {
    return maxLargeFileCacheSize;
  }

  /**
   * Sets the maximum size, in bytes, of the memory mapped cache for large files.
   *
   * @param maxLargeFileCacheSize the maximum size, in bytes, of the memory mapped cache for large
   *     files.
   */
  public void setMaxLargeFileCacheSize(long maxLargeFileCacheSize) {
    this.maxLargeFileCacheSize = maxLargeFileCacheSize;
  }

  /**
   * @return the maximum size, in bytes, of the heap cache for files below the water mark set by
   *     {@link #getMinEntrySize()}.
   */
  public long getMaxSmallFileCacheSize() {
    return maxSmallFileCacheSize;
  }

  /**
   * The maximum size, in bytes, of the heap cache for files below the water mark set by {@link
   * #getMinEntrySize()}.
   *
   * @param maxSmallFileCacheSize the maximum size, in bytes, of the heap cache for files below the
   *     water mark set by {@link #getMinEntrySize()}.
   */
  public void setMaxSmallFileCacheSize(long maxSmallFileCacheSize) {
    this.maxSmallFileCacheSize = maxSmallFileCacheSize;
  }

  /** @return <code>true</code> if the {@link FileCache} is enabled, otherwise <code>false</code> */
  public boolean isEnabled() {
    return enabled;
  }

  /**
   * Enables/disables the {@link FileCache}. By default, the {@link FileCache} is disabled.
   *
   * @param enabled <code>true</code> to enable the {@link FileCache}.
   */
  public void setEnabled(boolean enabled) {
    this.enabled = enabled;
  }

  /** Returns the <tt>FileCache</tt> compression configuration settings. */
  public CompressionConfig getCompressionConfig() {
    return compressionConfig;
  }

  /** Returns the folder to be used to store temporary compressed files. */
  public File getCompressedFilesFolder() {
    return compressedFilesFolder;
  }

  /** Sets the folder to be used to store temporary compressed files. */
  public void setCompressedFilesFolder(final File compressedFilesFolder) {
    this.compressedFilesFolder = compressedFilesFolder != null ? compressedFilesFolder : TMP_DIR;
  }

  /**
   * Returns <code>true</code> if File resources may be be sent using {@link
   * java.nio.channels.FileChannel#transferTo(long, long, java.nio.channels.WritableByteChannel)}.
   *
   * <p>
   *
   * <p>By default, this property will be true, except in the following cases:
   *
   * <p>
   *
   * <ul>
   *   <li>JVM OS is HP-UX
   *   <li>JVM OS is Linux, and the Oracle JVM in use is 1.6.0_17 or older
   * </ul>
   *
   * <p>
   *
   * <p>
   *
   * <p>Finally, if the connection between endpoints is secure, send file functionality will be
   * disabled regardless of configuration.
   *
   * @return <code>true</code> if resources will be sent using {@link
   *     java.nio.channels.FileChannel#transferTo(long, long,
   *     java.nio.channels.WritableByteChannel)}.
   * @since 2.3.5
   */
  public boolean isFileSendEnabled() {
    return fileSendEnabled;
  }

  /**
   * Configure whether or send-file support will enabled which allows sending {@link java.io.File}
   * resources via {@link java.nio.channels.FileChannel#transferTo(long, long,
   * java.nio.channels.WritableByteChannel)}. If disabled, the more traditional byte[] copy will be
   * used to send content.
   *
   * @param fileSendEnabled <code>true</code> to enable {@link
   *     java.nio.channels.FileChannel#transferTo(long, long,
   *     java.nio.channels.WritableByteChannel)} support.
   * @since 2.3.5
   */
  public void setFileSendEnabled(boolean fileSendEnabled) {
    this.fileSendEnabled = fileSendEnabled;
  }

  /** Creates a temporary compressed representation of the given cache entry. */
  protected void compressFile(final FileCacheEntry entry) {
    try {
      final File tmpCompressedFile =
          File.createTempFile(
              String.valueOf(entry.plainFile.hashCode()), ".tmpzip", compressedFilesFolder);
      tmpCompressedFile.deleteOnExit();

      InputStream in = null;
      OutputStream out = null;
      try {
        in = new FileInputStream(entry.plainFile);
        out = new GZIPOutputStream(new FileOutputStream(tmpCompressedFile));

        final byte[] tmp = new byte[1024];

        do {
          final int readNow = in.read(tmp);
          if (readNow == -1) {
            break;
          }

          out.write(tmp, 0, readNow);
        } while (true);
      } finally {
        if (in != null) {
          try {
            in.close();
          } catch (IOException ignored) {
          }
        }
        if (out != null) {
          try {
            out.close();
          } catch (IOException ignored) {
          }
        }
      }

      final long size = tmpCompressedFile.length();

      switch (entry.type) {
        case HEAP:
        case MAPPED:
          {
            final FileInputStream cFis = new FileInputStream(tmpCompressedFile);

            try {
              final FileChannel cFileChannel = cFis.getChannel();

              final MappedByteBuffer compressedBb =
                  cFileChannel.map(FileChannel.MapMode.READ_ONLY, 0, size);

              if (entry.type == CacheType.HEAP) {
                compressedBb.load();
              }

              entry.compressedBb = compressedBb;
            } finally {
              cFis.close();
            }

            break;
          }
        case FILE:
          {
            break;
          }

        default:
          throw new IllegalStateException("The type is not supported: " + entry.type);
      }

      entry.compressedFileSize = size;
      entry.compressedFile = tmpCompressedFile;
    } catch (IOException e) {
      LOGGER.log(Level.FINE, "Can not compress file: " + entry.plainFile, e);
    }
  }

  // ---------------------------------------------------- Monitoring --------//

  protected final long addHeapSize(long size) {
    return heapSize.addAndGet(size);
  }

  protected final long subHeapSize(long size) {
    return heapSize.addAndGet(-size);
  }

  /**
   * Return the heap space used for cache
   *
   * @return heap size
   */
  public long getHeapCacheSize() {
    return heapSize.get();
  }

  protected final long addMappedMemorySize(long size) {
    return mappedMemorySize.addAndGet(size);
  }

  protected final long subMappedMemorySize(long size) {
    return mappedMemorySize.addAndGet(-size);
  }

  /**
   * Return the size of Mapped memory used for caching
   *
   * @return Mapped memory size
   */
  public long getMappedCacheSize() {
    return mappedMemorySize.get();
  }

  /**
   * Check if the conditions specified in the optional If headers are satisfied.
   *
   * @return {@link HttpStatus} if the decision has been made and the response status has been
   *     defined, or <tt>null</tt> otherwise
   */
  private HttpStatus checkIfHeaders(final FileCacheEntry entry, final HttpRequestPacket request)
      throws IOException {

    HttpStatus httpStatus = checkIfMatch(entry, request);
    if (httpStatus == null) {
      httpStatus = checkIfModifiedSince(entry, request);
      if (httpStatus == null) {
        httpStatus = checkIfNoneMatch(entry, request);
        if (httpStatus == null) {
          httpStatus = checkIfUnmodifiedSince(entry, request);
        }
      }
    }

    return httpStatus;
  }

  /**
   * Check if the if-modified-since condition is satisfied.
   *
   * @return {@link HttpStatus} if the decision has been made and the response status has been
   *     defined, or <tt>null</tt> otherwise
   */
  private HttpStatus checkIfModifiedSince(
      final FileCacheEntry entry, final HttpRequestPacket request) throws IOException {
    try {
      final String reqModified = request.getHeader(Header.IfModifiedSince);
      if (reqModified != null) {
        // optimization - assume the String value sent in the
        // client's If-Modified-Since header is the same as what
        // was originally sent
        if (reqModified.equals(entry.lastModifiedHeader)) {
          return HttpStatus.NOT_MODIFIED_304;
        }
        long headerValue = convertToLong(reqModified);
        if (headerValue != -1) {
          long lastModified = entry.lastModified;
          // If an If-None-Match header has been specified,
          // If-Modified-Since is ignored.
          if ((request.getHeader(Header.IfNoneMatch) == null)
              && (lastModified - headerValue <= 1000)) {
            // The entity has not been modified since the date
            // specified by the client. This is not an error case.
            return HttpStatus.NOT_MODIFIED_304;
          }
        }
      }
    } catch (IllegalArgumentException illegalArgument) {
      notifyProbesError(this, illegalArgument);
    }

    return null;
  }

  /**
   * Check if the if-none-match condition is satisfied.
   *
   * @return {@link HttpStatus} if the decision has been made and the response status has been
   *     defined, or <tt>null</tt> otherwise
   */
  private HttpStatus checkIfNoneMatch(final FileCacheEntry entry, final HttpRequestPacket request)
      throws IOException {

    String headerValue = request.getHeader(Header.IfNoneMatch);
    if (headerValue != null) {
      String eTag = entry.Etag;

      boolean conditionSatisfied = false;

      if (!headerValue.equals("*")) {

        StringTokenizer commaTokenizer = new StringTokenizer(headerValue, ",");

        while (!conditionSatisfied && commaTokenizer.hasMoreTokens()) {
          String currentToken = commaTokenizer.nextToken();
          if (currentToken.trim().equals(eTag)) {
            conditionSatisfied = true;
          }
        }

      } else {
        conditionSatisfied = true;
      }
      if (conditionSatisfied) {

        // For GET and HEAD, we should respond with
        // 304 Not Modified.
        // For every other method, 412 Precondition Failed is sent
        // back.
        final Method method = request.getMethod();
        if (Method.GET.equals(method) || Method.HEAD.equals(method)) {
          return HttpStatus.NOT_MODIFIED_304;
        } else {
          return HttpStatus.PRECONDITION_FAILED_412;
        }
      }
    }

    return null;
  }

  /**
   * Check if the if-unmodified-since condition is satisfied.
   *
   * @return {@link HttpStatus} if the decision has been made and the response status has been
   *     defined, or <tt>null</tt> otherwise
   */
  private HttpStatus checkIfUnmodifiedSince(
      final FileCacheEntry entry, final HttpRequestPacket request) throws IOException {

    try {
      long lastModified = entry.lastModified;
      String h = request.getHeader(Header.IfUnmodifiedSince);
      if (h != null) {
        // optimization - assume the String value sent in the
        // client's If-Unmodified-Since header is the same as what
        // was originally sent
        if (h.equals(entry.lastModifiedHeader)) {
          // The entity has not been modified since the date
          // specified by the client. This is not an error case.
          return HttpStatus.PRECONDITION_FAILED_412;
        }
        long headerValue = convertToLong(h);
        if (headerValue != -1) {
          if (headerValue - lastModified <= 1000) {
            // The entity has not been modified since the date
            // specified by the client. This is not an error case.
            return HttpStatus.PRECONDITION_FAILED_412;
          }
        }
      }
    } catch (IllegalArgumentException illegalArgument) {
      notifyProbesError(this, illegalArgument);
    }

    return null;
  }

  /**
   * Check if the if-match condition is satisfied.
   *
   * @param request The servlet request we are processing
   * @param entry the FileCacheEntry to validate
   * @return {@link HttpStatus} if the decision has been made and the response status has been
   *     defined, or <tt>null</tt> otherwise
   */
  private HttpStatus checkIfMatch(final FileCacheEntry entry, final HttpRequestPacket request)
      throws IOException {

    String headerValue = request.getHeader(Header.IfMatch);
    if (headerValue != null) {
      if (headerValue.indexOf('*') == -1) {
        String eTag = entry.Etag;

        StringTokenizer commaTokenizer = new StringTokenizer(headerValue, ",");
        boolean conditionSatisfied = false;

        while (!conditionSatisfied && commaTokenizer.hasMoreTokens()) {
          String currentToken = commaTokenizer.nextToken();
          if (currentToken.trim().equals(eTag)) {
            conditionSatisfied = true;
          }
        }

        // If none of the given ETags match, 412 Precondition failed is
        // sent back
        if (!conditionSatisfied) {
          return HttpStatus.PRECONDITION_FAILED_412;
        }
      }
    }

    return null;
  }

  /** {@inheritDoc} */
  @Override
  public MonitoringConfig<FileCacheProbe> getMonitoringConfig() {
    return monitoringConfig;
  }

  /**
   * Notify registered {@link FileCacheProbe}s about the "entry added" event.
   *
   * @param fileCache the <tt>FileCache</tt> event occurred on.
   * @param entry entry been added
   */
  protected static void notifyProbesEntryAdded(
      final FileCache fileCache, final FileCacheEntry entry) {
    final FileCacheProbe[] probes = fileCache.monitoringConfig.getProbesUnsafe();
    if (probes != null) {
      for (FileCacheProbe probe : probes) {
        probe.onEntryAddedEvent(fileCache, entry);
      }
    }
  }

  /**
   * Notify registered {@link FileCacheProbe}s about the "entry removed" event.
   *
   * @param fileCache the <tt>FileCache</tt> event occurred on.
   * @param entry entry been removed
   */
  protected static void notifyProbesEntryRemoved(
      final FileCache fileCache, final FileCacheEntry entry) {
    final FileCacheProbe[] probes = fileCache.monitoringConfig.getProbesUnsafe();
    if (probes != null) {
      for (FileCacheProbe probe : probes) {
        probe.onEntryRemovedEvent(fileCache, entry);
      }
    }
  }

  /**
   * Notify registered {@link FileCacheProbe}s about the "entry hit event.
   *
   * @param fileCache the <tt>FileCache</tt> event occurred on.
   * @param entry entry been hit.
   */
  protected static void notifyProbesEntryHit(
      final FileCache fileCache, final FileCacheEntry entry) {
    final FileCacheProbe[] probes = fileCache.monitoringConfig.getProbesUnsafe();
    if (probes != null) {
      for (FileCacheProbe probe : probes) {
        probe.onEntryHitEvent(fileCache, entry);
      }
    }
  }

  /**
   * Notify registered {@link FileCacheProbe}s about the "entry missed" event.
   *
   * @param fileCache the <tt>FileCache</tt> event occurred on.
   * @param request HTTP request.
   */
  protected static void notifyProbesEntryMissed(
      final FileCache fileCache, final HttpRequestPacket request) {

    final FileCacheProbe[] probes = fileCache.monitoringConfig.getProbesUnsafe();
    if (probes != null && probes.length > 0) {
      for (FileCacheProbe probe : probes) {
        probe.onEntryMissedEvent(
            fileCache, request.getHeader(Header.Host), request.getRequestURI());
      }
    }
  }

  /**
   * Notify registered {@link FileCacheProbe}s about the error.
   *
   * @param fileCache the <tt>FileCache</tt> event occurred on.
   */
  protected static void notifyProbesError(final FileCache fileCache, final Throwable error) {
    final FileCacheProbe[] probes = fileCache.monitoringConfig.getProbesUnsafe();
    if (probes != null) {
      for (FileCacheProbe probe : probes) {
        probe.onErrorEvent(fileCache, error);
      }
    }
  }

  protected static long convertToLong(final String dateHeader) {

    if (dateHeader == null) return (-1L);

    final SimpleDateFormats formats = SimpleDateFormats.create();

    try {
      // Attempt to convert the date header in a variety of formats
      long result = FastHttpDateFormat.parseDate(dateHeader, formats.getFormats());
      if (result != (-1L)) {
        return result;
      }
      throw new IllegalArgumentException(dateHeader);
    } finally {
      formats.recycle();
    }
  }

  private static class EntryWorker implements DelayedExecutor.Worker<FileCacheEntry> {
    @Override
    public boolean doWork(final FileCacheEntry element) {
      element.run();
      return true;
    }
  }

  private static class EntryResolver implements DelayedExecutor.Resolver<FileCacheEntry> {

    @Override
    public boolean removeTimeout(FileCacheEntry element) {
      if (element.timeoutMillis != -1) {
        element.timeoutMillis = -1;
        return true;
      }

      return false;
    }

    @Override
    public long getTimeoutMillis(FileCacheEntry element) {
      return element.timeoutMillis;
    }

    @Override
    public void setTimeoutMillis(FileCacheEntry element, long timeoutMillis) {
      element.timeoutMillis = timeoutMillis;
    }
  }
}
/** @since 2.2.11 */
public class JdkVersion implements Comparable<JdkVersion> {
  private static final Logger LOGGER = Grizzly.logger(JdkVersion.class);

  // take max 4 parts of the JDK version and cut the rest (usually the build number)
  private static final Pattern VERSION_PATTERN =
      Pattern.compile("([0-9]+)(\\.([0-9]+))?(\\.([0-9]+))?([_\\.]([0-9]+))?.*");

  private static final boolean IS_UNSAFE_SUPPORTED;

  static {
    boolean isUnsafeFound;

    try {
      isUnsafeFound = Class.forName("sun.misc.Unsafe") != null;
    } catch (Throwable t) {
      isUnsafeFound = false;
    }

    IS_UNSAFE_SUPPORTED = isUnsafeFound;
  }

  private static final JdkVersion UNKNOWN_VERSION = new JdkVersion(-1, -1, -1, -1);
  private static final JdkVersion JDK_VERSION = parseVersion(System.getProperty("java.version"));

  private final int major;
  private final int minor;
  private final int maintenance;
  private final int update;

  // ------------------------------------------------------------ Constructors

  private JdkVersion(final int major, final int minor, final int maintenance, final int update) {
    this.major = major;
    this.minor = minor;
    this.maintenance = maintenance;
    this.update = update;
  }

  // ---------------------------------------------------------- Public Methods

  public static JdkVersion parseVersion(final String versionString) {

    try {
      final Matcher matcher = VERSION_PATTERN.matcher(versionString);
      if (matcher.matches()) {
        return new JdkVersion(
            parseInt(matcher.group(1)),
            parseInt(matcher.group(3)),
            parseInt(matcher.group(5)),
            parseInt(matcher.group(7)));
      }

      LOGGER.log(Level.FINE, "Can't parse the JDK version {0}", versionString);

    } catch (Exception e) {
      LOGGER.log(Level.FINE, "Error parsing the JDK version " + versionString, e);
    }

    return UNKNOWN_VERSION;
  }

  public static JdkVersion getJdkVersion() {
    return JDK_VERSION;
  }

  @SuppressWarnings("UnusedDeclaration")
  public int getMajor() {
    return major;
  }

  @SuppressWarnings("UnusedDeclaration")
  public int getMinor() {
    return minor;
  }

  @SuppressWarnings("UnusedDeclaration")
  public int getMaintenance() {
    return maintenance;
  }

  @SuppressWarnings("UnusedDeclaration")
  public int getUpdate() {
    return update;
  }

  /**
   * @return <tt>true</tt> if <code>sun.misc.Unsafe</code> is present in the current JDK version, or
   *     <tt>false</tt> otherwise.
   * @since 2.3.6
   */
  public boolean isUnsafeSupported() {
    return IS_UNSAFE_SUPPORTED;
  }

  @Override
  public String toString() {
    final StringBuilder sb = new StringBuilder();
    sb.append("JdkVersion");
    sb.append("{major=").append(major);
    sb.append(", minor=").append(minor);
    sb.append(", maintenance=").append(maintenance);
    sb.append(", update=").append(update);
    sb.append('}');
    return sb.toString();
  }

  // ------------------------------------------------- Methods from Comparable

  public int compareTo(String versionString) {
    return compareTo(JdkVersion.parseVersion(versionString));
  }

  @Override
  public int compareTo(JdkVersion otherVersion) {
    if (major < otherVersion.major) {
      return -1;
    }
    if (major > otherVersion.major) {
      return 1;
    }
    if (minor < otherVersion.minor) {
      return -1;
    }
    if (minor > otherVersion.minor) {
      return 1;
    }
    if (maintenance < otherVersion.maintenance) {
      return -1;
    }
    if (maintenance > otherVersion.maintenance) {
      return 1;
    }
    if (update < otherVersion.update) {
      return -1;
    }
    if (update > otherVersion.update) {
      return 1;
    }
    return 0;
  }

  private static int parseInt(final String s) {
    return s != null ? Integer.parseInt(s) : 0;
  }
}
/**
 * Abstraction exposing both byte and character methods to read content from the HTTP messaging
 * system in Grizzly.
 */
public class InputBuffer {
  private static final Logger LOGGER = Grizzly.logger(InputBuffer.class);
  private static final Level LOGGER_LEVEL = Level.FINER;

  /**
   * The {@link org.glassfish.grizzly.http.HttpHeader} associated with this <code>InputBuffer</code>
   */
  private HttpHeader httpHeader;

  /**
   * The {@link FilterChainContext} associated with this <code>InputBuffer</code>. The {@link
   * FilterChainContext} will be using to read <code>POST</code> data in blocking fashion.
   */
  private FilterChainContext ctx;

  /** Flag indicating whether or not this <code>InputBuffer</code> is processing character data. */
  private boolean processingChars;

  /** Flag indicating whether or not this <code>InputBuffer</code> has been closed. */
  private boolean closed;

  /** The {@link Buffer} consisting of the bytes from the HTTP message chunk(s). */
  private Buffer inputContentBuffer;

  /**
   * The {@link Connection} associated with this {@link
   * org.glassfish.grizzly.http.HttpRequestPacket}.
   */
  private Connection connection;

  /**
   * The mark position within the current binary content. Marking is not supported for character
   * content.
   */
  private int markPos = -1;

  /** Represents how many bytes can be read before {@link #markPos} is invalidated. */
  private int readAheadLimit = -1;

  /**
   * Counter to track how many bytes have been read. This counter is only used with the byte stream
   * has been marked.
   */
  private int readCount = 0;

  /**
   * The default character encoding to use if none is specified by the {@link
   * org.glassfish.grizzly.http.HttpRequestPacket}.
   */
  private String encoding = DEFAULT_HTTP_CHARACTER_ENCODING;

  /** The {@link CharsetDecoder} used to convert binary to character data. */
  private CharsetDecoder decoder;

  /** CharsetDecoders cache */
  private final Map<String, CharsetDecoder> decoders = new HashMap<String, CharsetDecoder>();

  /** Flag indicating all request content has been read. */
  private boolean contentRead;

  /** The {@link ReadHandler} to be notified as content is read. */
  private ReadHandler handler;

  /** The length of the content that must be read before notifying the {@link ReadHandler}. */
  private int requestedSize;

  /** {@link CharBuffer} for converting a single character at a time. */
  private final CharBuffer singleCharBuf =
      (CharBuffer) CharBuffer.allocate(1).position(1); // create CharBuffer w/ 0 chars remaining

  /** Used to estimate how many characters can be produced from a variable number of bytes. */
  private float averageCharsPerByte = 1.0f;

  /**
   * Flag shows if we're currently waiting for input data asynchronously (is OP_READ enabled for the
   * Connection)
   */
  private boolean isWaitingDataAsynchronously;

  // ------------------------------------------------------------ Constructors

  /**
   * Per-request initialization required for the InputBuffer to function properly.
   *
   * @param httpHeader the header from which input will be obtained.
   * @param ctx the FilterChainContext for the chain processing this request
   */
  public void initialize(final HttpHeader httpHeader, final FilterChainContext ctx) {

    if (ctx == null) {
      throw new IllegalArgumentException("ctx cannot be null.");
    }
    this.httpHeader = httpHeader;

    this.ctx = ctx;
    connection = ctx.getConnection();
    final Object message = ctx.getMessage();
    if (message instanceof HttpContent) {
      final HttpContent content = (HttpContent) message;

      // Check if HttpContent is chunked message trailer w/ headers
      checkHttpTrailer(content);
      updateInputContentBuffer(content.getContent());
      contentRead = content.isLast();
      content.recycle();

      if (LOGGER.isLoggable(LOGGER_LEVEL)) {
        log("InputBuffer %s initialize with ready content: %s", this, inputContentBuffer);
      }
    }
  }

  /**
   * Set the default character encoding for this <tt>InputBuffer</tt>, which would be applied if no
   * encoding was explicitly set on HTTP {@link org.glassfish.grizzly.http.HttpRequestPacket} and
   * character decoding wasn't started yet.
   */
  @SuppressWarnings("UnusedDeclaration")
  public void setDefaultEncoding(final String encoding) {
    this.encoding = encoding;
  }

  /** Recycle this <code>InputBuffer</code> for reuse. */
  public void recycle() {

    inputContentBuffer.tryDispose();
    inputContentBuffer = null;

    singleCharBuf.position(singleCharBuf.limit());

    connection = null;
    decoder = null;
    ctx = null;
    handler = null;

    processingChars = false;
    closed = false;
    contentRead = false;

    markPos = -1;
    readAheadLimit = -1;
    requestedSize = -1;
    readCount = 0;

    averageCharsPerByte = 1.0f;

    isWaitingDataAsynchronously = false;

    encoding = DEFAULT_HTTP_CHARACTER_ENCODING;
  }

  /**
   * This method should be called if the InputBuffer is being used in conjunction with a {@link
   * java.io.Reader} implementation. If this method is not called, any character-based methods
   * called on this <code>InputBuffer</code> will throw a {@link IllegalStateException}.
   */
  public void processingChars() {

    if (!processingChars) {
      processingChars = true;
      final String enc = httpHeader.getCharacterEncoding();
      if (enc != null) {
        encoding = enc;
        final CharsetDecoder localDecoder = getDecoder();
        averageCharsPerByte = localDecoder.averageCharsPerByte();
      }
    }
  }

  // --------------------------------------------- InputStream-Related Methods

  /**
   * This method always blocks.
   *
   * @see java.io.InputStream#read()
   */
  public int readByte() throws IOException {
    if (LOGGER.isLoggable(LOGGER_LEVEL)) {
      log("InputBuffer %s readByte. Ready content: %s", this, inputContentBuffer);
    }

    if (closed) {
      throw new IOException();
    }
    if (!inputContentBuffer.hasRemaining()) {
      if (fill(1) == -1) {
        return -1;
      }
    }

    checkMarkAfterRead(1);
    return inputContentBuffer.get() & 0xFF;
  }

  /** @see java.io.InputStream#read(byte[], int, int) */
  public int read(final byte b[], final int off, final int len) throws IOException {
    if (LOGGER.isLoggable(LOGGER_LEVEL)) {
      log(
          "InputBuffer %s read byte array of len: %s. Ready content: %s",
          this, len, inputContentBuffer);
    }

    if (closed) {
      throw new IOException();
    }

    if (len == 0) {
      return 0;
    }
    if (!inputContentBuffer.hasRemaining()) {
      if (fill(1) == -1) {
        return -1;
      }
    }

    int nlen = Math.min(inputContentBuffer.remaining(), len);
    inputContentBuffer.get(b, off, nlen);

    if (!checkMarkAfterRead(nlen)) {
      inputContentBuffer.shrink();
    }

    return nlen;
  }

  /**
   * Depending on the <tt>InputBuffer</tt> mode, method will return either number of available bytes
   * or characters ready to be read without blocking.
   *
   * @return depending on the <tt>InputBuffer</tt> mode, method will return either number of
   *     available bytes or characters ready to be read without blocking.
   */
  public int readyData() {
    if (closed) return 0;

    return ((processingChars) ? availableChar() : available());
  }

  /** @see java.io.InputStream#available() */
  public int available() {

    return ((closed) ? 0 : inputContentBuffer.remaining());
  }

  /**
   * Returns the duplicate of the underlying {@link Buffer} used to buffer incoming request data.
   * The content of the returned buffer will be that of the underlying buffer. Changes to returned
   * buffer's content will be visible in the underlying buffer, and vice versa; the two buffers'
   * position, limit, and mark values will be independent.
   *
   * @return the duplicate of the underlying {@link Buffer} used to buffer incoming request data.
   */
  public Buffer getBuffer() {
    if (LOGGER.isLoggable(LOGGER_LEVEL)) {
      log("InputBuffer %s getBuffer. Ready content: %s", this, inputContentBuffer);
    }

    return inputContentBuffer.duplicate();
  }

  /**
   * @return the underlying {@link Buffer} used to buffer incoming request data. Unlike {@link
   *     #getBuffer()}, this method detaches the returned {@link Buffer}, so user code becomes
   *     responsible for handling the {@link Buffer}.
   */
  public Buffer readBuffer() {
    if (LOGGER.isLoggable(LOGGER_LEVEL)) {
      log("InputBuffer %s readBuffer. Ready content: %s", this, inputContentBuffer);
    }

    return readBuffer(inputContentBuffer.remaining());
  }

  /**
   * @param size the requested size of the {@link Buffer} to be returned.
   * @return the {@link Buffer} of a given size, which represents a chunk of the underlying {@link
   *     Buffer} which contains incoming request data. This method detaches the returned {@link
   *     Buffer}, so user code becomes responsible for handling its life-cycle.
   */
  public Buffer readBuffer(final int size) {
    if (LOGGER.isLoggable(LOGGER_LEVEL)) {
      log(
          "InputBuffer %s readBuffer(size), size: %s. Ready content: %s",
          this, size, inputContentBuffer);
    }

    final int remaining = inputContentBuffer.remaining();
    if (size > remaining) {
      throw new IllegalStateException("Can not read more bytes than available");
    }

    final Buffer buffer;
    if (size == remaining) {
      buffer = inputContentBuffer;
      inputContentBuffer = Buffers.EMPTY_BUFFER;
    } else {
      final Buffer tmpBuffer = inputContentBuffer.split(inputContentBuffer.position() + size);
      buffer = inputContentBuffer;
      inputContentBuffer = tmpBuffer;
    }

    return buffer;
  }

  /** @return the {@link ReadHandler} current in use, if any. */
  public ReadHandler getReadHandler() {
    return handler;
  }

  // -------------------------------------------------- Reader-Related Methods

  /** @see java.io.Reader#read(java.nio.CharBuffer) */
  public int read(final CharBuffer target) throws IOException {
    if (LOGGER.isLoggable(LOGGER_LEVEL)) {
      log("InputBuffer %s read(CharBuffer). Ready content: %s", this, inputContentBuffer);
    }

    if (closed) {
      throw new IOException();
    }
    if (!processingChars) {
      throw new IllegalStateException();
    }
    if (target == null) {
      throw new IllegalArgumentException("target cannot be null.");
    }
    final int read = fillChars(1, target);
    checkMarkAfterRead(read);

    return read;
  }

  /** @see java.io.Reader#read() */
  public int readChar() throws IOException {
    if (LOGGER.isLoggable(LOGGER_LEVEL)) {
      log("InputBuffer %s readChar. Ready content: %s", this, inputContentBuffer);
    }

    if (closed) {
      throw new IOException();
    }
    if (!processingChars) {
      throw new IllegalStateException();
    }

    if (!singleCharBuf.hasRemaining()) {
      singleCharBuf.clear();
      int read = read(singleCharBuf);
      if (read == -1) {
        return -1;
      }
    }

    return singleCharBuf.get();
  }

  /** @see java.io.Reader#read(char[], int, int) */
  public int read(final char cbuf[], final int off, final int len) throws IOException {
    if (LOGGER.isLoggable(LOGGER_LEVEL)) {
      log(
          "InputBuffer %s read char array, len: %s. Ready content: %s",
          this, len, inputContentBuffer);
    }

    if (closed) {
      throw new IOException();
    }
    if (!processingChars) {
      throw new IllegalStateException();
    }

    if (len == 0) {
      return 0;
    }

    final CharBuffer buf = CharBuffer.wrap(cbuf, off, len);
    return read(buf);
  }

  /** @see java.io.Reader#ready() */
  public boolean ready() {

    if (closed) {
      return false;
    }
    if (!processingChars) {
      throw new IllegalStateException();
    }
    return (inputContentBuffer.hasRemaining() || httpHeader.isExpectContent());
  }

  /**
   * /** Fill the buffer (blocking) up to the requested length.
   *
   * @param length how much content should attempt to buffer, <code>-1</code> means buffer entire
   *     request.
   * @throws IOException
   */
  public void fillFully(final int length) throws IOException {
    if (LOGGER.isLoggable(LOGGER_LEVEL)) {
      log("InputBuffer %s fillFully, len: %s. Ready content: %s", this, length, inputContentBuffer);
    }

    if (length == 0) return;

    if (length > 0) {
      final int remaining = length - inputContentBuffer.remaining();

      if (remaining > 0) {
        fill(remaining);
      }
    } else {
      fill(-1);
    }
  }

  public int availableChar() {

    if (!singleCharBuf.hasRemaining()) {
      // fill the singleCharBuf to make sure we have at least one char
      singleCharBuf.clear();
      if (fillAvailableChars(1, singleCharBuf) == 0) {
        singleCharBuf.position(singleCharBuf.limit());
        return 0;
      }

      singleCharBuf.flip();
    }

    // we have 1 char pre-decoded + estimation for the rest byte[]->char[] count.
    return 1 + ((int) (inputContentBuffer.remaining() * averageCharsPerByte));
  }

  // ---------------------------------------------------- Common Input Methods

  /**
   * Supported with binary and character data.
   *
   * @see java.io.InputStream#mark(int)
   * @see java.io.Reader#mark(int)
   */
  public void mark(final int readAheadLimit) {

    if (readAheadLimit > 0) {
      markPos = inputContentBuffer.position();
      readCount = 0;
      this.readAheadLimit = readAheadLimit;
    }
  }

  /**
   * Only supported with binary data.
   *
   * @see java.io.InputStream#markSupported()
   */
  public boolean markSupported() {

    if (processingChars) {
      throw new IllegalStateException();
    }
    return true;
  }

  /**
   * Only supported with binary data.
   *
   * @see java.io.InputStream#reset()
   */
  public void reset() throws IOException {

    if (closed) {
      throw new IOException();
    }

    if (readAheadLimit == -1) {
      throw new IOException("Mark not set");
    }

    readCount = 0;
    inputContentBuffer.position(markPos);
  }

  /** @see java.io.Closeable#close() */
  public void close() throws IOException {

    closed = true;
  }

  /**
   * Skips the specified number of bytes/characters.
   *
   * @see java.io.InputStream#skip(long)
   * @see java.io.Reader#skip(long)
   * @deprecated pls. use {@link #skip(long)}, the <tt>block</tt> parameter will be ignored
   */
  public long skip(final long n, @SuppressWarnings("UnusedParameters") final boolean block)
      throws IOException {
    return skip(n);
  }

  /**
   * Skips the specified number of bytes/characters.
   *
   * @see java.io.InputStream#skip(long)
   * @see java.io.Reader#skip(long)
   */
  public long skip(final long n) throws IOException {
    if (LOGGER.isLoggable(LOGGER_LEVEL)) {
      log("InputBuffer %s skip %s bytes. Ready content: %s", this, n, inputContentBuffer);
    }

    if (closed) {
      throw new IOException();
    }

    if (!processingChars) {
      if (n <= 0) {
        return 0L;
      }

      if (!inputContentBuffer.hasRemaining()) {
        if (fill((int) n) == -1) {
          return -1;
        }
      }
      if (inputContentBuffer.remaining() < n) {
        fill((int) n);
      }

      long nlen = Math.min(inputContentBuffer.remaining(), n);
      inputContentBuffer.position(inputContentBuffer.position() + (int) nlen);

      if (!checkMarkAfterRead(n)) {
        inputContentBuffer.shrink();
      }

      return nlen;
    } else {
      if (n < 0) { // required by java.io.Reader.skip()
        throw new IllegalArgumentException();
      }
      if (n == 0) {
        return 0L;
      }
      final CharBuffer skipBuffer = CharBuffer.allocate((int) n);
      if (fillChars((int) n, skipBuffer) == -1) {
        return 0;
      }
      return Math.min(skipBuffer.remaining(), n);
    }
  }

  /**
   * When invoked, this method will call {@link org.glassfish.grizzly.ReadHandler#onAllDataRead()}
   * on the current {@link ReadHandler} (if any).
   *
   * <p>This method shouldn't be invoked by developers directly.
   */
  protected void finished() {
    if (!contentRead) {
      contentRead = true;
      final ReadHandler localHandler = handler;
      if (localHandler != null) {
        handler = null;
        invokeHandlerAllRead(localHandler, getThreadPool());
      }
    }
  }

  private void finishedInTheCurrentThread(final ReadHandler readHandler) {
    if (!contentRead) {
      contentRead = true;
      if (readHandler != null) {
        invokeHandlerAllRead(readHandler, null);
      }
    }
  }

  private void invokeHandlerAllRead(final ReadHandler readHandler, final Executor executor) {
    if (executor != null) {
      executor.execute(
          new Runnable() {
            @Override
            public void run() {
              try {
                readHandler.onAllDataRead();
              } catch (Throwable t) {
                readHandler.onError(t);
              }
            }
          });
    } else {
      try {
        readHandler.onAllDataRead();
      } catch (Throwable t) {
        readHandler.onError(t);
      }
    }
  }

  public void replayPayload(final Buffer buffer) {
    if (!isFinished()) {
      throw new IllegalStateException("Can't replay when InputBuffer is not closed");
    }

    if (LOGGER.isLoggable(LOGGER_LEVEL)) {
      log("InputBuffer %s replayPayload to %s", this, buffer);
    }

    closed = false;
    readCount = 0;

    readAheadLimit = -1;
    markPos = -1;

    inputContentBuffer = buffer;
  }

  /**
   * @return <code>true</code> if all request data has been read, otherwise returns <code>false
   *     </code>.
   */
  public boolean isFinished() {
    return contentRead;
  }

  /**
   * @return <code>true</code> if this <tt>InputBuffer</tt> is closed, otherwise returns <code>false
   *     </code>.
   */
  public boolean isClosed() {
    return closed;
  }

  /**
   * Installs a {@link ReadHandler} that will be notified when any data becomes available to read
   * without blocking.
   *
   * @param handler the {@link ReadHandler} to invoke.
   * @throws IllegalArgumentException if <code>handler</code> is <code>null</code>.
   * @throws IllegalStateException if an attempt is made to register a handler before an existing
   *     registered handler has been invoked or if all request data has already been read.
   */
  public void notifyAvailable(final ReadHandler handler) {
    notifyAvailable(handler, 1);
  }

  /**
   * Installs a {@link ReadHandler} that will be notified when the specified amount of data is
   * available to be read without blocking.
   *
   * @param handler the {@link ReadHandler} to invoke.
   * @param size the minimum number of bytes that must be available before the {@link ReadHandler}
   *     is notified.
   * @throws IllegalArgumentException if <code>handler</code> is <code>null</code>, or if <code>size
   *     </code> is less or equal to zero.
   * @throws IllegalStateException if an attempt is made to register a handler before an existing
   *     registered handler has been invoked.
   */
  public void notifyAvailable(final ReadHandler handler, final int size) {

    if (handler == null) {
      throw new IllegalArgumentException("handler cannot be null.");
    }
    if (size <= 0) {
      throw new IllegalArgumentException("size should be positive integer");
    }
    if (this.handler != null) {
      throw new IllegalStateException(
          "Illegal attempt to register a new handler before the existing handler has been notified");
    }

    // If we don't expect more data - call onAllDataRead() directly
    if (closed || isFinished()) {
      try {
        handler.onAllDataRead();
      } catch (Throwable ioe) {
        handler.onError(ioe);
      }

      return;
    }

    final int available = readyData();
    if (shouldNotifyNow(size, available)) {
      try {
        handler.onDataAvailable();
      } catch (Throwable ioe) {
        handler.onError(ioe);
      }
      return;
    }

    requestedSize = size;
    this.handler = handler;

    if (!isWaitingDataAsynchronously) {
      isWaitingDataAsynchronously = true;
      initiateAsyncronousDataReceiving();
    }
  }

  /**
   * Appends the specified {@link Buffer} to the internal composite {@link Buffer}.
   *
   * @param httpContent the {@link HttpContent} to append
   * @return <code>true</code> if {@link ReadHandler} callback was invoked, otherwise returns <code>
   *     false</code>.
   * @throws IOException if an error occurs appending the {@link Buffer}
   */
  public boolean append(final HttpContent httpContent) throws IOException {

    // Stop waiting for data asynchronously and enable it again
    // only if we have a handler registered, which requirement
    // (expected size) is not met.
    isWaitingDataAsynchronously = false;

    // check if it's broken HTTP content message or not
    if (!HttpContent.isBroken(httpContent)) {
      final Buffer buffer = httpContent.getContent();

      if (closed) {
        buffer.dispose();
        return false;
      }

      final ReadHandler localHandler = handler;

      final boolean isLast = httpContent.isLast();

      // if we have a handler registered - switch the flag to true
      boolean askForMoreDataInThisThread = !isLast && localHandler != null;
      boolean invokeDataAvailable = false;

      if (buffer.hasRemaining()) {
        updateInputContentBuffer(buffer);
        if (localHandler != null) {
          final int available = readyData();
          if (available >= requestedSize) {
            invokeDataAvailable = true;
            askForMoreDataInThisThread = false;
          }
        }
      }

      if (askForMoreDataInThisThread) {
        // There is a ReadHandler registered, but it requested more
        // data to be available before we can notify it - so wait for
        // more data to come
        isWaitingDataAsynchronously = true;
        return true;
      }

      handler = null;

      if (isLast) {
        checkHttpTrailer(httpContent);
      }

      invokeHandlerOnProperThread(localHandler, invokeDataAvailable, isLast);

    } else { // broken content
      final ReadHandler localHandler = handler;
      handler = null;
      invokeErrorHandlerOnProperThread(
          localHandler, ((HttpBrokenContent) httpContent).getException());
    }

    return false;
  }

  /**
   * @return if this buffer is being used to process asynchronous data.
   * @deprecated will always return true
   */
  public boolean isAsyncEnabled() {
    //        return asyncEnabled;
    return true;
  }

  /**
   * Sets the asynchronous processing state of this <code>InputBuffer</code>.
   *
   * @param asyncEnabled <code>true</code> if this <code>InputBuffer<code>
   *  is to be used to process asynchronous request data.
   * @deprecated <tt>InputBuffer</tt> always supports async mode
   */
  @SuppressWarnings("UnusedDeclaration")
  public void setAsyncEnabled(boolean asyncEnabled) {
    //        this.asyncEnabled = asyncEnabled;
  }

  /**
   * Invoke {@link ReadHandler#onError(Throwable)} (assuming a {@link ReadHandler} is available) }
   * passing a {#link CancellationException} if the current {@link Connection} is open, or a {#link
   * EOFException} if the connection was unexpectedly closed.
   *
   * @since 2.0.1
   */
  public void terminate() {
    final ReadHandler localHandler = handler;
    if (localHandler != null) {
      handler = null;
      // call in the current thread, because otherwise handler executed
      // in the different thread may deal with recycled Request/Response objects
      localHandler.onError(connection.isOpen() ? new CancellationException() : new EOFException());
    }
  }

  /**
   * Initiates asynchronous data receiving.
   *
   * <p>This is service method, usually users don't have to call it explicitly.
   */
  public void initiateAsyncronousDataReceiving() {
    // fork the FilterChainContext execution
    // keep the current FilterChainContext suspended, but make a copy and resume it
    ctx.fork(ctx.getStopAction());
  }

  // --------------------------------------------------------- Private Methods

  /**
   * @return {@link Executor}, which will be used for notifying user registered {@link ReadHandler}.
   */
  protected Executor getThreadPool() {
    if (!Threads.isService()) {
      return null;
    }
    final ExecutorService es = connection.getTransport().getWorkerThreadPool();
    return es != null && !es.isShutdown() ? es : null;
  }

  private void invokeErrorHandlerOnProperThread(
      final ReadHandler localHandler, final Throwable error) {
    if (!closed && localHandler != null) {
      final Executor executor = getThreadPool();
      if (executor != null) {
        executor.execute(
            new Runnable() {
              @Override
              public void run() {
                localHandler.onError(error);
              }
            });
      } else {
        localHandler.onError(error);
      }
    }
  }

  private void invokeHandlerOnProperThread(
      final ReadHandler localHandler, final boolean invokeDataAvailable, final boolean isLast)
      throws IOException {
    final Executor executor = getThreadPool();

    if (executor != null) {
      executor.execute(
          new Runnable() {

            @Override
            public void run() {
              invokeHandler(localHandler, invokeDataAvailable, isLast);
            }
          });
    } else {
      invokeHandler(localHandler, invokeDataAvailable, isLast);
    }
  }

  private void invokeHandler(
      final ReadHandler localHandler, final boolean invokeDataAvailable, final boolean isLast) {
    try {
      if (invokeDataAvailable) {
        localHandler.onDataAvailable();
      }

      if (isLast) {
        finishedInTheCurrentThread(localHandler);
      }
    } catch (Throwable t) {
      localHandler.onError(t);
    }
  }

  /**
   * Read next chunk of data in this thread, block if needed.
   *
   * @return {@link HttpContent}
   * @throws IOException
   */
  protected HttpContent blockingRead() throws IOException {
    final ReadResult rr = ctx.read();
    final HttpContent c = (HttpContent) rr.getMessage();
    rr.recycle();
    return c;
  }

  /**
   * Used to add additional HTTP message chunk content to {@link #inputContentBuffer}.
   *
   * @param requestedLen how much content should attempt to be read, <code>-1</code> means read till
   *     the end of the message.
   * @return the number of bytes actually read
   * @throws IOException if an I/O error occurs while reading content
   */
  private int fill(final int requestedLen) throws IOException {

    int read = 0;
    while ((requestedLen == -1 || read < requestedLen) && httpHeader.isExpectContent()) {

      final HttpContent c = blockingRead();

      final boolean isLast = c.isLast();
      // Check if HttpContent is chunked message trailer w/ headers
      checkHttpTrailer(c);

      final Buffer b;
      try {
        b = c.getContent();
      } catch (HttpBrokenContentException e) {
        final Throwable cause = e.getCause();
        throw Exceptions.makeIOException(cause != null ? cause : e);
      }

      read += b.remaining();
      updateInputContentBuffer(b);
      c.recycle();

      if (isLast) {
        finished();
        break;
      }
    }

    if (read > 0 || requestedLen == 0) {
      return read;
    }

    return -1;
  }

  /**
   * Used to convert bytes to chars.
   *
   * @param requestedLen how much content should attempt to be read
   * @return the number of chars actually read
   * @throws IOException if an I/O error occurs while reading content
   */
  private int fillChars(final int requestedLen, final CharBuffer dst) throws IOException {

    int read = 0;

    // 1) Check pre-decoded singleCharBuf
    if (dst != singleCharBuf && singleCharBuf.hasRemaining()) {
      dst.put(singleCharBuf.get());
      read = 1;
    }

    // 2) Decode available byte[] -> char[]
    if (inputContentBuffer.hasRemaining()) {
      read += fillAvailableChars(requestedLen - read, dst);
    }

    if (read >= requestedLen) {
      dst.flip();
      return read;
    }

    // 3) If we don't expect more data - return what we've read so far
    if (!httpHeader.isExpectContent()) {
      dst.flip();
      return read > 0 ? read : -1;
    }

    // 4) Try to read more data (we may block)
    CharsetDecoder decoderLocal = getDecoder();

    boolean isNeedMoreInput =
        false; // true, if content in composite buffer is not enough to produce even 1 char
    boolean last = false;

    while (read < requestedLen && httpHeader.isExpectContent()) {

      if (isNeedMoreInput || !inputContentBuffer.hasRemaining()) {
        final HttpContent c = blockingRead();
        updateInputContentBuffer(c.getContent());
        last = c.isLast();

        c.recycle();
        isNeedMoreInput = false;
      }

      final ByteBuffer bytes = inputContentBuffer.toByteBuffer();

      final int bytesPos = bytes.position();
      final int dstPos = dst.position();

      final CoderResult result = decoderLocal.decode(bytes, dst, false);

      final int producedChars = dst.position() - dstPos;
      final int consumedBytes = bytes.position() - bytesPos;

      read += producedChars;

      if (consumedBytes > 0) {
        bytes.position(bytesPos);
        inputContentBuffer.position(inputContentBuffer.position() + consumedBytes);
        if (readAheadLimit == -1) {
          inputContentBuffer.shrink();
        }
      } else {
        isNeedMoreInput = true;
      }

      if (last || result == CoderResult.OVERFLOW) {
        break;
      }
    }

    dst.flip();

    if (last && read == 0) {
      read = -1;
    }
    return read;
  }

  /**
   * Used to convert pre-read (buffered) bytes to chars.
   *
   * @param requestedLen how much content should attempt to be read
   * @return the number of chars actually read
   */
  private int fillAvailableChars(final int requestedLen, final CharBuffer dst) {

    final CharsetDecoder decoderLocal = getDecoder();
    final ByteBuffer bb = inputContentBuffer.toByteBuffer();
    final int oldBBPos = bb.position();

    int producedChars = 0;
    int consumedBytes = 0;

    int producedCharsNow;
    int consumedBytesNow;
    CoderResult result;

    int remaining = requestedLen;

    do {
      final int charPos = dst.position();
      final int bbPos = bb.position();
      result = decoderLocal.decode(bb, dst, false);

      producedCharsNow = dst.position() - charPos;
      consumedBytesNow = bb.position() - bbPos;

      producedChars += producedCharsNow;
      consumedBytes += consumedBytesNow;

      remaining -= producedCharsNow;

    } while (remaining > 0
        && (producedCharsNow > 0 || consumedBytesNow > 0)
        && bb.hasRemaining()
        && result == CoderResult.UNDERFLOW);

    bb.position(oldBBPos);
    inputContentBuffer.position(inputContentBuffer.position() + consumedBytes);

    if (readAheadLimit == -1) {
      inputContentBuffer.shrink();
    }

    return producedChars;
  }

  protected void updateInputContentBuffer(final Buffer buffer) {

    buffer.allowBufferDispose(true);

    if (inputContentBuffer == null) {
      inputContentBuffer = buffer;
    } else if (inputContentBuffer.hasRemaining()
        || readAheadLimit
            > 0) { // if the stream is marked - we can't dispose the inputContentBuffer, even if
      // it's been read off
      toCompositeInputContentBuffer().append(buffer);
    } else {
      inputContentBuffer.tryDispose();
      inputContentBuffer = buffer;
    }
  }

  /**
   * @param size the amount of data that must be available for a {@link ReadHandler} to be notified.
   * @param available the amount of data currently available.
   * @return <code>true</code> if the handler should be notified during a call to {@link
   *     #notifyAvailable(ReadHandler)} or {@link #notifyAvailable(ReadHandler, int)}, otherwise
   *     <code>false</code>
   */
  private static boolean shouldNotifyNow(final int size, final int available) {

    return (available != 0 && available >= size);
  }

  /**
   * @return the {@link CharsetDecoder} that should be used when converting content from binary to
   *     character
   */
  private CharsetDecoder getDecoder() {
    if (decoder == null) {
      decoder = decoders.get(encoding);

      if (decoder == null) {
        final Charset cs = Charsets.lookupCharset(encoding);
        decoder = cs.newDecoder();
        decoder.onMalformedInput(CodingErrorAction.REPLACE);
        decoder.onUnmappableCharacter(CodingErrorAction.REPLACE);

        decoders.put(encoding, decoder);
      } else {
        decoder.reset();
      }
    }

    return decoder;
  }

  private CompositeBuffer toCompositeInputContentBuffer() {
    if (!inputContentBuffer.isComposite()) {
      final CompositeBuffer compositeBuffer =
          CompositeBuffer.newBuffer(connection.getMemoryManager());

      compositeBuffer.allowBufferDispose(true);
      compositeBuffer.allowInternalBuffersDispose(true);

      int posAlign = 0;

      if (readAheadLimit > 0) { // the simple inputContentBuffer is marked
        // make the marked data still available
        inputContentBuffer.position(inputContentBuffer.position() - readCount);
        posAlign = readCount;

        markPos = 0; // for the CompositeBuffer markPos is 0
      }

      compositeBuffer.append(inputContentBuffer);
      compositeBuffer.position(posAlign);

      inputContentBuffer = compositeBuffer;
    }

    return (CompositeBuffer) inputContentBuffer;
  }

  /**
   * @param n read bytes count
   * @return <tt>true</tt> if mark is still active, or <tt>false</tt> if the mark hasn't been set or
   *     has been invalidated
   */
  private boolean checkMarkAfterRead(final long n) {
    if (n > 0 && readAheadLimit != -1) {
      if ((long) readCount + n <= readAheadLimit) {
        readCount += n;
        return true;
      }

      // invalidate the mark
      readAheadLimit = -1;
      markPos = -1;
      readCount = 0;
    }

    return false;
  }

  /**
   * Check if passed {@link HttpContent} is {@link HttpTrailer}, which represents trailer chunk
   * (when chunked Transfer-Encoding is used), if it is a trailer chunk - then copy all the
   * available trailer headers to request headers map.
   *
   * @param httpContent
   */
  private static void checkHttpTrailer(final HttpContent httpContent) {
    if (HttpTrailer.isTrailer(httpContent)) {
      final HttpTrailer httpTrailer = (HttpTrailer) httpContent;
      final HttpHeader httpHeader = httpContent.getHttpHeader();

      final MimeHeaders trailerHeaders = httpTrailer.getHeaders();
      final int size = trailerHeaders.size();
      for (int i = 0; i < size; i++) {
        httpHeader.addHeader(
            trailerHeaders.getName(i).toString(), trailerHeaders.getValue(i).toString());
      }
    }
  }

  private static void log(final String message, Object... params) {
    final String preparedMsg = String.format(message, params);

    if (LOGGER.isLoggable(Level.FINEST)) {
      LOGGER.log(Level.FINEST, preparedMsg, new Exception("Logged at"));
    } else {
      LOGGER.log(LOGGER_LEVEL, preparedMsg);
    }
  }
}
/**
 * Test {@link HttpHandlerChain} use of the {@link MapperTest}
 *
 * @author Jeanfrancois Arcand
 */
public class MapperTest extends HttpServerAbstractTest {

  public static final int PORT = 18080;
  private static final Logger LOGGER = Grizzly.logger(MapperTest.class);

  public void testOverlappingMapping() throws IOException {
    LOGGER.fine("testOverlappingMapping");
    try {
      startHttpServer(PORT);
      WebappContext ctx = new WebappContext("Test");
      String[] aliases = new String[] {"/aaa/bbb", "/aaa/ccc"};
      for (String alias : aliases) {
        addServlet(ctx, alias);
      }
      ctx.deploy(httpServer);
      for (String alias : aliases) {
        HttpURLConnection conn = getConnection(alias, PORT);
        assertEquals(HttpServletResponse.SC_OK, getResponseCodeFromAlias(conn));
        assertEquals(alias, readResponse(conn));
        assertEquals(alias, conn.getHeaderField("servlet-path"));
        assertNull(alias, conn.getHeaderField("path-info"));
      }
    } finally {
      stopHttpServer();
    }
  }

  public void testOverlappingMapping2() throws IOException {
    LOGGER.fine("testOverlappingMapping2");
    try {
      startHttpServer(PORT);

      String[] alias = new String[] {"*.jsp", "/jsp/*"};

      WebappContext ctx = new WebappContext("Test");
      addServlet(ctx, "*.jsp");
      addServlet(ctx, "/jsp/*");
      ctx.deploy(httpServer);

      HttpURLConnection conn = getConnection("/jsp/index.jsp", PORT);
      assertEquals(HttpServletResponse.SC_OK, getResponseCodeFromAlias(conn));
      assertEquals(alias[1], readResponse(conn));
      assertEquals("/jsp", conn.getHeaderField("servlet-path"));
      assertEquals("/index.jsp", conn.getHeaderField("path-info"));

    } finally {
      stopHttpServer();
    }
  }

  public void testRootStarMapping() throws IOException {
    LOGGER.fine("testRootMapping");
    try {
      startHttpServer(PORT);
      WebappContext ctx = new WebappContext("Test");
      String alias = "/*";
      addServlet(ctx, alias); // overrides the static resource handler
      ctx.deploy(httpServer);
      HttpURLConnection conn = getConnection("/index.html", PORT);
      assertEquals(HttpServletResponse.SC_OK, getResponseCodeFromAlias(conn));
      assertEquals(alias, readResponse(conn));
      assertEquals("", conn.getHeaderField("servlet-path"));
      assertEquals("/index.html", conn.getHeaderField("path-info"));
    } finally {
      stopHttpServer();
    }
  }

  public void testRootStarMapping2() throws IOException {
    LOGGER.fine("testRootMapping");
    try {
      startHttpServer(PORT);
      WebappContext ctx = new WebappContext("Test");
      String alias = "/*";
      addServlet(ctx, alias); // overrides the static resource handler
      ctx.deploy(httpServer);
      HttpURLConnection conn = getConnection("/foo/index.html", PORT);
      assertEquals(HttpServletResponse.SC_OK, getResponseCodeFromAlias(conn));
      assertEquals(alias, readResponse(conn));
      assertEquals("", conn.getHeaderField("servlet-path"));
      assertEquals("/foo/index.html", conn.getHeaderField("path-info"));
    } finally {
      stopHttpServer();
    }
  }

  public void testRootMapping() throws IOException {
    LOGGER.fine("testRootMapping");
    try {
      startHttpServer(PORT);
      WebappContext ctx = new WebappContext("Test");
      String alias = "/";
      addServlet(ctx, alias); // overrides the static resource handler
      ctx.deploy(httpServer);
      HttpURLConnection conn = getConnection("/", PORT);
      assertEquals(HttpServletResponse.SC_OK, getResponseCodeFromAlias(conn));
      assertEquals(alias, readResponse(conn));
      assertEquals("/", conn.getHeaderField("servlet-path"));
      assertEquals(null, conn.getHeaderField("path-info"));
    } finally {
      stopHttpServer();
    }
  }

  public void testRootMapping2() throws IOException {
    LOGGER.fine("testRootMapping");
    try {
      startHttpServer(PORT);
      WebappContext ctx = new WebappContext("Test");
      String alias = "/";
      addServlet(ctx, alias); // overrides the static resource handler
      ctx.deploy(httpServer);
      HttpURLConnection conn = getConnection("/foo/index.html", PORT);
      assertEquals(HttpServletResponse.SC_OK, getResponseCodeFromAlias(conn));
      assertEquals(alias, readResponse(conn));
      assertEquals("/foo/index.html", conn.getHeaderField("servlet-path"));
      assertEquals(null, conn.getHeaderField("path-info"));
    } finally {
      stopHttpServer();
    }
  }

  public void testWrongMapping() throws IOException {
    LOGGER.fine("testWrongMapping");
    try {
      startHttpServer(PORT);
      WebappContext ctx = new WebappContext("Test");
      String alias = "/a/b/c";
      addServlet(ctx, alias);
      ctx.deploy(httpServer);
      HttpURLConnection conn = getConnection("/aaa.html", PORT);
      assertEquals(HttpServletResponse.SC_NOT_FOUND, getResponseCodeFromAlias(conn));
    } finally {
      stopHttpServer();
    }
  }
  //
  //    public void testComplexMapping() throws IOException {
  //        LOGGER.fine("testComplexMapping");
  //        try {
  //            startHttpServer(PORT);
  //            String alias = "/a/b/c/*.html";
  //            addHttpHandler(alias);
  //            HttpURLConnection conn = getConnection("/a/b/c/index.html", PORT);
  //            assertEquals(HttpServletResponse.SC_OK,
  //                    getResponseCodeFromAlias(conn));
  //            assertEquals(alias, readResponse(conn));
  //        } finally {
  //            stopHttpServer();
  //        }
  //    }
  //
  public void testWildcardMapping() throws IOException {
    LOGGER.fine("testWildcardMapping");
    try {
      startHttpServer(PORT);
      WebappContext ctx = new WebappContext("Test");
      String alias = "*.html";
      addServlet(ctx, alias);
      ctx.deploy(httpServer);
      HttpURLConnection conn = getConnection("/index.html", PORT);
      assertEquals(HttpServletResponse.SC_OK, getResponseCodeFromAlias(conn));
      assertEquals(alias, readResponse(conn));
      assertEquals("/index.html", conn.getHeaderField("servlet-path"));
      assertNull(conn.getHeaderField("path-info"));
    } finally {
      stopHttpServer();
    }
  }

  public void testWrongMappingRootContext() throws IOException {
    LOGGER.fine("testWrongMappingRootContext");
    try {
      startHttpServer(PORT);
      WebappContext ctx = new WebappContext("Test");
      String alias = "*.a";
      addServlet(ctx, alias);
      ctx.deploy(httpServer);
      HttpURLConnection conn = getConnection("/aaa.html", PORT);
      assertEquals(HttpServletResponse.SC_NOT_FOUND, getResponseCodeFromAlias(conn));
    } finally {
      stopHttpServer();
    }
  }

  public void testDefaultServletOverride() throws Exception {
    try {
      startHttpServer(PORT);
      WebappContext ctx = new WebappContext("Test");
      String alias = "/";
      addServlet(ctx, alias);
      ctx.deploy(httpServer);
      HttpURLConnection conn = getConnection("/", PORT);
      assertEquals(HttpServletResponse.SC_OK, getResponseCodeFromAlias(conn));
      assertEquals("/", conn.getHeaderField("servlet-path"));
    } finally {
      stopHttpServer();
    }
  }

  public void testDefaultContext() throws Exception {
    try {
      startHttpServer(PORT);
      WebappContext ctx = new WebappContext("Test", "/");
      String alias = "/foo/*";
      addServlet(ctx, alias);
      ctx.deploy(httpServer);
      HttpURLConnection conn = getConnection("/foo/bar/baz", PORT);
      assertEquals(HttpServletResponse.SC_OK, getResponseCodeFromAlias(conn));
      assertEquals("/foo/bar/baz", conn.getHeaderField("request-uri"));
      assertEquals("", conn.getHeaderField("context-path"));
      assertEquals("/foo", conn.getHeaderField("servlet-path"));
      assertEquals("/bar/baz", conn.getHeaderField("path-info"));
    } finally {
      stopHttpServer();
    }
  }

  // --------------------------------------------------------- Private Methods

  private static ServletRegistration addServlet(final WebappContext ctx, final String alias) {
    final ServletRegistration reg =
        ctx.addServlet(
            alias,
            new HttpServlet() {

              @Override
              protected void doGet(HttpServletRequest req, HttpServletResponse resp)
                  throws IOException {
                LOGGER.log(
                    Level.INFO,
                    "{0} received request {1}",
                    new Object[] {alias, req.getRequestURI()});
                resp.setStatus(HttpServletResponse.SC_OK);
                resp.setHeader("Path-Info", req.getPathInfo());
                resp.setHeader("Servlet-Path", req.getServletPath());
                resp.setHeader("Request-Was", req.getRequestURI());
                resp.setHeader("Servlet-Name", getServletName());
                resp.setHeader("Request-Uri", req.getRequestURI());
                resp.setHeader("Context-Path", req.getContextPath());
                resp.getWriter().write(alias);
              }
            });
    reg.addMapping(alias);

    return reg;
  }
}
/**
 * Common {@link Connection} implementation for Java NIO <tt>Connection</tt>s.
 *
 * @author Alexey Stashok
 */
public abstract class AIOConnection implements Connection<SocketAddress> {
  private static final Logger logger = Grizzly.logger(AIOConnection.class);

  protected final AIOTransport transport;

  protected volatile int readBufferSize;
  protected volatile int writeBufferSize;

  protected volatile long readTimeoutMillis = 30000;
  protected volatile long writeTimeoutMillis = 30000;

  protected volatile AsynchronousChannel channel;

  protected volatile Processor processor;
  protected volatile ProcessorSelector processorSelector;

  protected final AttributeHolder attributes;

  protected final TaskQueue<AsyncReadQueueRecord> asyncReadQueue;
  protected final TaskQueue<AsyncWriteQueueRecord> asyncWriteQueue;

  protected final AtomicBoolean isClosed = new AtomicBoolean(false);

  protected volatile boolean isBlocking;

  protected volatile boolean isStandalone;

  private final Queue<CloseListener> closeListeners =
      DataStructures.getLTQinstance(CloseListener.class);

  /** Connection probes */
  protected final MonitoringConfigImpl<ConnectionProbe> monitoringConfig =
      new MonitoringConfigImpl<>(ConnectionProbe.class);

  public AIOConnection(AIOTransport transport) {
    this.transport = transport;
    asyncReadQueue = TaskQueue.createSafeTaskQueue();
    asyncWriteQueue = TaskQueue.createSafeTaskQueue();

    attributes = new IndexedAttributeHolder(transport.getAttributeBuilder());
  }

  @Override
  public void configureBlocking(boolean isBlocking) {
    this.isBlocking = isBlocking;
  }

  @Override
  public boolean isBlocking() {
    return isBlocking;
  }

  @Override
  public synchronized void configureStandalone(boolean isStandalone) {
    if (this.isStandalone != isStandalone) {
      this.isStandalone = isStandalone;
      if (isStandalone) {
        processor = StandaloneProcessor.INSTANCE;
        processorSelector = StandaloneProcessorSelector.INSTANCE;
      } else {
        processor = transport.getProcessor();
        processorSelector = transport.getProcessorSelector();
      }
    }
  }

  @Override
  public boolean isStandalone() {
    return isStandalone;
  }

  @Override
  public Transport getTransport() {
    return transport;
  }

  @Override
  public int getReadBufferSize() {
    return readBufferSize;
  }

  @Override
  public void setReadBufferSize(int readBufferSize) {
    this.readBufferSize = readBufferSize;
  }

  @Override
  public int getWriteBufferSize() {
    return writeBufferSize;
  }

  @Override
  public void setWriteBufferSize(int writeBufferSize) {
    this.writeBufferSize = writeBufferSize;
  }

  @Override
  public long getReadTimeout(TimeUnit timeUnit) {
    return timeUnit.convert(readTimeoutMillis, TimeUnit.MILLISECONDS);
  }

  @Override
  public void setReadTimeout(long timeout, TimeUnit timeUnit) {
    readTimeoutMillis = TimeUnit.MILLISECONDS.convert(timeout, timeUnit);
  }

  @Override
  public long getWriteTimeout(TimeUnit timeUnit) {
    return timeUnit.convert(writeTimeoutMillis, TimeUnit.MILLISECONDS);
  }

  @Override
  public void setWriteTimeout(long timeout, TimeUnit timeUnit) {
    writeTimeoutMillis = TimeUnit.MILLISECONDS.convert(timeout, timeUnit);
  }

  public AsynchronousChannel getChannel() {
    return channel;
  }

  protected void setChannel(AsynchronousChannel channel) {
    this.channel = channel;
  }

  @Override
  public Processor obtainProcessor(IOEvent ioEvent) {
    if (processor == null && processorSelector == null) {
      return transport.obtainProcessor(ioEvent, this);
    }

    if (processor != null && processor.isInterested(ioEvent)) {
      return processor;
    } else if (processorSelector != null) {
      final Processor selectedProcessor = processorSelector.select(ioEvent, this);
      if (selectedProcessor != null) {
        return selectedProcessor;
      }
    }

    return null;
  }

  @Override
  public Processor getProcessor() {
    return processor;
  }

  @Override
  public void setProcessor(Processor preferableProcessor) {
    this.processor = preferableProcessor;
  }

  @Override
  public ProcessorSelector getProcessorSelector() {
    return processorSelector;
  }

  @Override
  public void setProcessorSelector(ProcessorSelector preferableProcessorSelector) {
    this.processorSelector = preferableProcessorSelector;
  }

  public TaskQueue<AsyncReadQueueRecord> getAsyncReadQueue() {
    return asyncReadQueue;
  }

  public TaskQueue<AsyncWriteQueueRecord> getAsyncWriteQueue() {
    return asyncWriteQueue;
  }

  @Override
  public AttributeHolder getAttributes() {
    return attributes;
  }

  @Override
  public <M> GrizzlyFuture<ReadResult<M, SocketAddress>> read() throws IOException {
    return read(null);
  }

  @Override
  public <M> GrizzlyFuture<ReadResult<M, SocketAddress>> read(
      CompletionHandler<ReadResult<M, SocketAddress>> completionHandler) throws IOException {

    final Processor obtainedProcessor = obtainProcessor(IOEvent.READ);
    return obtainedProcessor.read(this, completionHandler);
  }

  @Override
  public <M> GrizzlyFuture<WriteResult<M, SocketAddress>> write(M message) throws IOException {
    return write(null, message, null);
  }

  @Override
  public <M> GrizzlyFuture<WriteResult<M, SocketAddress>> write(
      M message, CompletionHandler<WriteResult<M, SocketAddress>> completionHandler)
      throws IOException {
    return write(null, message, completionHandler);
  }

  @Override
  public <M> GrizzlyFuture<WriteResult<M, SocketAddress>> write(
      SocketAddress dstAddress,
      M message,
      CompletionHandler<WriteResult<M, SocketAddress>> completionHandler)
      throws IOException {

    final Processor obtainedProcessor = obtainProcessor(IOEvent.WRITE);
    return obtainedProcessor.write(this, dstAddress, message, completionHandler);
  }

  @Override
  public boolean isOpen() {
    return channel != null && channel.isOpen() && !isClosed.get();
  }

  @Override
  public GrizzlyFuture close() throws IOException {
    if (!isClosed.getAndSet(true)) {
      preClose();
      notifyCloseListeners();
      notifyProbesClose(this);

      channel.close();
      //            return transport.getSelectorHandler().executeInSelectorThread(
      //                    selectorRunner, new Runnable() {
      //
      //                @Override
      //                public void run() {
      //                    try {
      //                        ((AIOTransport) transport).closeConnection(AIOConnection.this);
      //                    } catch (IOException e) {
      //                        logger.log(Level.FINE, "Error during connection close", e);
      //                    }
      //                }
      //            }, null);
    }

    return ReadyFutureImpl.create(this);
  }

  /** {@inheritDoc} */
  @Override
  public void addCloseListener(CloseListener closeListener) {
    // check if connection is still open
    if (!isClosed.get()) {
      // add close listener
      closeListeners.add(closeListener);
      // check the connection state again
      if (isClosed.get() && closeListeners.remove(closeListener)) {
        // if connection was closed during the method call - notify the listener
        try {
          closeListener.onClosed(this);
        } catch (IOException ignored) {
        }
      }
    } else { // if connection is closed - notify the listener
      try {
        closeListener.onClosed(this);
      } catch (IOException ignored) {
      }
    }
  }

  /** {@inheritDoc} */
  @Override
  public boolean removeCloseListener(CloseListener closeListener) {
    return closeListeners.remove(closeListener);
  }

  /** {@inheritDoc} */
  @Override
  public void notifyConnectionError(Throwable error) {
    notifyProbesError(this, error);
  }

  /** {@inheritDoc} */
  @Override
  public final MonitoringConfig<ConnectionProbe> getMonitoringConfig() {
    return monitoringConfig;
  }

  /**
   * Notify registered {@link ConnectionProbe}s about the bind event.
   *
   * @param connection the <tt>Connection</tt> event occurred on.
   */
  protected static void notifyProbesBind(AIOConnection connection) {
    final ConnectionProbe[] probes = connection.monitoringConfig.getProbesUnsafe();
    if (probes != null) {
      for (ConnectionProbe probe : probes) {
        probe.onBindEvent(connection);
      }
    }
  }

  /**
   * Notify registered {@link ConnectionProbe}s about the accept event.
   *
   * @param connection the <tt>Connection</tt> event occurred on.
   */
  protected static void notifyProbesAccept(AIOConnection connection) {
    final ConnectionProbe[] probes = connection.monitoringConfig.getProbesUnsafe();
    if (probes != null) {
      for (ConnectionProbe probe : probes) {
        probe.onAcceptEvent(connection);
      }
    }
  }

  /**
   * Notify registered {@link ConnectionProbe}s about the connect event.
   *
   * @param connection the <tt>Connection</tt> event occurred on.
   */
  protected static void notifyProbesConnect(final AIOConnection connection) {
    final ConnectionProbe[] probes = connection.monitoringConfig.getProbesUnsafe();
    if (probes != null) {
      for (ConnectionProbe probe : probes) {
        probe.onConnectEvent(connection);
      }
    }
  }

  /** Notify registered {@link ConnectionProbe}s about the read event. */
  protected static void notifyProbesRead(AIOConnection connection, Buffer data, int size) {

    final ConnectionProbe[] probes = connection.monitoringConfig.getProbesUnsafe();
    if (probes != null) {
      for (ConnectionProbe probe : probes) {
        probe.onReadEvent(connection, data, size);
      }
    }
  }

  /** Notify registered {@link ConnectionProbe}s about the write event. */
  protected static void notifyProbesWrite(AIOConnection connection, Buffer data, int size) {

    final ConnectionProbe[] probes = connection.monitoringConfig.getProbesUnsafe();
    if (probes != null) {
      for (ConnectionProbe probe : probes) {
        probe.onWriteEvent(connection, data, size);
      }
    }
  }

  /**
   * Notify registered {@link ConnectionProbe}s about the IO Event ready event.
   *
   * @param connection the <tt>Connection</tt> event occurred on.
   * @param ioEvent the {@link IOEvent}.
   */
  protected static void notifyIOEventReady(AIOConnection connection, IOEvent ioEvent) {
    final ConnectionProbe[] probes = connection.monitoringConfig.getProbesUnsafe();
    if (probes != null) {
      for (ConnectionProbe probe : probes) {
        probe.onIOEventReadyEvent(connection, ioEvent);
      }
    }
  }

  /**
   * Notify registered {@link ConnectionProbe}s about the IO Event enabled event.
   *
   * @param connection the <tt>Connection</tt> event occurred on.
   * @param ioEvent the {@link IOEvent}.
   */
  protected static void notifyIOEventEnabled(AIOConnection connection, IOEvent ioEvent) {
    final ConnectionProbe[] probes = connection.monitoringConfig.getProbesUnsafe();
    if (probes != null) {
      for (ConnectionProbe probe : probes) {
        probe.onIOEventEnableEvent(connection, ioEvent);
      }
    }
  }

  /**
   * Notify registered {@link ConnectionProbe}s about the IO Event disabled event.
   *
   * @param connection the <tt>Connection</tt> event occurred on.
   * @param ioEvent the {@link IOEvent}.
   */
  protected static void notifyIOEventDisabled(AIOConnection connection, IOEvent ioEvent) {
    final ConnectionProbe[] probes = connection.monitoringConfig.getProbesUnsafe();
    if (probes != null) {
      for (ConnectionProbe probe : probes) {
        probe.onIOEventDisableEvent(connection, ioEvent);
      }
    }
  }

  /**
   * Notify registered {@link ConnectionProbe}s about the close event.
   *
   * @param connection the <tt>Connection</tt> event occurred on.
   */
  protected static void notifyProbesClose(AIOConnection connection) {
    final ConnectionProbe[] probes = connection.monitoringConfig.getProbesUnsafe();
    if (probes != null) {
      for (ConnectionProbe probe : probes) {
        probe.onCloseEvent(connection);
      }
    }
  }

  /**
   * Notify registered {@link ConnectionProbe}s about the error.
   *
   * @param connection the <tt>Connection</tt> event occurred on.
   */
  protected static void notifyProbesError(AIOConnection connection, Throwable error) {
    final ConnectionProbe[] probes = connection.monitoringConfig.getProbesUnsafe();
    if (probes != null) {
      for (ConnectionProbe probe : probes) {
        probe.onErrorEvent(connection, error);
      }
    }
  }

  /** Notify all close listeners */
  private void notifyCloseListeners() {
    CloseListener closeListener;
    while ((closeListener = closeListeners.poll()) != null) {
      try {
        closeListener.onClosed(this);
      } catch (IOException ignored) {
      }
    }
  }

  protected abstract void preClose();
}
/**
 * Test {@link FilterChain} blocking read.
 *
 * @author Alexey Stashok
 */
@SuppressWarnings("unchecked")
public class FilterChainReadTest extends TestCase {
  public static int PORT = 7785;

  private static final Logger logger = Grizzly.logger(FilterChainReadTest.class);

  public void testBlockingRead() throws Exception {
    final String[] clientMsgs = {"XXXXX", "Hello", "from", "client"};

    Connection connection = null;
    int messageNum = 3;

    final BlockingQueue<String> intermResultQueue = DataStructures.getLTQInstance(String.class);

    final PUFilter puFilter = new PUFilter();
    FilterChain subProtocolChain =
        puFilter
            .getPUFilterChainBuilder()
            .add(new MergeFilter(clientMsgs.length, intermResultQueue))
            .add(new EchoFilter())
            .build();

    puFilter.register(new SimpleProtocolFinder(clientMsgs[0]), subProtocolChain);

    FilterChainBuilder filterChainBuilder = FilterChainBuilder.newInstance();
    filterChainBuilder.add(new TransportFilter());
    filterChainBuilder.add(new StringFilter());
    filterChainBuilder.add(puFilter);

    final TCPNIOTransport transport = TCPNIOTransportBuilder.newInstance().build();
    transport.setFilterChain(filterChainBuilder.build());

    try {
      transport.bind(PORT);
      transport.start();

      final BlockingQueue<String> resultQueue = DataStructures.getLTQInstance(String.class);

      Future<Connection> future = transport.connect("localhost", PORT);
      connection = future.get(10, TimeUnit.SECONDS);
      assertTrue(connection != null);

      FilterChainBuilder clientFilterChainBuilder = FilterChainBuilder.newInstance();
      clientFilterChainBuilder.add(new TransportFilter());
      clientFilterChainBuilder.add(new StringFilter());
      clientFilterChainBuilder.add(
          new BaseFilter() {

            @Override
            public NextAction handleRead(FilterChainContext ctx) throws IOException {
              resultQueue.add((String) ctx.getMessage());
              return ctx.getStopAction();
            }
          });
      final FilterChain clientFilterChain = clientFilterChainBuilder.build();

      connection.setFilterChain(clientFilterChain);

      for (int i = 0; i < messageNum; i++) {
        String clientMessage = "";

        for (int j = 0; j < clientMsgs.length; j++) {
          String msg = clientMsgs[j] + "-" + i;
          Future<WriteResult> writeFuture = connection.write(msg);

          assertTrue("Write timeout loop: " + i, writeFuture.get(10, TimeUnit.SECONDS) != null);

          final String srvInterm = intermResultQueue.poll(10, TimeUnit.SECONDS);

          assertEquals("Unexpected interm. response (" + i + ", " + j + ")", msg, srvInterm);

          clientMessage += msg;
        }

        final String message = resultQueue.poll(10, TimeUnit.SECONDS);

        assertEquals("Unexpected response (" + i + ")", clientMessage, message);
      }
    } finally {
      if (connection != null) {
        connection.closeSilently();
      }

      transport.shutdownNow();
    }
  }

  public void testBlockingReadWithRemainder() throws Exception {
    final String[] clientMsgs = {"YYYYY", "Hello", "from", "client"};

    Connection connection = null;
    int messageNum = 3;

    final StringFilter stringFilter = new StringFilter();

    final BlockingQueue<String> intermResultQueue = DataStructures.getLTQInstance(String.class);

    final PUFilter puFilter = new PUFilter();
    FilterChain subProtocolChain =
        puFilter
            .getPUFilterChainBuilder()
            .add(new MergeFilter(clientMsgs.length, intermResultQueue))
            .add(new EchoFilter())
            .build();

    puFilter.register(new SimpleProtocolFinder(clientMsgs[0]), subProtocolChain);

    FilterChainBuilder filterChainBuilder = FilterChainBuilder.newInstance();
    filterChainBuilder.add(new TransportFilter());
    filterChainBuilder.add(stringFilter);
    filterChainBuilder.add(puFilter);

    TCPNIOTransport transport = TCPNIOTransportBuilder.newInstance().build();
    transport.setFilterChain(filterChainBuilder.build());

    try {
      transport.bind(PORT);
      transport.start();

      final BlockingQueue<String> resultQueue = DataStructures.getLTQInstance(String.class);

      Future<Connection> future = transport.connect("localhost", PORT);
      connection = future.get(10, TimeUnit.SECONDS);
      assertTrue(connection != null);

      FilterChainBuilder clientFilterChainBuilder = FilterChainBuilder.newInstance();
      clientFilterChainBuilder.add(new TransportFilter());
      clientFilterChainBuilder.add(stringFilter);
      clientFilterChainBuilder.add(
          new BaseFilter() {

            @Override
            public NextAction handleRead(FilterChainContext ctx) throws IOException {
              resultQueue.add((String) ctx.getMessage());
              return ctx.getStopAction();
            }
          });
      final FilterChain clientFilterChain = clientFilterChainBuilder.build();

      connection.setFilterChain(clientFilterChain);

      final MemoryManager memoryManager = transport.getMemoryManager();
      for (int i = 0; i < messageNum; i++) {
        String clientMessage = "";

        CompositeBuffer bb = CompositeBuffer.newBuffer(memoryManager);

        for (int j = 0; j < clientMsgs.length; j++) {
          String msg = clientMsgs[j] + "-" + i;
          clientMessage += msg;
          Buffer buffer = stringFilter.encode(memoryManager, msg);
          bb.append(buffer);
        }

        Future<WriteResult<WritableMessage, SocketAddress>> writeFuture = connection.write(bb);

        assertTrue("Write timeout loop: " + i, writeFuture.get(10, TimeUnit.SECONDS) != null);

        for (int j = 0; j < clientMsgs.length; j++) {
          String msg = clientMsgs[j] + "-" + i;
          final String srvInterm = intermResultQueue.poll(10, TimeUnit.SECONDS);

          assertEquals("Unexpected interm. response (" + i + ", " + j + ")", msg, srvInterm);
        }

        final String message = resultQueue.poll(10, TimeUnit.SECONDS);

        assertEquals("Unexpected response (" + i + ")", clientMessage, message);
      }
    } finally {
      if (connection != null) {
        connection.closeSilently();
      }

      transport.shutdownNow();
    }
  }

  public void testBlockingReadError() throws Exception {
    final String[] clientMsgs = {"ZZZZZ", "Hello", "from", "client"};

    Connection connection = null;

    final BlockingQueue intermResultQueue = DataStructures.getLTQInstance();

    final PUFilter puFilter = new PUFilter();
    FilterChain subProtocolChain =
        puFilter
            .getPUFilterChainBuilder()
            .add(new MergeFilter(clientMsgs.length, intermResultQueue))
            .add(new EchoFilter())
            .build();

    puFilter.register(new SimpleProtocolFinder(clientMsgs[0]), subProtocolChain);

    FilterChainBuilder filterChainBuilder = FilterChainBuilder.newInstance();
    filterChainBuilder.add(new TransportFilter());
    filterChainBuilder.add(new StringFilter());
    filterChainBuilder.add(puFilter);

    TCPNIOTransport transport = TCPNIOTransportBuilder.newInstance().build();
    transport.setFilterChain(filterChainBuilder.build());

    try {
      transport.bind(PORT);
      transport.start();

      Future<Connection> future = transport.connect("localhost", PORT);
      connection = future.get(10, TimeUnit.SECONDS);
      assertTrue(connection != null);

      FilterChainBuilder clientFilterChainBuilder = FilterChainBuilder.newInstance();
      clientFilterChainBuilder.add(new TransportFilter());
      clientFilterChainBuilder.add(new StringFilter());
      final FilterChain clientFilterChain = clientFilterChainBuilder.build();

      connection.setFilterChain(clientFilterChain);

      String msg = clientMsgs[0];
      Future<WriteResult> writeFuture = connection.write(msg);

      assertTrue("Write timeout", writeFuture.get(10, TimeUnit.SECONDS) != null);

      final String srvInterm = (String) intermResultQueue.poll(10, TimeUnit.SECONDS);

      assertEquals("Unexpected interm. response", msg, srvInterm);

      connection.closeSilently();
      connection = null;

      final Exception e = (Exception) intermResultQueue.poll(10, TimeUnit.SECONDS);

      assertTrue(
          "Unexpected response. Exception: " + e.getClass() + ": " + e.getMessage(),
          e instanceof IOException);
    } finally {
      if (connection != null) {
        connection.closeSilently();
      }

      transport.shutdownNow();
    }
  }

  private static final class SimpleProtocolFinder implements ProtocolFinder {
    public final String name;

    public SimpleProtocolFinder(final String name) {
      this.name = name;
    }

    @Override
    public Result find(PUContext puContext, FilterChainContext ctx) {
      final String requestedProtocolName = ctx.getMessage();

      return requestedProtocolName.startsWith(name) ? Result.FOUND : Result.NOT_FOUND;
    }
  }

  private static final class MergeFilter extends BaseFilter {

    private final int clientMsgs;
    private final BlockingQueue intermResultQueue;

    public MergeFilter(int clientMsgs, BlockingQueue intermResultQueue) {
      this.clientMsgs = clientMsgs;
      this.intermResultQueue = intermResultQueue;
    }

    @Override
    public NextAction handleRead(final FilterChainContext ctx) throws IOException {

      String message = (String) ctx.getMessage();

      logger.log(Level.INFO, "First chunk come: {0}", message);
      intermResultQueue.add(message);

      Connection connection = ctx.getConnection();
      connection.setBlockingReadTimeout(10, TimeUnit.SECONDS);

      try {
        for (int i = 0; i < clientMsgs - 1; i++) {
          final ReadResult rr = ctx.read();
          final String blckMsg = (String) rr.getMessage();

          rr.recycle();
          logger.log(Level.INFO, "Blocking chunk come: {0}", blckMsg);
          intermResultQueue.add(blckMsg);
          message += blckMsg;
        }
      } catch (Exception e) {
        intermResultQueue.add(e);
        return ctx.getStopAction();
      }

      ctx.setMessage(message);

      return ctx.getInvokeAction();
    }
  }
}
Beispiel #12
0
public class HttpServer {
  private static final Logger LOGGER = Grizzly.logger(HttpServer.class);

  /** Configuration details for this server instance. */
  private final ServerConfiguration serverConfig = new ServerConfiguration(this);

  /** Flag indicating whether or not this server instance has been started. */
  private State state = State.STOPPED;

  /** Future to control graceful shutdown status */
  private FutureImpl<HttpServer> shutdownFuture;

  /** HttpHandler, which processes HTTP requests */
  private final HttpHandlerChain httpHandlerChain = new HttpHandlerChain(this);

  /** Mapping of {@link NetworkListener}s, by name, used by this server instance. */
  private final Map<String, NetworkListener> listeners = new HashMap<String, NetworkListener>(2);

  private volatile ExecutorService auxExecutorService;

  volatile DelayedExecutor delayedExecutor;

  protected volatile GrizzlyJmxManager jmxManager;

  protected volatile Object managementObject;

  // ---------------------------------------------------------- Public Methods

  /** @return the {@link ServerConfiguration} used to configure this {@link HttpServer} instance */
  public final ServerConfiguration getServerConfiguration() {
    return serverConfig;
  }

  /**
   * Adds the specified <code>listener</code> to the server instance.
   *
   * <p>If the server is already running when this method is called, the listener will be started.
   *
   * @param listener the {@link NetworkListener} to associate with this server instance.
   */
  public synchronized void addListener(final NetworkListener listener) {

    if (state != State.RUNNING) {
      listeners.put(listener.getName(), listener);
    } else {
      configureListener(listener);
      if (!listener.isStarted()) {
        try {
          listener.start();
        } catch (IOException ioe) {
          if (LOGGER.isLoggable(Level.SEVERE)) {
            LOGGER.log(
                Level.SEVERE,
                "Failed to start listener [{0}] : {1}",
                new Object[] {listener.toString(), ioe.toString()});
            LOGGER.log(Level.SEVERE, ioe.toString(), ioe);
          }
        }
      }
    }
  }

  /**
   * @param name the {@link NetworkListener} name.
   * @return the {@link NetworkListener}, if any, associated with the specified <code>name</code>.
   */
  public synchronized NetworkListener getListener(final String name) {

    return listeners.get(name);
  }

  /**
   * @return a <code>read only</code> {@link Collection} over the listeners associated with this
   *     <code>HttpServer</code> instance.
   */
  public synchronized Collection<NetworkListener> getListeners() {
    return Collections.unmodifiableCollection(listeners.values());
  }

  /**
   * Removes the {@link NetworkListener} associated with the specified <code>name</code>.
   *
   * <p>If the server is running when this method is invoked, the listener will be stopped before
   * being returned.
   *
   * @param name the name of the {@link NetworkListener} to remove.
   */
  public synchronized NetworkListener removeListener(final String name) {

    final NetworkListener listener = listeners.remove(name);
    if (listener != null) {
      if (listener.isStarted()) {
        try {
          listener.shutdownNow();
        } catch (IOException ioe) {
          if (LOGGER.isLoggable(Level.SEVERE)) {
            LOGGER.log(
                Level.SEVERE,
                "Failed to shutdown listener [{0}] : {1}",
                new Object[] {listener.toString(), ioe.toString()});
            LOGGER.log(Level.SEVERE, ioe.toString(), ioe);
          }
        }
      }
    }
    return listener;
  }

  /**
   * Starts the <code>HttpServer</code>.
   *
   * @throws IOException if an error occurs while attempting to start the server.
   */
  public synchronized void start() throws IOException {

    if (state == State.RUNNING) {
      return;
    } else if (state == State.STOPPING) {
      throw new IllegalStateException(
          "The server is currently in pending"
              + " shutdown state. You have to either wait for shutdown to"
              + " complete or force it by calling shutdownNow()");
    }
    state = State.RUNNING;
    shutdownFuture = null;

    configureAuxThreadPool();

    delayedExecutor = new DelayedExecutor(auxExecutorService);
    delayedExecutor.start();

    for (final NetworkListener listener : listeners.values()) {
      configureListener(listener);
    }

    if (serverConfig.isJmxEnabled()) {
      enableJMX();
    }

    for (final NetworkListener listener : listeners.values()) {
      try {
        listener.start();
      } catch (IOException ioe) {
        if (LOGGER.isLoggable(Level.FINEST)) {
          LOGGER.log(
              Level.FINEST,
              "Failed to start listener [{0}] : {1}",
              new Object[] {listener.toString(), ioe.toString()});
          LOGGER.log(Level.FINEST, ioe.toString(), ioe);
        }

        throw ioe;
      }
    }

    setupHttpHandler();

    if (serverConfig.isJmxEnabled()) {
      for (final JmxEventListener l : serverConfig.getJmxEventListeners()) {
        l.jmxEnabled();
      }
    }

    if (LOGGER.isLoggable(Level.INFO)) {
      LOGGER.log(Level.INFO, "[{0}] Started.", getServerConfiguration().getName());
    }
  }

  private void setupHttpHandler() {

    serverConfig.addJmxEventListener(httpHandlerChain);

    synchronized (serverConfig.handlersSync) {
      for (final HttpHandler httpHandler : serverConfig.orderedHandlers) {
        final String[] mappings = serverConfig.handlers.get(httpHandler);
        httpHandlerChain.addHandler(httpHandler, mappings);
      }
    }
    httpHandlerChain.start();
  }

  private void tearDownHttpHandler() {

    httpHandlerChain.destroy();
  }

  /** @return the {@link HttpHandler} used by this <code>HttpServer</code> instance. */
  public HttpHandler getHttpHandler() {
    return httpHandlerChain;
  }

  /** @return <code>true</code> if this <code>HttpServer</code> has been started. */
  public boolean isStarted() {
    return state != State.STOPPED;
  }

  public Object getManagementObject(boolean clear) {
    if (!clear && managementObject == null) {
      synchronized (serverConfig) {
        if (managementObject == null) {
          managementObject =
              MonitoringUtils.loadJmxObject(
                  "org.glassfish.grizzly.http.server.jmx.HttpServer", this, HttpServer.class);
        }
      }
    }
    try {
      return managementObject;
    } finally {
      if (clear) {
        managementObject = null;
      }
    }
  }

  public synchronized GrizzlyFuture<HttpServer> shutdown(
      final long gracePeriod, final TimeUnit timeUnit) {
    if (state != State.RUNNING) {
      return shutdownFuture != null ? shutdownFuture : Futures.createReadyFuture(this);
    }

    shutdownFuture = Futures.createSafeFuture();
    state = State.STOPPING;

    final int listenersCount = listeners.size();
    final FutureImpl<HttpServer> shutdownFutureLocal = shutdownFuture;

    final CompletionHandler<NetworkListener> shutdownCompletionHandler =
        new EmptyCompletionHandler<NetworkListener>() {
          final AtomicInteger counter = new AtomicInteger(listenersCount);

          @Override
          public void completed(final NetworkListener networkListener) {
            if (counter.decrementAndGet() == 0) {
              try {
                shutdownNow();
                shutdownFutureLocal.result(HttpServer.this);
              } catch (Throwable e) {
                shutdownFutureLocal.failure(e);
              }
            }
          }
        };

    for (NetworkListener listener : listeners.values()) {
      listener.shutdown(gracePeriod, timeUnit).addCompletionHandler(shutdownCompletionHandler);
    }

    return shutdownFuture;
  }

  /** Gracefully shuts down the <code>HttpServer</code> instance. */
  public synchronized GrizzlyFuture<HttpServer> shutdown() {
    return shutdown(-1, TimeUnit.MILLISECONDS);
  }

  /** Immediately shuts down the <code>HttpServer</code> instance. */
  public synchronized void shutdownNow() {

    if (state == State.STOPPED) {
      return;
    }
    state = State.STOPPED;

    try {

      if (serverConfig.isJmxEnabled()) {
        for (final JmxEventListener l : serverConfig.getJmxEventListeners()) {
          l.jmxDisabled();
        }
      }

      tearDownHttpHandler();

      final String[] names = listeners.keySet().toArray(new String[listeners.size()]);
      for (final String name : names) {
        removeListener(name);
      }

      delayedExecutor.stop();
      delayedExecutor.destroy();
      delayedExecutor = null;

      stopAuxThreadPool();

      if (serverConfig.isJmxEnabled()) {
        disableJMX();
      }

    } catch (Exception e) {
      LOGGER.log(Level.WARNING, null, e);
    } finally {
      for (final NetworkListener listener : listeners.values()) {
        final FilterChain filterChain = listener.getTransport().getFilterChain();
        filterChain.clear();
      }

      if (shutdownFuture != null) {
        shutdownFuture.result(this);
      }
    }
  }

  /**
   * @return a <code>HttpServer</code> configured to listen to requests on {@link
   *     NetworkListener#DEFAULT_NETWORK_HOST}:{@link NetworkListener#DEFAULT_NETWORK_PORT}, using
   *     the directory in which the server was launched the server's document root
   */
  public static HttpServer createSimpleServer() {

    return createSimpleServer(".");
  }

  /**
   * @param docRoot the document root, can be <code>null</code> when no static pages are needed
   * @return a <code>HttpServer</code> configured to listen to requests on {@link
   *     NetworkListener#DEFAULT_NETWORK_HOST}:{@link NetworkListener#DEFAULT_NETWORK_PORT}, using
   *     the specified <code>docRoot</code> as the server's document root
   */
  public static HttpServer createSimpleServer(final String docRoot) {

    return createSimpleServer(docRoot, NetworkListener.DEFAULT_NETWORK_PORT);
  }

  /**
   * @param docRoot the document root, can be <code>null</code> when no static pages are needed
   * @param port the network port to which this listener will bind
   * @return a <code>HttpServer</code> configured to listen to requests on {@link
   *     NetworkListener#DEFAULT_NETWORK_HOST}:<code>port</code>, using the specified <code>docRoot
   *     </code> as the server's document root
   */
  public static HttpServer createSimpleServer(final String docRoot, final int port) {

    return createSimpleServer(docRoot, NetworkListener.DEFAULT_NETWORK_HOST, port);
  }

  /**
   * @param docRoot the document root, can be <code>null</code> when no static pages are needed
   * @param range port range to attempt to bind to
   * @return a <code>HttpServer</code> configured to listen to requests on {@link
   *     NetworkListener#DEFAULT_NETWORK_HOST}:<code>[port-range]</code>, using the specified <code>
   *     docRoot</code> as the server's document root
   */
  public static HttpServer createSimpleServer(final String docRoot, final PortRange range) {

    return createSimpleServer(docRoot, NetworkListener.DEFAULT_NETWORK_HOST, range);
  }

  /**
   * @param docRoot the document root, can be <code>null</code> when no static pages are needed
   * @param socketAddress the endpoint address to which this listener will bind
   * @return a <code>HttpServer</code> configured to listen to requests on <code>socketAddress
   *     </code>, using the specified <code>docRoot</code> as the server's document root
   */
  public static HttpServer createSimpleServer(
      final String docRoot, final SocketAddress socketAddress) {

    final InetSocketAddress inetAddr = (InetSocketAddress) socketAddress;
    return createSimpleServer(docRoot, inetAddr.getHostName(), inetAddr.getPort());
  }

  /**
   * @param docRoot the document root, can be <code>null</code> when no static pages are needed
   * @param host the network port to which this listener will bind
   * @param port the network port to which this listener will bind
   * @return a <code>HttpServer</code> configured to listen to requests on <code>host</code>:<code>
   *     port</code>, using the specified <code>docRoot</code> as the server's document root
   */
  public static HttpServer createSimpleServer(
      final String docRoot, final String host, final int port) {

    return createSimpleServer(docRoot, host, new PortRange(port));
  }

  /**
   * @param docRoot the document root, can be <code>null</code> when no static pages are needed
   * @param host the network port to which this listener will bind
   * @param range port range to attempt to bind to
   * @return a <code>HttpServer</code> configured to listen to requests on <code>host</code>:<code>
   *     [port-range]</code>, using the specified <code>docRoot</code> as the server's document root
   */
  public static HttpServer createSimpleServer(
      final String docRoot, final String host, final PortRange range) {

    final HttpServer server = new HttpServer();
    final ServerConfiguration config = server.getServerConfiguration();
    if (docRoot != null) {
      config.addHttpHandler(new StaticHttpHandler(docRoot), "/");
    }
    final NetworkListener listener = new NetworkListener("grizzly", host, range);
    server.addListener(listener);
    return server;
  }

  // ------------------------------------------------------- Protected Methods

  protected void enableJMX() {

    if (jmxManager == null) {
      synchronized (serverConfig) {
        if (jmxManager == null) {
          jmxManager = GrizzlyJmxManager.instance();
        }
      }
    }
    jmxManager.registerAtRoot(getManagementObject(false), serverConfig.getName());
  }

  protected void disableJMX() {

    if (jmxManager != null) {
      jmxManager.deregister(getManagementObject(true));
    }
  }

  // --------------------------------------------------------- Private Methods

  private void configureListener(final NetworkListener listener) {
    FilterChain chain = listener.getFilterChain();
    if (chain == null) {
      final FilterChainBuilder builder = FilterChainBuilder.newInstance();
      builder.add(new TransportFilter());
      if (listener.isSecure()) {
        SSLEngineConfigurator sslConfig = listener.getSslEngineConfig();
        if (sslConfig == null) {
          sslConfig =
              new SSLEngineConfigurator(SSLContextConfigurator.DEFAULT_CONFIG, false, false, false);
          listener.setSSLEngineConfig(sslConfig);
        }
        final SSLBaseFilter filter = new SSLBaseFilter(sslConfig);
        builder.add(filter);
      }
      final int maxHeaderSize =
          listener.getMaxHttpHeaderSize() == -1
              ? org.glassfish.grizzly.http.HttpServerFilter.DEFAULT_MAX_HTTP_PACKET_HEADER_SIZE
              : listener.getMaxHttpHeaderSize();

      // Passing null value for the delayed executor, because IdleTimeoutFilter should
      // handle idle connections for us
      final org.glassfish.grizzly.http.HttpServerFilter httpServerCodecFilter =
          new org.glassfish.grizzly.http.HttpServerFilter(
              listener.isChunkingEnabled(),
              maxHeaderSize,
              null,
              listener.getKeepAliveConfig(),
              null,
              listener.getMaxRequestHeaders(),
              listener.getMaxResponseHeaders());
      final Set<ContentEncoding> contentEncodings = configureCompressionEncodings(listener);
      for (ContentEncoding contentEncoding : contentEncodings) {
        httpServerCodecFilter.addContentEncoding(contentEncoding);
      }

      httpServerCodecFilter
          .getMonitoringConfig()
          .addProbes(serverConfig.getMonitoringConfig().getHttpConfig().getProbes());
      builder.add(httpServerCodecFilter);

      builder.add(
          new IdleTimeoutFilter(
              delayedExecutor,
              listener.getKeepAliveConfig().getIdleTimeoutInSeconds(),
              TimeUnit.SECONDS));

      final Transport transport = listener.getTransport();
      final FileCache fileCache = listener.getFileCache();
      fileCache.initialize(delayedExecutor);
      final FileCacheFilter fileCacheFilter = new FileCacheFilter(fileCache);
      fileCache
          .getMonitoringConfig()
          .addProbes(serverConfig.getMonitoringConfig().getFileCacheConfig().getProbes());
      builder.add(fileCacheFilter);

      final ServerFilterConfiguration config = new ServerFilterConfiguration(serverConfig);

      if (listener.isSendFileExplicitlyConfigured()) {
        config.setSendFileEnabled(listener.isSendFileEnabled());
        fileCache.setFileSendEnabled(listener.isSendFileEnabled());
      }

      if (listener.getBackendConfig() != null) {
        config.setBackendConfiguration(listener.getBackendConfig());
      }

      if (listener.getDefaultErrorPageGenerator() != null) {
        config.setDefaultErrorPageGenerator(listener.getDefaultErrorPageGenerator());
      }

      config.setTraceEnabled(config.isTraceEnabled() || listener.isTraceEnabled());

      config.setMaxFormPostSize(listener.getMaxFormPostSize());
      config.setMaxBufferedPostSize(listener.getMaxBufferedPostSize());

      final HttpServerFilter httpServerFilter = new HttpServerFilter(config, delayedExecutor);
      httpServerFilter.setHttpHandler(httpHandlerChain);

      httpServerFilter
          .getMonitoringConfig()
          .addProbes(serverConfig.getMonitoringConfig().getWebServerConfig().getProbes());

      builder.add(httpServerFilter);
      chain = builder.build();

      final AddOn[] addons = listener.getAddOnSet().getArray();
      if (addons != null) {
        for (AddOn addon : addons) {
          addon.setup(listener, chain);
        }
      }

      listener.setFilterChain(chain);
      final int transactionTimeout = listener.getTransactionTimeout();
      if (transactionTimeout >= 0) {
        ThreadPoolConfig threadPoolConfig = transport.getWorkerThreadPoolConfig();

        if (threadPoolConfig != null) {
          threadPoolConfig.setTransactionTimeout(
              delayedExecutor, transactionTimeout, TimeUnit.SECONDS);
        }
      }
    }
    configureMonitoring(listener);
  }

  protected Set<ContentEncoding> configureCompressionEncodings(final NetworkListener listener) {

    final CompressionConfig compressionConfig = listener.getCompressionConfig();

    if (compressionConfig.getCompressionMode() != CompressionMode.OFF) {
      final ContentEncoding gzipContentEncoding =
          new GZipContentEncoding(
              GZipContentEncoding.DEFAULT_IN_BUFFER_SIZE,
              GZipContentEncoding.DEFAULT_OUT_BUFFER_SIZE,
              new CompressionEncodingFilter(
                  compressionConfig, GZipContentEncoding.getGzipAliases()));
      final ContentEncoding lzmaEncoding =
          new LZMAContentEncoding(
              new CompressionEncodingFilter(
                  compressionConfig, LZMAContentEncoding.getLzmaAliases()));
      final Set<ContentEncoding> set = new HashSet<ContentEncoding>(2);
      set.add(gzipContentEncoding);
      set.add(lzmaEncoding);
      return set;
    } else {
      return Collections.emptySet();
    }
  }

  @SuppressWarnings("unchecked")
  private void configureMonitoring(final NetworkListener listener) {
    final Transport transport = listener.getTransport();

    final MonitoringConfig<TransportProbe> transportMonitoringCfg = transport.getMonitoringConfig();
    final MonitoringConfig<ConnectionProbe> connectionMonitoringCfg =
        transport.getConnectionMonitoringConfig();
    final MonitoringConfig<MemoryProbe> memoryMonitoringCfg =
        transport.getMemoryManager().getMonitoringConfig();
    final MonitoringConfig<ThreadPoolProbe> threadPoolMonitoringCfg =
        transport.getThreadPoolMonitoringConfig();

    transportMonitoringCfg.addProbes(
        serverConfig.getMonitoringConfig().getTransportConfig().getProbes());
    connectionMonitoringCfg.addProbes(
        serverConfig.getMonitoringConfig().getConnectionConfig().getProbes());
    memoryMonitoringCfg.addProbes(serverConfig.getMonitoringConfig().getMemoryConfig().getProbes());
    threadPoolMonitoringCfg.addProbes(
        serverConfig.getMonitoringConfig().getThreadPoolConfig().getProbes());
  }

  private void configureAuxThreadPool() {
    final AtomicInteger threadCounter = new AtomicInteger();

    auxExecutorService =
        Executors.newCachedThreadPool(
            new ThreadFactory() {

              @Override
              public Thread newThread(Runnable r) {
                final Thread newThread =
                    new DefaultWorkerThread(
                        AttributeBuilder.DEFAULT_ATTRIBUTE_BUILDER,
                        serverConfig.getName() + "-" + threadCounter.getAndIncrement(),
                        null,
                        r);
                newThread.setDaemon(true);
                return newThread;
              }
            });
  }

  private void stopAuxThreadPool() {
    final ExecutorService localThreadPool = auxExecutorService;
    auxExecutorService = null;

    if (localThreadPool != null) {
      localThreadPool.shutdownNow();
    }
  }

  // ************ Runtime config change listeners ******************

  /** Modifies handlers mapping during runtime. */
  synchronized void onAddHttpHandler(HttpHandler httpHandler, String[] mapping) {
    if (isStarted()) {
      httpHandlerChain.addHandler(httpHandler, mapping);
    }
  }

  /** Modifies handlers mapping during runtime. */
  synchronized void onRemoveHttpHandler(HttpHandler httpHandler) {
    if (isStarted()) {
      httpHandlerChain.removeHttpHandler(httpHandler);
    }
  }
}