My JSF Books/Videos My JSF Tutorials OmniFaces/JSF PPTs
JSF 2.3 Tutorial
JSF Caching Tutorial
JSF Navigation Tutorial
JSF Scopes Tutorial
JSF Page Author Beginner's Guide
OmniFaces 2.3 Tutorial Examples
OmniFaces 2.2 Tutorial Examples
JSF Events Tutorial
OmniFaces Callbacks Usages
JSF State Tutorial
JSF and Design Patterns
JSF 2.3 New Features (2.3-m04)
Introduction to OmniFaces
25+ Reasons to use OmniFaces in JSF
OmniFaces Validators
OmniFaces Converters
JSF Design Patterns
Mastering OmniFaces
Reusable and less-verbose JSF code

My JSF Resources ...

Java EE Guardian
Member of JCG Program
Member MVB DZone
Blog curated on ZEEF
OmniFaces is an utility library for JSF, including PrimeFaces, RichFaces, ICEfaces ...

[OmniFaces Utilities] - Find the right JSF OmniFaces 2 utilities methods/functions

Search on blog

Petition by Java EE Guardians

Twitter

vineri, 20 februarie 2015

Exploring OmniFaces buffered HTTP Servlet response implementation

In this post we will draw the main lines of creating a buffered HTTP Servlet response. Is far for our aim to present the "bowels" of Servlets API, but we can sketch a simple flow of an HTTP response and we will see how to obtain a buffered version of it.
Basically, a Servlet receive a request from the client and provide an answer, well-known as response. So, we can draw the base line in the javax.servlet.ServletResponse interface. The Servlet container creates this object especially to assist us during sending a response to the client. The response can be plain text (character text) or binary. The response-text is managed through a java.io.PrintWriter object, which extends the functionality of a java.io.Writer for printing formatted representations of objects to a text-output stream (obtained via ServletResponse#getWriter() method) and the response-binary data is managed through a javax.servlet.ServletOutputStream object, which extends the functionality of java.io.OutputStream to provide an output stream for sending binary data to the client (obtained via ServletResponse#getOutputStream() method).
The ServletResponse interface is extended with HTTP-specific functionality in sending a response by the javax.servlet.http.HttpServletResponse interface (notice the presence of 'http' word in each class name that is related to HTTP specific functionalities). Most probably, you are familiar with this interface from the doGet() and doPost() methods of a Servlet - this is the second argument in these methods, next to the HttpServletRequest object. We know that the Servlet container will provide them out of the box, and we just call the getWriter() or getOutputStream() methods to obtain an object capable to write a response to the client. Most probably, you saw many times the below snippet:
...
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
          throws ServletException, IOException {

 try (PrintWriter out = response.getWriter()) {
      //...
      out.println(some_character_text);
 }
       
 try (ServletOutputStream out = response.getOutputStream()) {
      //...
      out.write(some_bytes);
 }
}
...

Based on this snippet, we know that some_character_text and some_bytes will be sent to the client.
A convenient implementation of the HttpServletResponse interface is named, javax.servlet.http.HttpServletResponseWrapper. This class is mainly important for developers who want to provide a custom implementation (behavior) to the response from a Servlet. When this class is extended it provides at least two major advantages: first, you can focus on overriding only the necessary methods, and second, via HttpServletResponseWrapper constructor, we can obtain a response adaptor wrapping the given response:

public HttpServletResponseWrapper(HttpServletResponse response)

As you will see later, OmniFaces extends this class in order to provide a buffered response. But, before that, OmniFaces focused on java.io.Writer and java.io.OutputStream. First, it extends the java.io.Writer to obtain "a resettable buffered writer capable to buffer everything until the given buffer size, regardless of flush calls. Only when the buffer size is exceeded, or when close is called, then the buffer will be actually flushed" - source: OmniFaces documentation. You can see the implementation below and focus on the highlighted part (originating here):

public class ResettableBufferedWriter extends Writer implements ResettableBuffer {

 private Writer writer;
 private Charset charset;
 private CharArrayWriter buffer;
 private int bufferSize;
 private int writtenBytes;

 public ResettableBufferedWriter(Writer writer, int bufferSize, String characterEncoding) {
  this.writer = writer;
  this.bufferSize = bufferSize;
  this.charset = Charset.forName(characterEncoding);
  this.buffer = new CharArrayWriter(bufferSize);
  }

 @Override
 public void write(char[] chars, int offset, int length) throws IOException {
  if (buffer != null) {
      if ((writtenBytes += charset.encode(CharBuffer.wrap(chars, offset, length)).limit()) > bufferSize) {
           writer.write(buffer.toCharArray());
           writer.write(chars, offset, length);
           buffer = null;
       } else {
           buffer.write(chars, offset, length);
       }
  } else {
     writer.write(chars, offset, length);
  }
 }

 @Override
 public void reset() {
  buffer = new CharArrayWriter(bufferSize);
  writtenBytes = 0;
 }

 @Override
 public void flush() throws IOException {
  if (buffer == null) {
      writer.flush();
  }
 }

 @Override
 public void close() throws IOException {
  if (buffer != null) {
      writer.write(buffer.toCharArray());
      buffer = null;
  }
  writer.close();
 }

 @Override
 public boolean isResettable() {
  return buffer != null;
 }
}

In order to cover the binary part, OmniFaces follows the same principle as above and extends the java.io.OutputStream and creates " a resettable buffered output stream will buffer everything until the given buffer size, regardless of flush calls. Only when the buffer size is exceeded, or when close is called, then the buffer will be actually flushed." - source: OmniFaces documentation. You can see the implementation below and focus on the highlighted part (originating here):

public class ResettableBufferedOutputStream extends OutputStream implements ResettableBuffer {

 private OutputStream output;
 private ByteArrayOutputStream buffer;
 private int bufferSize;
 private int writtenBytes;

 public ResettableBufferedOutputStream(OutputStream output, int bufferSize) {
  this.output = output;
  this.bufferSize = bufferSize;
  this.buffer = new ByteArrayOutputStream(bufferSize);
 }

 @Override
 public void write(int b) throws IOException {
  write(new byte[] { (byte) b }, 0, 1);
 }

 @Override
 public void write(byte[] bytes) throws IOException {
  write(bytes, 0, bytes.length);
 }

 @Override
 public void write(byte[] bytes, int offset, int length) throws IOException {
  if (buffer != null) {
      if ((writtenBytes += (length - offset)) > bufferSize) {
           output.write(buffer.toByteArray());
           output.write(bytes, offset, length);
           buffer = null;
      } else {
           buffer.write(bytes, offset, length);
      }
  } else {
     output.write(bytes, offset, length);
  }
 }

 @Override
 public void reset() {
  buffer = new ByteArrayOutputStream(bufferSize);
              writtenBytes = 0;
       }

       @Override
       public void flush() throws IOException {
              if (buffer == null) {
                     output.flush();
              }
       }

       @Override
       public void close() throws IOException {
              if (buffer != null) {
                     output.write(buffer.toByteArray());
                     buffer = null;
              }

              output.close();
       }

       @Override
       public boolean isResettable() {
              return buffer != null;
       }

}

The ResettableBuffer interface is the base interface for a resettable buffer (nothing fancy here):

public interface ResettableBuffer {

 void reset();
 boolean isResettable();

}

So, as a quick resume, we have a resettable-text-buffer and a resettable-binary-buffer ready for use. Further, OmniFaces uses the ResettableBufferedWriter  and ResettableBufferedOutputStream in the extension of the HttpServletResponseWrapper, named HttpServletResponseOutputWrapper (abstract implementation). Is a good moment to point the fact that OmniFaces allows us to choose between the default PrintWriter/ServletOutputStream and the custom ones (new PrintWriter/ServletOutputStream for using the above buffers) via a simple flag named, passThrough (if passThrough is true (default false), then use defaults). You can see the implementation below and focus on the highlighted part (originating here):

public abstract class HttpServletResponseOutputWrapper extends HttpServletResponseWrapper {

 private static final String ERROR_GETOUTPUT_ALREADY_CALLED =
       "getOutputStream() has already been called on this response.";
 private static final String ERROR_GETWRITER_ALREADY_CALLED =
       "getWriter() has already been called on this response.";

 private ServletOutputStream output;
 private PrintWriter writer;
 private ResettableBuffer buffer;
 private boolean passThrough;

 public HttpServletResponseOutputWrapper(HttpServletResponse wrappedResponse) {
  super(wrappedResponse);
 }

 protected abstract OutputStream createOutputStream();

 @Override
 public ServletOutputStream getOutputStream() throws IOException {
  if (passThrough) {
      return super.getOutputStream();
  }

  if (writer != null) {
      throw new IllegalStateException(ERROR_GETWRITER_ALREADY_CALLED);
  }

  if (output == null) {
      buffer = new ResettableBufferedOutputStream(createOutputStream(), getBufferSize());
      output = new ServletOutputStream() {
         @Override
         public void write(int b) throws IOException {
          (OutputStream) buffer).write(b);
         }
         @Override
         public void write(byte[] bytes) throws IOException {
          ((OutputStream) buffer).write(bytes);
         }
         @Override
         public void write(byte[] bytes, int offset, int length) throws IOException {
          ((OutputStream) buffer).write(bytes, offset, length);
         }
         @Override
         public void flush() throws IOException {
          ((OutputStream) buffer).flush();
         }
         @Override
         public void close() throws IOException {
          ((OutputStream) buffer).close();
         }
      };
  }

  return output;
 }

 @Override
 public PrintWriter getWriter() throws IOException {
  if (passThrough) {
      return super.getWriter();
  }

  if (output != null) {
      throw new IllegalStateException(ERROR_GETOUTPUT_ALREADY_CALLED);
  }

  if (writer == null) {
      buffer = new ResettableBufferedWriter(new OutputStreamWriter(createOutputStream(),getCharacterEncoding()), getBufferSize(), getCharacterEncoding());
     writer = new PrintWriter((Writer) buffer);
  }

  return writer;
 }

 @Override
 public void flushBuffer() throws IOException {
  super.flushBuffer();

  if (passThrough) {
      return;
  }

  if (writer != null) {
      writer.flush();
  } else if (output != null) {
              output.flush();
         }
 }

 public void close() throws IOException {
  if (writer != null) {
      writer.close();
  } else if (output != null) {
             output.close();
         }
 }

 @Override
 public void reset() {
  super.reset();
  if (buffer != null) {
      buffer.reset();
  }
 }

 @Override
 public boolean isCommitted() {
  return super.isCommitted() || (buffer != null && !buffer.isResettable());
 }

 public boolean isPassThrough() {
  return passThrough;
 }

 public void setPassThrough(boolean passThrough) {
  this.passThrough = passThrough;
 }
}

One more thing is relevant here, the createOutputStream() abstract method. When a developer need to extend the HttpServletResponseOutputWrapper, it can simply override the createOutputStream() method. The indicated output stream will be used in both, getOutputStream() and getWriter().
Now, OmniFaces provides an out of the box implementation of an HTTP servlet response that buffers the entire response body. The buffered response body is available as a byte array via a method named, getBuffer() or as a string via a method named, getBufferAsString().You can see the implementation below and focus on the highlighted part (originating here):

public class BufferedHttpServletResponse extends HttpServletResponseOutputWrapper {

 private final ByteArrayOutputStream buffer;
      
 public BufferedHttpServletResponse(HttpServletResponse response) {
  super(response);
  buffer = new ByteArrayOutputStream(response.getBufferSize());
 }

 @Override
 protected OutputStream createOutputStream() {
  return buffer;
 }

 public byte[] getBuffer() throws IOException {
  close();
  return buffer.toByteArray();
 }

 public String getBufferAsString() throws IOException {
  return new String(getBuffer(), getCharacterEncoding());
 }
}

Finally, we have reached the top of the iceberg. Now, you can try your own implementation by extending the HttpServletResponseOutputWrapper, or use this implementation. OmniFaces use this implementation in several artifacts, per example, you may want to check the source code for the ResourceInclude component. Here it is a small fragment of how it is used:

...
FacesContext context;
ExternalContext externalContext = context.getExternalContext();
HttpServletRequest request = (HttpServletRequest) externalContext.getRequest();
HttpServletResponse response = (HttpServletResponse) externalContext.getResponse();
BufferedHttpServletResponse bufferedResponse = new BufferedHttpServletResponse(response);

request.getRequestDispatcher((String) getAttributes().get("path")).include(request, bufferedResponse);
...

Of course, you can used it for many other scenarios. Another example can be seen in "Use OmniFaces to Buffer FacesServlet Output".

Niciun comentariu :

Trimiteți un comentariu

JSF BOOKS COLLECTION

Postări populare

OmniFaces/JSF Fans

Follow by Email

Visitors Starting 4 September 2015

Locations of Site Visitors