org.icefaces.impl.context.DOMResponseWriter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of icefaces Show documentation
Show all versions of icefaces Show documentation
The core library of the ICEfaces product.
/*
* Copyright 2004-2014 ICEsoft Technologies Canada Corp.
*
* 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 org.icefaces.impl.context;
import org.icefaces.impl.fastinfoset.com.sun.xml.fastinfoset.dom.DOMDocumentParser;
import org.icefaces.impl.fastinfoset.com.sun.xml.fastinfoset.dom.DOMDocumentSerializer;
import org.icefaces.impl.fastinfoset.org.jvnet.fastinfoset.FastInfosetException;
import org.icefaces.impl.util.DOMUtils;
import org.icefaces.util.EnvUtils;
import org.w3c.dom.*;
import javax.el.ELContext;
import javax.el.ValueExpression;
import javax.faces.application.ProjectStage;
import javax.faces.component.UIComponent;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.faces.context.PartialViewContext;
import javax.faces.context.ResponseWriter;
import javax.faces.context.ResponseWriterWrapper;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FilterWriter;
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
public class DOMResponseWriter extends ResponseWriterWrapper {
private static Logger log = Logger.getLogger("org.icefaces.impl.context.DOMResponseWriter");
public static final String DEFAULT_ENCODING = "UTF-8";
private String encoding;
public static final String DEFAULT_TYPE = "text/html";
private String contentType;
public static final String OLD_DOM = "org.icefaces.old-dom";
protected static final String XML_MARKER = " stateNodes = new ArrayList();
private boolean suppressNextNode = false;
// flag to indicate we shouldn't escape
private boolean dontEscape;
// flag to indicate that we're writing a 'script' or 'style' element
private boolean isScript;
// flag to indicate that we're writing a 'style' element
private boolean isStyle;
private static Method getPassThroughAttributesMethod;
static {
try {
getPassThroughAttributesMethod = UIComponent.class.getDeclaredMethod("getPassThroughAttributes", new Class[] { boolean.class });
} catch (NoSuchMethodException e) {
getPassThroughAttributesMethod = null;
}
}
private Map> elementPasstroughAttributes = new HashMap();
public DOMResponseWriter(Writer writer, String encoding, String contentType) {
this.writer = writer;
if (writer instanceof ResponseWriter) {
wrapped = (ResponseWriter) writer;
}
this.encoding = (encoding != null) ? encoding : DEFAULT_ENCODING;
this.contentType = (contentType != null) ? contentType : DEFAULT_TYPE;
}
public ResponseWriter getWrapped() {
return wrapped;
}
public String getCharacterEncoding() {
return encoding;
}
public String getContentType() {
return contentType;
}
public void writeDoctype(String doctype) throws IOException {
processXMLPreamble(doctype);
}
public void writePreamble(String preamble) throws IOException {
processXMLPreamble(preamble);
}
public void write(char[] cbuf, int off, int len) throws IOException {
if (suppressNextNode) {
//a component is attempting to render outside its subtree
return;
}
if (0 == len) {
return;
}
if (null == document) {
writer.write(cbuf, off, len);
return;
}
try {
String data = new String(cbuf, off, len);
//Handle the passthroughAttributes = component.getPassThroughAttributes();
if (passthroughAttributes != null && !passthroughAttributes.isEmpty()) {
Map copiedAttributes = new HashMap(passthroughAttributes);
elementPasstroughAttributes.put(cursor, copiedAttributes);
}
}
}
public void endElement(String name) throws IOException {
if ("form".equalsIgnoreCase(name)) {
writeClientWindow();
}
if (FacesContext.getCurrentInstance().isProjectStage(ProjectStage.Development)) {
if (log.isLoggable(Level.WARNING)) {
if (!cursor.getNodeName().equals(name)) {
logUnclosedNode(cursor);
}
}
cursor.setUserData("closed", Boolean.TRUE, null);
}
Map passthroughAttributes = elementPasstroughAttributes.get(cursor);
if (passthroughAttributes != null) {
FacesContext context = FacesContext.getCurrentInstance();
for (Map.Entry entry : passthroughAttributes.entrySet()) {
Object valObj = entry.getValue();
String val = getAttributeValue(context, valObj);
String key = entry.getKey();
writeAttribute(key, val, key);
}
elementPasstroughAttributes.remove(cursor);
}
pointCursorAt(cursor.getParentNode());
}
private void writeClientWindow() throws IOException {
FacesContext context = FacesContext.getCurrentInstance();
if (EnvUtils.isMyFaces() && context.isPostback()) {
Integer index = (Integer) context.getExternalContext().getRequestMap().get("form-index");
index = index == null ? 1 : (index + 1);
context.getExternalContext().getRequestMap().put("form-index", index);
String clientWindow = context.getExternalContext().getClientWindow().getId();
startElement("input", null);
writeAttribute("id", context.getViewRoot().getId() + ":javax.faces.ClientWindow:" + index, null);
writeAttribute("name", "javax.faces.ClientWindow", null);
writeAttribute("type", "hidden", null);
writeAttribute("value", clientWindow, null);
endElement("input");
}
}
private void logUnclosedNode(Node node) {
if (node == null) return;
StringBuilder path = new StringBuilder();
Node tempCursor = node;
while (tempCursor != null) {
if (tempCursor != node) {
path.insert(0, " -> ");
}
path.insert(0, tempCursor.getNodeName());
tempCursor = tempCursor.getParentNode();
}
Node idNode = node.hasAttributes() ? node.getAttributes().getNamedItem("id") : null;
log.log(Level.WARNING, "Missing end-element for: "
+ node.getNodeName() + (idNode == null ? "" : ("[" + idNode.toString() + "]"))
+ " (path: " + path + ")");
}
public void writeAttribute(String name, Object value, String property) throws IOException {
if (null == value) {
return;
}
if (EnvUtils.isMyFaces()){
//Similar to Mojarra, we need to track the ViewState and ClientWindow nodes for MyFaces so that
//we can removed them from the DOM after they have been rendered out to the response.
if(name != null && name.equalsIgnoreCase("name") && value instanceof String) {
String val = (String) value;
if ("javax.faces.ViewState".equalsIgnoreCase(val)) {
stateNodes.add(cursor);
}
}
}
Attr attribute = document.createAttribute(name.trim());
attribute.setValue(String.valueOf(value));
appendToCursor(attribute);
}
public void writeURIAttribute(String name, Object value, String property) throws IOException {
String stringValue = String.valueOf(value);
if (stringValue.startsWith("javascript:")) {
writeAttribute(name, stringValue, property);
} else {
writeAttribute(name, stringValue.replace(' ', '+'), property);
}
}
public void writeComment(Object comment) throws IOException {
String commentString = String.valueOf(comment);
if (null == document) {
writer.write(commentString);
return;
}
appendToCursor(document.createComment(commentString));
}
public void writeText(Object text, String property) throws IOException {
if (suppressNextNode) {
//a component is attempting to render outside its subtree
return;
}
if (text == null) {
throw new NullPointerException("WriteText method cannot write null text");
}
String textString = String.valueOf(text);
if (textString.length() == 0) {
return;
}
if (!dontEscape) {
textString = DOMUtils.escapeAnsi(textString);
}
appendToCursor(textString);
}
public void writeText(char[] text, int off, int len) throws IOException {
if (suppressNextNode) {
//a component is attempting to render outside its subtree
return;
}
// escaping done in writeText(object, String) method
if (len == 0) {
return;
}
writeText(new String(text, off, len), null);
}
public void writeText(Object text, UIComponent component, String property) throws IOException {
writeText(text, property);
}
public void startCDATA() throws IOException {
if (null == document) {
document = DOMUtils.getNewDocument();
}
pointCursorAt(appendToCursor(document.createCDATASection("")));
}
public void endCDATA() throws IOException {
pointCursorAt(cursor.getParentNode());
}
public ResponseWriter cloneWithWriter(Writer writer) {
String enc = getCharacterEncoding();
String type = getContentType();
if (writer instanceof ResponseWriter) {
wrapped = (ResponseWriter) writer;
enc = wrapped.getCharacterEncoding();
type = wrapped.getContentType();
}
ResponseWriter clone = null;
if (writer.getClass().getName().endsWith("FastStringWriter")) {
clone = new BasicResponseWriter(writer, enc, type);
} else {
clone = new DOMResponseWriter(writer, enc, type);
}
return clone;
}
private Node appendToCursor(String data) {
if (cursor == null) {
Element doc = document.getDocumentElement();
if( doc != null ){
cursor = doc;
} else {
cursor = document;
}
}
if (cursor.getNodeType() == Node.CDATA_SECTION_NODE) {
CDATASection section = (CDATASection) cursor;
section.appendData(data);
return section;
} else {
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));
}
if (cursor == null) {
Element doc = document.getDocumentElement();
if( doc != null ){
cursor = doc;
} else {
cursor = document;
}
}
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));
}
Node result = ((Element) cursor).setAttributeNode(node);
if ("id".equals(node.getName())) {
((Element) cursor).setIdAttributeNode(node, true);
}
return result;
} 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;
}
/**
* Prepare for rendering into subtrees.
*/
public void startSubtreeRendering(Document doc) {
if (doc == null) {
//This call attempts to get the old DOM from the View map
//which should have already been attempted but we try
//it here again to make sure.
doc = getOldDocument();
if (doc == null) {
refreshDocument();
return;
}
}
}
public Document getDocument() {
return document;
}
public void saveOldDocument() throws IOException {
FacesContext facesContext = FacesContext.getCurrentInstance();
if (!EnvUtils.isCompressDOM(facesContext)) {
facesContext.getViewRoot().getViewMap().put(OLD_DOM, document);
return;
}
byte[] data = serializeDocument(document);
facesContext.getViewRoot().getViewMap().put(OLD_DOM, data);
}
private static byte[] serializeDocument(Document document) throws IOException {
if (document == null) {
return new byte[0];
} else {
byte[] data;
DOMDocumentSerializer serializer = new DOMDocumentSerializer();
ByteArrayOutputStream out = new ByteArrayOutputStream(10000);
serializer.setOutputStream(out);
serializer.serialize(document);
data = out.toByteArray();
return data;
}
}
public Document getOldDocument() {
return DOMResponseWriter.getOldDocument(
FacesContext.getCurrentInstance());
}
public static Document getOldDocument(FacesContext facesContext) {
Object oldDOMObject = facesContext.getViewRoot()
.getViewMap().get(OLD_DOM);
if (null == oldDOMObject) {
return null;
}
if (!EnvUtils.isCompressDOM(facesContext)) {
return (Document) oldDOMObject;
}
try {
return deserializeDocument((byte[]) oldDOMObject);
} catch (Exception e) {
log.log(Level.SEVERE, "Failed to restore old DOM ", e);
return DOMUtils.getNewDocument();
}
}
private static Document deserializeDocument(byte[] data) throws FastInfosetException, IOException {
if (data.length == 0) {
return null;
} else {
Document document = DOMUtils.getNewDocument();
//FastInfoset does not tolerate stray xmlns declarations
document.setStrictErrorChecking(false);
DOMDocumentParser parser = new DOMDocumentParser();
ByteArrayInputStream in = new ByteArrayInputStream(data);
parser.parse(document, in);
return document;
}
}
public Node getCursorParent() {
return cursor;
}
public void setCursorParent(Node cursorParent) {
this.cursor = cursorParent;
}
//If postback navigation occurs (navigating back to the same
//page with the same view ID) in the middle of partial rendering,
//it may be necessary to refresh the document for the DOMResponseWriter
//so that the subtrees can actually be rendered.
private void refreshDocument() {
document = DOMUtils.getNewDocument();
Element root = document.createElement("html");
document.appendChild(root);
cursor = document.getDocumentElement();
}
public void setDocument(Document doc) {
this.document = doc;
this.cursor = doc;
}
private boolean isScriptOrStyle(String name) {
if ("script".equalsIgnoreCase(name)) {
isScript = true;
dontEscape = true;
} else if ("style".equalsIgnoreCase(name)) {
isStyle = true;
dontEscape = true;
} else {
isScript = false;
isStyle = false;
dontEscape = false;
}
return (isScript || isStyle);
}
private class WriteViewStateMarkup extends FilterWriter {
protected WriteViewStateMarkup(Writer out) {
super(out);
}
public void write(String str) throws IOException {
if (EnvUtils.getStateMarker().equals(str) ) {
FacesContext fc = FacesContext.getCurrentInstance();
out.write("");
} else {
out.write(str);
}
}
}
private String getAttributeValue(FacesContext context, Object expr) {
String val;
if (expr instanceof ValueExpression) {
ELContext elContext = context.getELContext();
val = (String) ((ValueExpression) expr).getValue(elContext);
} else {
val = (String) expr;
}
return val;
}
}