Package net.zschech.gwt.comet.server.impl

Source Code of net.zschech.gwt.comet.server.impl.AbstractGrizzlyAsyncServlet$GrizzyOutputStream

/*
* Copyright 2010 Richard Zschech.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package net.zschech.gwt.comet.server.impl;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;

import com.sun.grizzly.comet.CometContext;
import com.sun.grizzly.comet.CometEngine;
import com.sun.grizzly.comet.CometEvent;
import com.sun.grizzly.comet.CometHandler;
import com.sun.grizzly.comet.CometWriter;

public abstract class AbstractGrizzlyAsyncServlet extends NonBlockingAsyncServlet {
 
  private final Field socketChannelField;
 
  private CometEngine cometEngine;
  private CometContext<?> cometContext;
  private Selector selector;
 
  public AbstractGrizzlyAsyncServlet() throws SecurityException, NoSuchFieldException {
    socketChannelField = CometWriter.class.getDeclaredField("socketChannel");
    socketChannelField.setAccessible(true);
  }
 
  @Override
  public void init(ServletContext context) throws ServletException {
    super.init(context);
    try {
      cometEngine = CometEngine.getEngine();
      cometContext = cometEngine.register(context.getContextPath());
      cometContext.setExpirationDelay(-1);
      cometContext.setBlockingNotification(true);
    }
    catch (IllegalStateException e) {
      throw new ServletException(e.getMessage());
    }
  }
 
  @Override
  protected void shutdown() {
    cometEngine.unregister(getServletContext().getContextPath());
    super.shutdown();
  }
 
  @Override
  public OutputStream getOutputStream(OutputStream outputStream) {
    return new GrizzyOutputStream(outputStream);
  }
 
  @Override
  public Object suspend(CometServletResponseImpl response, CometSessionImpl session, HttpServletRequest request) throws IOException {
    assert Thread.holdsLock(response);
    assert session == null || !Thread.holdsLock(session);
   
    // Unfortunately we have to flush the response line and headers before switching to async mode :-(
    response.flush();
   
    initSelector(response);
   
    CometHandlerImpl handler = new CometHandlerImpl(response, session);
    try {
      cometContext.addCometHandler(handler);
    }
    catch (IllegalStateException e) {
      throw new IOException(e.getMessage());
    }
   
    if (session != null) {
      GrizzyOutputStream asyncOutputStream = (GrizzyOutputStream) response.getAsyncOutputStream();
      asyncOutputStream.wrapped = new GrizzyAsyncBufferOutputStream(handler);
    }
   
    return handler;
  }
 
  @Override
  public void terminate(CometServletResponseImpl response, CometSessionImpl session, boolean serverInitiated, Object suspendInfo) {
    assert Thread.holdsLock(response);
    assert session == null || !Thread.holdsLock(session);
   
    final CometHandlerImpl handler = (CometHandlerImpl) suspendInfo;
    if (serverInitiated) {
      if (session != null) {
        handler.registerAsyncWrite();
      }
      else {
        cometContext.resumeCometHandler(handler);
      }
    }
  }
 
  @Override
  public void invalidate(CometSessionImpl session) {
    enqueued(session);
  }
 
  @Override
  public void enqueued(CometSessionImpl session) {
    CometServletResponseImpl response = session.getResponse();
    if (response != null) {
      if (response.setProcessing(true)) {
        synchronized (response) {
          if (!response.isTerminated()) {
            Object suspendInfo = response.getSuspendInfo();
            if (suspendInfo != null) {
              CometHandlerImpl handler = (CometHandlerImpl) suspendInfo;
              handler.registerAsyncWrite();
            }
          }
        }
      }
    }
  }
 
  private Selector initSelector(CometServletResponseImpl response) {
    if (selector != null) {
      return selector;
    }
   
    selector = getSelector(response);
    return selector;
  }
 
  protected abstract Selector getSelector(CometServletResponseImpl response);
 
  private class CometHandlerImpl implements CometHandler<Object> {
   
    private final CometServletResponseImpl response;
    private final CometSessionImpl session;
    private final boolean chunked;
    private volatile boolean active;
    private volatile ByteBuffer buffer;
    private volatile AtomicInteger activeFailureCount = new AtomicInteger();
    private AtomicBoolean registered = new AtomicBoolean();
   
    public CometHandlerImpl(CometServletResponseImpl response, CometSessionImpl session) {
      this.response = response;
      this.session = session;
      this.chunked = response.getResponse().containsHeader("Transfer-Encoding");
    }
   
    @Override
    public void attach(Object attachment) {
    }
   
    public void registerAsyncWrite() {
      assert Thread.holdsLock(response);
      // Unfortunately Grizzly does not setup it CometContext with the CometHandler immediately so we have to schedule it
      // until it is active. See CometEngine.handle(AsyncProcessorTask) executeServlet() is called before cometContext.addActiveHandler(cometTask)
      if (!active && !response.isTerminated() && activeFailureCount.get() < 100) {
        cometEngine.getThreadPool().execute(new Runnable() {
          @Override
          public void run() {
            try {
              cometContext.registerAsyncWrite(CometHandlerImpl.this);
              selector.wakeup();
              if (activeFailureCount.getAndSet(0) != 0) {
                log("Comet handler " + Integer.toHexString(hashCode()) + " active");
              }
            }
            catch (IllegalStateException e) {
//              log("Comet handler " + Integer.toHexString(hashCode()) + " not active yet, retrying: " + e.getMessage());
              activeFailureCount.incrementAndGet();
              registerAsyncWrite();
            }
          }
        });
      }
      else {
        if (registered.compareAndSet(false, true)) {
          try {
            cometContext.registerAsyncWrite(this);
            selector.wakeup();
            if (activeFailureCount.getAndSet(0) != 0) {
              log("Comet handler " + Integer.toHexString(hashCode()) + " active");
            }
          }
          catch (CancelledKeyException e) {
            if (!response.isTerminated()) {
              response.setTerminated(true);
            }
          }
          catch (IllegalStateException e) {
  //          log("Comet handler " + Integer.toHexString(hashCode()) + " not active yet, giving up: " + e.getMessage());
          }
        }
      }
    }
   
    @Override
    public void onInitialize(CometEvent event) throws IOException {
      synchronized (response) {
        if (!session.isEmpty()) {
          registerAsyncWrite();
        }
      }
    }
   
    @Override
    public void onInterrupt(CometEvent event) throws IOException {
      terminate();
    }
   
    @Override
    public void onTerminate(CometEvent event) throws IOException {
      terminate();
    }
   
    private void terminate() {
      synchronized (response) {
        if (!response.isTerminated()) {
          response.setTerminated(false);
        }
      }
    }
   
    @Override
    public void onEvent(CometEvent event) throws IOException {
      active = true;
      if (event.getType() == CometEvent.WRITE) {
        CometWriter writer = (CometWriter) event.attachment();
       
        try {
          // Unfortunately Grizzly's CometWriter assumes that the transfer encoding is going to be chunked and that
          // only one chunk is going to be written to the response so we have to write to the SocketChanel directly.
          // https://grizzly.dev.java.net/issues/show_bug.cgi?id=791
          SocketChannel socketChannel = (SocketChannel) socketChannelField.get(writer);
         
          GrizzyAsyncBufferOutputStream output = (GrizzyAsyncBufferOutputStream) ((GrizzyOutputStream) response.getAsyncOutputStream()).getWrapped();
         
          while (true) {
            if (buffer == null || !buffer.hasRemaining()) {
              synchronized (response) {
                int count;
                while ((count = output.getCount()) == 0 && !session.isEmpty()) {
                  session.writeQueue(response, true);
                }
               
                if (count == 0) {
                  buffer = null;
                  break;
                }
               
                byte[] bytes = output.getBytes();
                if (chunked) {
                  byte[] chunkLength = Integer.toHexString(count).getBytes("ASCII");
                 
                  buffer = ByteBuffer.allocate(chunkLength.length + count + 4);
                  buffer.put(chunkLength);
                  buffer.put((byte) '\r');
                  buffer.put((byte) '\n');
                  buffer.put(bytes, 0, count);
                  buffer.put((byte) '\r');
                  buffer.put((byte) '\n');
                  buffer.flip();
                }
                else {
                  buffer = ByteBuffer.wrap(bytes, 0, count);
                }
                output.clear();
              }
            }
           
            int write = socketChannel.write(buffer);
            if (write <= 0) {
              // can't write any more
              cometContext.registerAsyncWrite(this);
              break;
            }
            else if (!buffer.hasRemaining()) {
              buffer = null;
            }
          }
         
          if (buffer == null) {
            if (response.isTerminated()) {
              cometContext.resumeCometHandler(this);
            }
            else {
              registered.set(false);
              response.setProcessing(false);
            }
          }
        }
        catch (IllegalArgumentException e) {
          log("Error accessing socket chanel", e);
        }
        catch (IllegalAccessException e) {
          log("Error accessing socket chanel", e);
        }
      }
    }
  }
 
  private static class GrizzyOutputStream extends OutputStream {
   
    private volatile OutputStream wrapped;
   
    public GrizzyOutputStream(OutputStream wrapped) {
      this.wrapped = wrapped;
    }
   
    @Override
    public void write(byte[] b) throws IOException {
      wrapped.write(b);
    }
   
    @Override
    public void write(byte[] b, int off, int len) throws IOException {
      wrapped.write(b, off, len);
    }
   
    @Override
    public void write(int b) throws IOException {
      wrapped.write(b);
    }
   
    @Override
    public void close() throws IOException {
      wrapped.close();
    }
   
    @Override
    public void flush() throws IOException {
      wrapped.flush();
    }
   
    public OutputStream getWrapped() {
      return wrapped;
    }
  }
 
  private static class GrizzyAsyncBufferOutputStream extends ByteArrayOutputStream {
   
    private final CometHandlerImpl handler;
   
    private GrizzyAsyncBufferOutputStream(CometHandlerImpl handler) {
      this.handler = handler;
    }
   
    @Override
    public void write(byte[] b) {
      write(b, 0, b.length);
    }
   
    @Override
    public void write(byte[] b, int off, int len) {
      super.write(b, off, len);
      handler.registerAsyncWrite();
    }
   
    @Override
    public void write(int b) {
      super.write(b);
      handler.registerAsyncWrite();
    }
   
    public byte[] getBytes() {
      return buf;
    }
   
    public int getCount() {
      return count;
    }
   
    public void clear() {
      count = 0;
      buf = new byte[buf.length];
    }
  }
}
TOP

Related Classes of net.zschech.gwt.comet.server.impl.AbstractGrizzlyAsyncServlet$GrizzyOutputStream

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.
.js','ga'); ga('create', 'UA-20639858-1', 'auto'); ga('send', 'pageview');