/**
* License Agreement.
*
* Rich Faces - Natural Ajax for Java Server Faces (JSF)
*
* Copyright (C) 2007 Exadel, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License version 2.1 as published by the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.ajax4jsf.application;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import javax.faces.FacesException;
import javax.faces.component.UIComponent;
import javax.faces.component.UIViewRoot;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.el.MethodBinding;
import javax.faces.el.ValueBinding;
import javax.faces.event.PhaseId;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.ajax4jsf.Messages;
import org.ajax4jsf.resource.InternetResource;
import org.ajax4jsf.resource.InternetResourceBuilder;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;
/**
* @author asmirnov@exadel.com (latest modification by $Author: alexsmirnov $)
* @version $Revision: 1.1.2.1 $ $Date: 2007/01/09 18:57:11 $
*
*/
public class DebugOutputMaker {
private final static String LT = "<";
private final static String GT = ">";
// Attributes that should not be printed
static public final HashSet IGNORE_ATTRIBUTES;
static {
IGNORE_ATTRIBUTES = new HashSet();
IGNORE_ATTRIBUTES.add("attributes");
IGNORE_ATTRIBUTES.add("children");
IGNORE_ATTRIBUTES.add("childCount");
IGNORE_ATTRIBUTES.add("class");
IGNORE_ATTRIBUTES.add("facets");
IGNORE_ATTRIBUTES.add("facetsAndChildren");
IGNORE_ATTRIBUTES.add("parent");
IGNORE_ATTRIBUTES.add("actionListeners");
IGNORE_ATTRIBUTES.add("valueChangeListeners");
IGNORE_ATTRIBUTES.add("validators");
IGNORE_ATTRIBUTES.add("rowData");
IGNORE_ATTRIBUTES.add("javax.faces.webapp.COMPONENT_IDS");
IGNORE_ATTRIBUTES.add("javax.faces.webapp.FACET_NAMES");
IGNORE_ATTRIBUTES.add("javax.faces.webapp.CURRENT_VIEW_ROOT");
}
private static final String LOGFILE_PARAM = "org.ajax4jsf.LOGFILE";
/**
* Output error state for lifecycle.
*
* @param context
* @param phase
* TODO
* @param error
* @throws IOException
*/
public void writeErrorMessage(FacesContext context, Throwable e, String phase)
throws FacesException {
if (null == context) {
throw new FacesException(Messages.getMessage(Messages.FACES_CONTEXT_NOT_CREATED), e);
}
ExternalContext externalContext = context
.getExternalContext();
if (null == externalContext) {
throw new FacesException(Messages.getMessage(Messages.FACES_CONTEXT_HAS_NOT_EXTERNAL), e);
}
// debug for http servlet environment, portlets needs other debugger
HttpServletResponse response;
HttpServletRequest request;
try {
response = (HttpServletResponse) externalContext
.getResponse();
request = (HttpServletRequest) externalContext.getRequest();
response.setContentType("text/html;charset=UTF-8");
// set internal server error status
response.setStatus(500);
} catch (Exception er) {
throw new FacesException(Messages.getMessage(Messages.FACES_CONTEXT_HAS_NOT_RESPONSE), e);
}
PrintWriter out;
try {
out = response.getWriter();
} catch (IOException e1) {
throw new FacesException(e1.getMessage(), e);
}
UIViewRoot viewRoot = context.getViewRoot();
String viewId;
if (null != viewRoot) {
viewId = viewRoot.getViewId();
} else {
viewId = request.getRequestURL().toString();
}
// output html prolog
out.println("<html><head><title>" +
Messages.getMessage(Messages.ERROR_ON_PAGE, viewId) +
"</title></head><body>");
// write script
writeScriptAndStyle(out);
// Header
PhaseId facesPhase = (PhaseId) context.getExternalContext().getRequestMap().get(DebugLifecycle.PHASE_ID_PARAM);
String errorMessage =
(facesPhase == null)
? Messages.getMessage(Messages.LIFECYCLE_ERROR, viewId, phase)
: Messages.getMessage(Messages.LIFECYCLE_ERROR_AT_PHASE, new Object[]{viewId, phase, facesPhase.toString()});
out.println("<h1 >");
out.println(errorMessage);
out.println("</h1>");
response.setHeader("Ajax-Error-Message", errorMessage+",\n caused by "+e.getClass().getName()+", with message: "+e.getMessage());
// Output exceptions
writeExceptionStack(e, out);
// Output view tree :
if (null != viewRoot) {
writeComponentsTree(context, out);
} else {
out.print("<h2 class=' a4j_debug'> " +
Messages.getMessage(Messages.COMPONENT_TREE_NOT_CREATED) +
" </h2>");
}
// Out scope variables
writeContextVariables(context, out);
// Write log output iframe :
writeLog(context, out);
// out html tail
out.println("</body></html>");
out.flush();
out.close();
}
/**
* @param e
* @param out
*/
public void writeExceptionStack(Throwable e, PrintWriter out) {
out.println("<h2 class=\"a4j_debug\">Exceptions: </h2>");
Throwable error = e;
int errorId = 0;
String caused = "exception ";
while (null != error) {
out.print("<h3 onclick=\"toggle('exception" + errorId + "')\" class='exception a4j_debug'>");
writeToggleMark(out,"exception" + errorId);
out.print( caused + error.getClass().getName() + " : "
+ error.getMessage() + "</h3>");
out.println("<div id='exception" + errorId
+ "' style='display: none;' class='exception'><p>Exception stack :</p><pre>");
StackTraceElement[] stackTrace = error.getStackTrace();
for (int i = 0; i < stackTrace.length; i++) {
out.print(" at " + stackTrace[i].getClassName());
out.print("." + stackTrace[i].getMethodName());
out.println(" in " + stackTrace[i].getFileName() + " line "
+ stackTrace[i].getLineNumber());
}
// error.printStackTrace(out);
out.println("</pre></div>");
error = error.getCause();
caused = "caused by ";
errorId++;
}
}
/**
* @param context
* @param out
*/
public void writeContextVariables(FacesContext context, PrintWriter out) {
out.print("<h2 class=\"a4j_debug\" onclick=\"toggle('variables')\">");
writeToggleMark(out,"variables");
out.println("Faces variables: </h2><div id='variables' style='display: none;' class='variables a4j_debug'>");
writeVariables(out,context);
out.println("</div>");
}
/**
* @param context
* @param out
*/
public void writeComponentsTree(FacesContext context, PrintWriter out) {
out
.print("<h2 class=\"a4j_debug\" onclick=\"toggle('tree')\">");
writeToggleMark(out,"tree");
out.println("Component tree: </h2><div id='tree' style='display: none;' class='tree a4j_debug'><dl>");
writeComponent(context, out, context.getViewRoot(), null);
out.println("</dl></div>");
}
public void writeScriptAndStyle(PrintWriter out) {
writeScript(out);
writeStyleSheet(out);
}
/**
* @param context
* @param out
* @throws FacesException
*/
public void writeLog(FacesContext context, PrintWriter out) throws FacesException {
String logname = context.getExternalContext().getInitParameter(LOGFILE_PARAM);
if (null != logname) {
InternetResource logResource = InternetResourceBuilder.getInstance().createResource(this,logname);
out.print("<h2 onclick=\"toggle('log')\" class=\"a4j_debug\">");
writeToggleMark(out, "log");
out
.println("Faces log: </h2><div id='log' style='display: none;' class='log a4j_debug'>");
out.print("<iframe name='log' class='log' src='"+logResource.getUri(context,null)+"'><a href='src='"+logResource.getUri(context,null)+"'>Faces log file </a> </iframe>");
out.println("</div>");
}
}
public void writeToggleMark(PrintWriter out,String id) {
out.print("<span style=\"display:none;\" id=\""+id+"_expanded\" >- </span>");
out.print("<span style=\"display:inline;\" id=\""+id+"_collapsed\" >+</span> ");
}
/**
* Out component with properties and it's facets and childrens
* @param context TODO
* @param out
* @param viewRoot
*/
private void writeComponent(FacesContext context, PrintWriter out,
UIComponent component, String facetName) {
String clientId = "_tree:"+component.getClientId(context);
out.println("<dt onclick=\"toggle('"+clientId+"')\" class='tree'>");
writeToggleMark(out,clientId);
// out component name
if (null != facetName) {
out.print("Facet:'" + facetName + "' ");
}
out.println("<code>" + component.getClass().getName() + "</code> Id:["+component.getId()+"]");
out.println("</dt>");
out.println("<dd id='"+clientId+"' style='display:none;' class='tree' ><ul class='tree'>");
// out bean properties
try {
PropertyDescriptor propertyDescriptors[] = PropertyUtils.getPropertyDescriptors(component);
for (int i = 0; i < propertyDescriptors.length; i++) {
if (PropertyUtils.isReadable(component,propertyDescriptors[i].getName())) {
String name = propertyDescriptors[i].getName();
ValueBinding vb = component.getValueBinding(name);
if (vb != null) {
writeAttribute(out, name, vb.getExpressionString());
} else {
if (!IGNORE_ATTRIBUTES.contains(name)) {
try {
String value = BeanUtils.getProperty(component,name);
writeAttribute(out, name, value);
} catch (Exception e) {
writeAttribute(out, name, null);
}
}
}
}
}
} catch (Exception e) {
// Do nothing - we in error page.
}
// out bindings
// out attributes map
for (Iterator it = component.getAttributes().entrySet().iterator(); it
.hasNext();) {
Map.Entry entry = (Map.Entry) it.next();
writeAttribute(out, (String) entry.getKey(), entry.getValue());
}
// out listeners
out.println("</ul></dd>");
if (component.getFacetsAndChildren().hasNext()) {
out.println("<dd class='tree_childs'><dl class='tree_childs'>");
// out childs of this component
// facets
for (Iterator facetEntry = component.getFacets().entrySet()
.iterator(); facetEntry.hasNext();) {
Map.Entry entry = (Map.Entry) facetEntry.next();
writeComponent(context, out,
(UIComponent) entry.getValue(), (String) entry.getKey());
}
// childs components
for (Iterator childIter = component.getChildren().iterator(); childIter
.hasNext();) {
UIComponent child = (UIComponent) childIter.next();
writeComponent(context, out, child, null);
}
out.println("</dl></dd>");
}
}
private void writeAttribute(PrintWriter out, String name, Object value) {
if (IGNORE_ATTRIBUTES.contains(name))
return;
if (name.startsWith("javax.faces.webapp.UIComponentTag.")) {
name = name
.substring("javax.faces.webapp.UIComponentTag.".length());
}
out.print("<li>");
out.print(name);
out.print("=\"");
if (value != null) {
if (value instanceof UIComponent) {
out.print("[id:");
out.print(((UIComponent) value).getId());
out.print(']');
} else if (value instanceof MethodBinding) {
out.print(((MethodBinding) value).getExpressionString());
} else if (value instanceof ValueBinding) {
out.print(((ValueBinding) value).getExpressionString());
} else {
out.print(value.toString());
}
} else {
out.print("NULL");
}
out.println("\"</li>");
}
private void writeVariables(PrintWriter out, FacesContext faces) {
ExternalContext ctx = faces.getExternalContext();
writeVariables(out, ctx.getRequestParameterMap(), "Request Parameters");
writeVariables(out, ctx.getRequestMap(), "Request Attributes");
if (ctx.getSession(false) != null) {
writeVariables(out, ctx.getSessionMap(), "Session Attributes");
}
writeVariables(out, ctx.getApplicationMap(), "Application Attributes");
}
private void writeVariables(PrintWriter out, Map vars, String caption) {
out.print("<table><caption>");
out.print(caption);
out.println("</caption><thead><tr><th style=\"width: 10%; \">Name</th><th style=\"width: 90%; \">Value</th></tr></thead><tbody>");
boolean written = false;
if (!vars.isEmpty()) {
SortedMap map = new TreeMap(vars);
Map.Entry entry = null;
String key = null;
for (Iterator itr = map.entrySet().iterator(); itr.hasNext(); ) {
entry = (Map.Entry) itr.next();
key = entry.getKey().toString();
if (key.indexOf('.') == -1) {
out.println("<tr><td>");
out.println(key.replaceAll("<", LT).replaceAll(">", GT));
out.println("</td><td><span class='value'>");
Object value = entry.getValue();
out.println(value.toString().replaceAll("<", LT).replaceAll(">", GT));
out.println("</span>");
try {
PropertyDescriptor propertyDescriptors[] = PropertyUtils.getPropertyDescriptors(value);
if (propertyDescriptors.length>0) {
out.print("<div class='properties'><ul class=\'properties\'>");
for (int i = 0; i < propertyDescriptors.length; i++) {
String beanPropertyName = propertyDescriptors[i].getName();
if (PropertyUtils.isReadable(value,beanPropertyName
)) {
out.print("<li class=\'properties\'>");
out.print(beanPropertyName+" = "+BeanUtils.getProperty(value,beanPropertyName));
out.print("</li>");
}
}
out.print("</ul></div>");
}
} catch (Exception e) {
// TODO: log exception
}
out.println("</td></tr>");
written = true;
}
}
}
if (!written) {
out.println("<tr><td colspan=\"2\"><em>None</em></td></tr>");
}
out.println("</tbody></table>");
}
/**
* @param out
*/
private void writeScript(PrintWriter out) {
out
.println("<script type='text/javascript' language='javascript'>\n"
+ "function toggle(id) {\n"
+ "var style = document.getElementById(id).style;\n"
+ "if ('block' == style.display) {\n"
+ "style.display = 'none';\n"
+ "document.getElementById(id+'_collapsed').style.display = 'inline';\n"
+ "document.getElementById(id+'_expanded').style.display = 'none';\n"
+ "} else {\n"
+ "style.display = 'block';\n"
+ "document.getElementById(id+'_collapsed').style.display = 'none';\n"
+ "document.getElementById(id+'_expanded').style.display = 'inline';\n"
+ "}\n" + "}\n\n" +
"</script>");
}
/**
* @param out
*/
private void writeStyleSheet(PrintWriter out) {
out
.println("<style type=\'text/css\' >\n" +
"div.a4j_debug, .a4j_debug span, .a4j_debug td, .a4j_debug th, .a4j_debug caption { font-family: Verdana, Arial, Sans-Serif; }\n" +
".a4j_debug li{\n" +
" list-style-position : inside;\n" +
"}\n" +
"\n" +
".a4j_debug li, .a4j_debug pre { padding: 0; margin: 0; font-size : 12px;}\n" +
".a4j_debug ul { padding: 0 0 10 0; margin: 0; font-size : 12px;}\n" +
".a4j_debug h1 { color: #fff; background-color : #a00; font-size : 17px; padding : 7px 10px 10px 10px}\n" +
"h2.a4j_debug , h2.a4j_debug span { color: #a00; font-size : 17px; padding : 0px 0px 0px 10px;}\n" +
"h2.a4j_debug a { text-decoration: none; color: #a00; }\n" +
".exception { color: #000; font-size : 14px; padding : 0px 0px 0px 10px;}\n" +
".grayBox { padding: 8px; margin: 10px 0; border: 1px solid #CCC; background-color: #f9f9f9; font-size : 12px; }\n" +
"#error { color: #900; font-weight: bold; font-size: medium; }\n" +
"#trace, #tree, #vars { display: none; }\n" +
".a4j_debug code { font-size: medium; font-size : 14px;}\n" +
"#tree dl { color: #666; }\n" +
"#tree dd { font-size : 12px;}\n" +
"#tree dt { border: 1px solid #DDD; padding: 2px 4px 4px 4px; border-left: 2px solid #a00; font-family: \"Courier New\", Courier, mono; font-size: small; font-size : 12px; margin-top: 2px; margin-bottom: 2px;}\n" +
".uicText { color: #999; }\n" +
".a4j_debug table { border: 1px solid #CCC; border-collapse: collapse; border-spacing: 0px; width: 100%; text-align: left; }\n" +
".a4j_debug td { border: 1px solid #CCC; font-size : 12px; vertical-align : top}\n" +
".a4j_debug thead tr th { padding: 2px; color: #030; background-color: #F9F9F9; font-size : 12px;}\n" +
".a4j_debug tbody tr td { padding: 10px 6px; }\n" +
".a4j_debug table caption { text-align: left; padding: 20 0 5 0; font-size : 12px; font-weight : bold;}\n" +
".value {font-size : 12px; font-weight : bold;}\n" +
"div.log { width: 100%; height: 400px;}\n" +
"iframe.log { width: 99%; height: 99%; border: 1px solid #CCC;}\n" +
"</style>");
}
}