DOMResponseWriter.java :  » J2EE » ICEfaces-2.0.0 » org » icefaces » context » Java Open Source

Java Open Source » J2EE » ICEfaces 2.0.0 
ICEfaces 2.0.0 » org » icefaces » context » DOMResponseWriter.java
/*
 * Version: MPL 1.1
 *
 * "The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (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.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations under
 * the License.
 *
 * The Original Code is ICEfaces 1.5 open source software code, released
 * November 5, 2006. The Initial Developer of the Original Code is ICEsoft
 * Technologies Canada, Corp. Portions created by ICEsoft are Copyright (C)
 * 2004-2009 ICEsoft Technologies Canada, Corp. All Rights Reserved.
 *
 * Contributor(s): _____________________.
 *
*/

package org.icefaces.context;

import org.icefaces.util.DOMUtils;
import org.w3c.dom.*;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.faces.context.PartialViewContext;

public class DOMResponseWriter extends ResponseWriter {

    private static Logger log = Logger.getLogger("org.icefaces.context");

    private static final String OLD_DOM = "org.icefaces.old-dom";
    private static final String STATE_FIELD_MARKER = "~com.sun.faces.saveStateFieldMarker~";
    private static final String XML_MARKER = "<?xml";
    private static final String DOCTYPE_MARKER = "<!DOCTYPE";

    private Writer writer;
    private Document document;
    private Node cursor;
    private List<Node> stateNodes = new ArrayList<Node>();

    public DOMResponseWriter(Writer writer) {
        this(DOMUtils.getNewDocument(), writer);
    }

    public DOMResponseWriter(Document document, Writer writer) {
        this.writer = writer;
        this.document = document;
    }

    /**
     * <p>Return the content type (such as "text/html") for this {@link
     * javax.faces.context.ResponseWriter}.  Note: this must not include the "charset="
     * suffix.</p>
     */
    public String getContentType() {
        //TODO:  Should we make this switchable for other content types
        //A somewhat dated article of interest appears here: http://hixie.ch/advocacy/xhtml
        return "text/html";
    }

    /**
     * <p>Return the character encoding (such as "ISO-8859-1") for this
     * {@link javax.faces.context.ResponseWriter}.  Please see <a
     * href="http://www.iana.org/assignments/character-sets">the
     * IANA</a> for a list of character encodings.</p>
     */
    public String getCharacterEncoding() {
        //TODO:  What is the logic for actually determining what this should be?
        return "UTF-8";
    }

    /**
     * Write a portion of an array of characters.
     *
     * @param cbuf Array of characters
     * @param off  Offset from which to start writing characters
     * @param len  Number of characters to write
     * @throws java.io.IOException If an I/O error occurs
     */
    public void write(char[] cbuf, int off, int len) throws IOException {
        //TODO: I believe we only need to implement the single basic write() method as the
        //other write methods all eventually call down to this anyway.
        if (0 == len) {
            return;
        }

        if (null == document) {
            writer.write(cbuf, off, len);
            return;
        }
        
        try {
            String data = new String(cbuf, off, len);

            //Write out the <?xml or <!DOCTYPE preamble as text rather than
            //trying to handle them as DOM nodes, but only if it's not an
            //Ajax request.  In that case we ignore them.
            if (data.startsWith(XML_MARKER) || data.startsWith(DOCTYPE_MARKER)) {
                PartialViewContext pvc = FacesContext.getCurrentInstance().getPartialViewContext();
                if( pvc == null || !pvc.isAjaxRequest() ){
                    writer.write(data);
                }
                return;
            }

            appendToCursor(data);
        } catch (Exception e) {
            if (log.isLoggable(Level.INFO)) {
                log.log(Level.INFO, "cannot write " + new String(cbuf, off, len), e);
            }
        }
    }

    public void write(String str) throws IOException {
        if ("".equals(str)) {
            return;
        }
        if (STATE_FIELD_MARKER.equals(str)) {
            //TODO: suppress insertion of state node into DOM rather than
            //remove later.  This special case is caused by the fact that
            //during partial responses the Sun implementation does not
            //write the state marker
            Node stateNode = document.createTextNode(str);
            stateNodes.add(stateNode);
            appendToCursor(stateNode);
            return;
        }
        if (null == document) {
            if (log.isLoggable(Level.INFO)) {
                log.info(writer.getClass() + " raw write str " + str);
            }
            writer.write(str);
            return;
        }
        try {
            appendToCursor(document.createTextNode(str));
        } catch (Exception e) {
            log.log(Level.SEVERE, "failed to write " + str, e);
        }
    }


    /**
     * <p>Flush any ouput buffered by the output method to the
     * underlying Writer or OutputStream.  This method
     * will not flush the underlying Writer or OutputStream;  it
     * simply clears any values buffered by this {@link javax.faces.context.ResponseWriter}.</p>
     */
    public void flush() throws IOException {
    }

    /**
     * Close the stream, flushing it first.  Once a stream has been closed,
     * further write() or flush() invocations will cause an IOException to be
     * thrown.  Closing a previously-closed stream, however, has no effect.
     *
     * @throws java.io.IOException If an I/O error occurs
     */
    public void close() throws IOException {
    }

    /**
     * <p>Write whatever text should begin a response.</p>
     *
     * @throws java.io.IOException if an input/output error occurs
     */
    public void startDocument() throws IOException {

        //TODO: Should we always create a new document here?  The constructor takes one
        //as an argument which would always get replaced when this method is called.
        document = DOMUtils.getNewDocument();
        cursor = document;
    }

    /**
     * <p>Write whatever text should end a response.  If there is an open
     * element that has been created by a call to <code>startElement()</code>,
     * that element will be closed first.</p>
     *
     * @throws java.io.IOException if an input/output error occurs
     */
    public void endDocument() throws IOException {
        boolean isPartialRequest = FacesContext.getCurrentInstance().getPartialViewContext().isPartialRequest();

        //full-page requests write directly to the response
        if (!isPartialRequest) {
            DOMUtils.printNode(document, writer);
        }

        if (null != document.getDocumentElement()) {
            for (Node stateNode : stateNodes) {
                stateNode.getParentNode().removeChild(stateNode);
            }
            stateNodes.clear();
            FacesContext.getCurrentInstance().getViewRoot().getAttributes().put(OLD_DOM, document);
        }

        document = null;
        cursor = null;
//        savedJSFStateCursor = null;
    }

    /**
     * <p>Write the start of an element, up to and including the
     * element name.  Once this method has been called, clients can
     * call the <code>writeAttribute()</code> or
     * <code>writeURIAttribute()</code> methods to add attributes and
     * corresponding values.  The starting element will be closed
     * (that is, the trailing '>' character added)
     * on any subsequent call to <code>startElement()</code>,
     * <code>writeComment()</code>,
     * <code>writeText()</code>, <code>endElement()</code>,
     * <code>endDocument()</code>, <code>close()</code>,
     * <code>flush()</code>, or <code>write()</code>.</p>
     *
     * @param name      Name of the element to be started
     * @param component The {@link javax.faces.component.UIComponent} (if any) to which
     *                  this element corresponds
     * @throws java.io.IOException  if an input/output error occurs
     * @throws NullPointerException if <code>name</code>
     *                              is <code>null</code>
     */
    public void startElement(String name, UIComponent component) throws IOException {

        //TODO:  Does this ever happen - ie does startDocument not get called for some reason?
        if (null == document) {
            document = DOMUtils.getNewDocument();
        }
        pointCursorAt(appendToCursor(document.createElement(name)));
    }

    /**
     * <p>Write the end of an element, after closing any open element
     * created by a call to <code>startElement()</code>.  Elements must be
     * closed in the inverse order from which they were opened; it is an
     * error to do otherwise.</p>
     *
     * @param name Name of the element to be ended
     * @throws java.io.IOException  if an input/output error occurs
     * @throws NullPointerException if <code>name</code>
     *                              is <code>null</code>
     */
    public void endElement(String name) throws IOException {
        pointCursorAt(cursor.getParentNode());
    }

    /**
     * <p>Write an attribute name and corresponding value, after converting
     * that text to a String (if necessary), and after performing any escaping
     * appropriate for the markup language being rendered.
     * This method may only be called after a call to
     * <code>startElement()</code>, and before the opened element has been
     * closed.</p>
     *
     * @param name     Attribute name to be added
     * @param value    Attribute value to be added
     * @param property Name of the property or attribute (if any) of the
     *                 {@link javax.faces.component.UIComponent} associated with the containing element,
     *                 to which this generated attribute corresponds
     * @throws IllegalStateException if this method is called when there
     *                               is no currently open element
     * @throws java.io.IOException   if an input/output error occurs
     * @throws NullPointerException  if <code>name</code> is
     *                               <code>null</code>
     */
    public void writeAttribute(String name, Object value, String property) throws IOException {
        //TODO: As per the javadoc, is there any escaping we should be aware of here?
        Attr attribute = document.createAttribute(name.trim());
        attribute.setValue(String.valueOf(value));
        appendToCursor(attribute);
    }

    /**
     * <p>Write a URI attribute name and corresponding value, after converting
     * that text to a String (if necessary), and after performing any encoding
     * appropriate to the markup language being rendered.
     * This method may only be called after a call to
     * <code>startElement()</code>, and before the opened element has been
     * closed.</p>
     *
     * @param name     Attribute name to be added
     * @param value    Attribute value to be added
     * @param property Name of the property or attribute (if any) of the
     *                 {@link javax.faces.component.UIComponent} associated with the containing element,
     *                 to which this generated attribute corresponds
     * @throws IllegalStateException if this method is called when there
     *                               is no currently open element
     * @throws java.io.IOException   if an input/output error occurs
     * @throws NullPointerException  if <code>name</code> is
     *                               <code>null</code>
     */
    public void writeURIAttribute(String name, Object value, String property) throws IOException {
        //TODO: Should there be more comprehensvie encoding here?
        String stringValue = String.valueOf(value);
        if (stringValue.startsWith("javascript:")) {
            writeAttribute(name, stringValue, property);
        } else {
            writeAttribute(name, stringValue.replace(' ', '+'), property);
        }
    }

    /**
     * <p>Write a comment containing the specified text, after converting
     * that text to a String (if necessary), and after performing any escaping
     * appropriate for the markup language being rendered.  If there is
     * an open element that has been created by a call to
     * <code>startElement()</code>, that element will be closed first.</p>
     *
     * @param comment Text content of the comment
     * @throws java.io.IOException  if an input/output error occurs
     * @throws NullPointerException if <code>comment</code>
     *                              is <code>null</code>
     */
    public void writeComment(Object comment) throws IOException {
        //TODO: We don't always consistently check for null document?  Should we? Escaping?
        String commentString = String.valueOf(comment);
        if (null == document) {
            writer.write(commentString);
            return;
        }
        appendToCursor(document.createComment(commentString));
    }

    /**
     * <p>Write an object, after converting it to a String (if necessary),
     * and after performing any escaping appropriate for the markup language
     * being rendered.  If there is an open element that has been created
     * by a call to <code>startElement()</code>, that element will be closed
     * first.</p>
     *
     * @param text     Text to be written
     * @param property Name of the property or attribute (if any) of the
     *                 {@link javax.faces.component.UIComponent} associated with the containing element,
     *                 to which this generated text corresponds
     * @throws java.io.IOException  if an input/output error occurs
     * @throws NullPointerException if <code>text</code>
     *                              is <code>null</code>
     */
    public void writeText(Object text, String property) throws IOException {
        //TODO: Escaping?  Null checks and exceptions?
        String textString = String.valueOf(text);
        if (textString.length() == 0) {
            return;
        }
        appendToCursor(textString);
    }

    /**
     * <p>Write text from a character array, after any performing any
     * escaping appropriate for the markup language being rendered.
     * If there is an open element that has been created by a call to
     * <code>startElement()</code>, that element will be closed first.</p>
     *
     * @param text Text to be written
     * @param off  Starting offset (zero-relative)
     * @param len  Number of characters to be written
     * @throws IndexOutOfBoundsException if the calculated starting or
     *                                   ending position is outside the bounds of the character array
     * @throws java.io.IOException       if an input/output error occurs
     * @throws NullPointerException      if <code>text</code>
     *                                   is <code>null</code>
     */
    public void writeText(char[] text, int off, int len) throws IOException {
        //TODO: Escaping?  Null checks?
        if (len == 0) {
            return;
        }
        writeText(new String(text, off, len), null);
    }

    /**
     * <p>Create and return a new instance of this {@link javax.faces.context.ResponseWriter},
     * using the specified <code>Writer</code> as the output destination.</p>
     *
     * @param writer The <code>Writer</code> that is the output destination
     */
    public ResponseWriter cloneWithWriter(Writer writer) {
        //TODO: Should we be creating a brand new one here or using the same document
        //with a new writer?
        if (writer.getClass().getName().endsWith("FastStringWriter")) {
            return new BasicResponseWriter(writer, getContentType(), getCharacterEncoding());
        }
        return new DOMResponseWriter(null, writer);
    }


    //
    private Node appendToCursor(String data) {
        return appendToCursor(document.createTextNode(data));
    }

    private Node appendToCursor(Node node) {
        try {
            if (log.isLoggable(Level.FINEST)) {
                log.finest("appending " + DOMUtils.toDebugString(node) + " into " + DOMUtils.toDebugString(cursor));
            }
            return cursor.appendChild(node);

        } catch (DOMException e) {
            String message = "failed to append " + DOMUtils.toDebugString(node) + " into " + DOMUtils.toDebugString(cursor);
            log.log(Level.SEVERE, message, e);
            throw new RuntimeException(message, e);
        }
    }

    private Node appendToCursor(Attr node) {
        try {
            if (log.isLoggable(Level.FINEST)) {
                log.finest("Appending " + DOMUtils.toDebugString(node) + " into " + DOMUtils.toDebugString(cursor));
            }
            return ((Element) cursor).setAttributeNode(node);
        } catch (DOMException e) {
            String message = "failed to append " + DOMUtils.toDebugString(node) + " into " + DOMUtils.toDebugString(cursor);
            log.log(Level.SEVERE, message, e);
            throw new RuntimeException(message, e);
        } catch (ClassCastException e) {
            String message = "cursor is not an element: " + DOMUtils.toDebugString(cursor);
            log.log(Level.SEVERE, message, e);
            throw new RuntimeException(message, e);
        }
    }

    private void pointCursorAt(Node node) {
        if (log.isLoggable(Level.FINEST)) {
            log.finest("moving cursor to " + DOMUtils.toDebugString(node));
        }
        cursor = node;
    }

    public Document getDocument() {
        return document;
    }

    public Document getOldDocument() {
        return (Document) FacesContext.getCurrentInstance().getViewRoot().getAttributes().get(OLD_DOM);
    }

    public Node getCursorParent() {
        return cursor;
    }

    public void setCursorParent(Node cursorParent) {
        this.cursor = cursorParent;
    }

}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.