/* * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 * as published by the Free Software Foundation. * * Resin Open Source 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, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * * Free Software Foundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Alex Rojkov */ package com.caucho.jsf.dev; import com.caucho.hessian.io.Hessian2Input; import com.caucho.hessian.io.Hessian2Output; import com.caucho.util.L10N; import com.caucho.jsf.webapp.*; import javax.el.ELContext; import javax.el.ValueExpression; import javax.faces.FactoryFinder; import javax.faces.component.UIViewRoot; import javax.faces.context.FacesContext; import javax.faces.context.FacesContextFactory; import javax.faces.lifecycle.Lifecycle; import javax.faces.lifecycle.LifecycleFactory; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import java.lang.reflect.Field; import java.net.URLDecoder; import java.net.URLEncoder; import java.util.Date; import java.util.List; import java.util.Map; import java.util.logging.Logger; public class JsfDeveloperAidServlet extends GenericServlet { private static final Logger log = Logger.getLogger(FacesServletImpl.class.getName()); private static final L10N L = new L10N(com.caucho.jsf.dev.JsfDeveloperAid.class); private ServletContext _webApp; private FacesContextFactory _facesContextFactory; private Lifecycle _lifecycle; public void init(ServletConfig config) throws ServletException { _webApp = config.getServletContext(); _facesContextFactory = (FacesContextFactory) FactoryFinder.getFactory(FactoryFinder.FACES_CONTEXT_FACTORY); LifecycleFactory lifecycleFactory = (LifecycleFactory) FactoryFinder.getFactory(FactoryFinder.LIFECYCLE_FACTORY); String name = config.getInitParameter("javax.faces.LIFECYCLE_ID"); if (name == null) name = _webApp.getInitParameter("javax.faces.LIFECYCLE_ID"); if (name == null) name = LifecycleFactory.DEFAULT_LIFECYCLE; _lifecycle = lifecycleFactory.getLifecycle(name); } private void printControls(PrintWriter out, HttpServletRequest request, HttpSession session) { if (request.getParameter("viewId") != null) out.println(" <br/><br/><a href=\"" + request.getContextPath() + "/caucho.jsf.developer.aid" + (session == null ? "" : ";jsessionid=" + session.getId()) + "\"><em>" + "Show Available Views" + "</em></a>"); out.println(" <br/><br/><a href=\"" + request.getContextPath() + "/caucho.jsf.developer.aid" + (session == null ? "" : ";jsessionid=" + session.getId()) + "?save=\"><em>" + "Save to file" + "</em></a>"); out.println( " <br/><br/><form enctype=\"multipart/form-data\" method=\"POST\"><em>Load from file</em><input name=\"file\" type=\"file\"/><input type=\"submit\" value=\"Upload\"/></form>"); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { ServletInputStream in = request.getInputStream(); byte []data = null; int position = 0; Scan scan = new Scan(); int count; boolean found = false; byte []buffer = new byte[4096]; while ((count = in.read(buffer, position, buffer.length - position)) > 0) { if (found) continue; if (count + position == buffer.length) { final byte []newData = new byte[buffer.length * 2]; System.arraycopy(buffer, 0, newData, 0, buffer.length); buffer = newData; } if (scan.boundaryEnd == -1) { for (int i = 0; i < position + count; i++) { if (buffer[i] == '\n') { scan.boundaryEnd = i - 1; scan.pointer = i; break; } } } position = position + count; found = find(buffer, scan, position); if (found) { data = buffer; buffer = new byte[256]; position = 0; } } if (found) { int start = -1; for (int i = scan.boundaryEnd; i < data.length; i++) { if (data[i] == '\n' && data[i - 2] == '\n') { start = i; break; } } Hessian2Input input = new Hessian2Input(new ByteArrayInputStream(data, start + 1, scan .pointer - start - 3)); Object obj = input.readObject(); HttpSession session = request.getSession(true); session.setAttribute("caucho.jsf.developer.aid", obj); } else { response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } } private class Scan { private int pointer = 0; private int boundaryIdx = 0; private int boundaryEnd = -1; } public boolean find(byte []data, Scan scan, int limit) { for (; scan.pointer < limit; scan.pointer++) { for (; scan.boundaryIdx < scan.boundaryEnd; scan.boundaryIdx++) { if (limit >= (scan.pointer + scan.boundaryIdx)) { if (data[scan.pointer + scan.boundaryIdx] != data[scan.boundaryIdx]) { scan.boundaryIdx = 0; break; } } else break; } if (scan.boundaryIdx == scan.boundaryEnd) return true; } return false; } public void service(ServletRequest req, ServletResponse res) throws IOException, ServletException { final HttpServletRequest request = (HttpServletRequest) req; final HttpServletResponse response = (HttpServletResponse) res; final PrintWriter out = res.getWriter(); if ("POST".equalsIgnoreCase(request.getMethod())) { doPost(request, response); } final HttpSession session = request.getSession(); final Map<String, com.caucho.jsf.dev.JsfDeveloperAid.JsfRequestSnapshot> aidMap; if (session != null) aidMap = (Map<String, JsfDeveloperAid.JsfRequestSnapshot>) session.getAttribute("caucho.jsf.developer.aid"); else aidMap = null; if (req.getParameter("save") != null) { if (aidMap != null) serveAidMap(aidMap, res); else response.sendError(HttpServletResponse.SC_NO_CONTENT); return; } final FacesContext oldContext = FacesContext.getCurrentInstance(); FacesContext context = null; try { context = _facesContextFactory.getFacesContext(_webApp, req, res, _lifecycle); res.setCharacterEncoding("UTF-8"); res.setContentType("text/html"); out.println( "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">"); out.println( "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\">"); if (session == null || aidMap == null) { out.println("<body>"); printControls(out, request, null); out.println("</body>"); out.println("</html>"); return; } final String viewId = req.getParameter("viewId"); if (viewId == null) { out.println(" <head>"); out.print(" <title>"); out.print("Available Views"); out.println("</title>"); out.println(" </head>"); out.println(" <body>"); out.println(" <em>Available Views</em>"); out.println(" <ul>"); for (String view : aidMap.keySet()) { out.println(" <li>"); out.println(" <a href=\"" + request.getContextPath() + "/caucho.jsf.developer.aid;jsessionid=" + session.getId() + "?viewId=" + view + "\">" + view + "</a>"); out.println(" </li>"); } out.println(" </ul>"); printControls(out, request, session); out.println(" </body>"); out.println("</html>"); return; } else { out.println("<head>"); out.print("<title>View: "); out.print(viewId); out.println("</title>"); out.println("<style type=\"text/css\" media=\"all\">"); out.println("#header ul {list-style: none;padding: 0;margin: 0;}"); out.println( "#header li {float: left;border: 1px solid;border-bottom-width: 0;margin: 0 0.5em 0 0; font-weight: bold}"); out.println("#header a {display: block;padding: 0 1em;}"); out.println( "#header #selected {position: relative;top: 1px;background: white; font-weight: normal}"); out.println("#content {border: 1px solid;clear: both;}"); out.println("#view {padding: 10px, 10px, 10px, 10px}"); out.println("table {width: 100%}"); out.println("td {border: 1px dotted}"); out.println("h1 {margin: 0;padding: 0 0 1em 0;}"); out.println("</style>"); out.println("</head>"); // out.println("<body>"); out.println(" <div id=\"header\">"); out.println(" <ul>"); final String phaseId = req.getParameter("phaseId"); String valueExpression = req.getParameter("valueExpression"); if (valueExpression != null) valueExpression = URLDecoder.decode(valueExpression, "UTF-8"); com.caucho.jsf.dev.JsfDeveloperAid.JsfRequestSnapshot snapshot = aidMap.get(viewId); out.print(" <li" + (phaseId == null ? " id=\"selected\"" : "") + ">"); out.print("<a href=\"" + request.getContextPath() + "/caucho.jsf.developer.aid;jsessionid=" + session.getId() + "?viewId=" + viewId + "\">" + "Request Info" + "</a>"); out.println("</li>"); com.caucho.jsf.dev.JsfDeveloperAid.ViewRoot viewRoot = null; com.caucho.jsf.dev.JsfDeveloperAid.ViewRoot []viewRoots = snapshot.getPhases(); for (com.caucho.jsf.dev.JsfDeveloperAid.ViewRoot root : viewRoots) { String phase = root.getPhase(); boolean selected = false; if (phase.equals(phaseId) && valueExpression == null) { selected = true; viewRoot = root; } out.print(" <li" + (selected ? " id=\"selected\"" : "") + ">"); out.print("<a href=\"" + request.getContextPath() + "/caucho.jsf.developer.aid;jsessionid=" + session.getId() + "?viewId=" + viewId + "&phaseId=" + phase + "\">" + phase + "</a>"); out.println("</li>"); } if (valueExpression != null) { out.print(" <li id=\"selected\">"); out.print("<a href=\"" + request.getContextPath() + "/caucho.jsf.developer.aid;jsessionid=" + session.getId() + "?viewId=" + viewId + "&phaseId=" + phaseId + "&valueExpression=" + URLEncoder.encode(valueExpression, "UTF-8") + "\">" + valueExpression + "</a>"); out.println("</li>"); } out.println(" </ul>"); out.println(" </div>"); out.println(" <div id=\"content\">"); if (valueExpression != null) { com.caucho.jsf.dev.JsfDeveloperAid.ViewRoot root = viewRoots[1]; UIViewRoot uiViewRoot = new UIViewRoot(); uiViewRoot.setLocale(root.getLocale()); uiViewRoot.setRenderKitId(root.getRenderKitId()); //need view for resolving property bundles. context.setViewRoot(uiViewRoot); printEvaluated(context, request, out, valueExpression, viewId, phaseId); out.println("<br/>"); out.println("<em><a href=\"" + request.getContextPath() + "/caucho.jsf.developer.aid;jsessionid=" + session.getId() + "?viewId=" + viewId + "&phaseId=" + phaseId + "\">" + "<<< Back" + "</a></em>"); } else if (phaseId == null) { out.println("<table>"); out.println("<thead>"); out.println( "<tr><td colspan=\"2\" align=\"center\"><strong>Snoop</strong></td></tr>"); out.println( "<tr><td><strong>Name</strong></td><td><strong>Value</strong></td></tr>"); out.println("</thead>"); out.println("<tbody>"); //headers out.println( "<tr><td colspan=\"2\" align=\"center\"><em>Headers</em></td></tr>"); Map<String, String> headers = snapshot.getHeaderMap(); for (String header : headers.keySet()) { String value = headers.get(header); out.println("<tr><td><em>" + header + "</em></td><td><em>" + value + "</em></td></tr>"); } //parameters out.println( "<tr><td colspan=\"2\" align=\"center\"><em>Parameters</em></td></tr>"); Map<String, String> parameters = snapshot.getParameterMap(); for (String parameter : parameters.keySet()) { String value = parameters.get(parameter); out.println("<tr><td><em>" + parameter + "</em></td><td><em>" + value + "</em></td></tr>"); } out.println("</tbody>"); out.println("</table>"); } else { out.println(" <div id=\"view\">"); printComponentTree(request, out, viewRoot, null, viewId, phaseId, 0); out.println(" </div>"); //snoop out.println(" <table>"); out.println(" <thead>"); out.println( " <tr><td colspan=\"2\" align=\"center\"><strong>Snoop</strong></td></tr>"); out.println( " <tr><td><strong>Name</strong></td><td><strong>Value</strong></td></tr>"); out.println(" </thead>"); out.println(" <tbody>"); //request out.println( " <tr><td colspan=\"2\" align=\"center\"><em>Request</em></td></tr>"); printBeanMap(out, viewRoot.getRequestMap()); //session out.println( " <tr><td colspan=\"2\" align=\"center\"><em>Session</em></td></tr>"); printBeanMap(out, viewRoot.getSessionMap()); //application out.println( " <tr><td colspan=\"2\" align=\"center\"><em>Application</em></td></tr>"); printBeanMap(out, viewRoot.getApplicationMap()); out.println(" </tbody>"); out.println(" </table>"); } out.println(" </div>"); printControls(out, request, session); out.println(" </body>"); out.println("</html>"); } out.flush(); } catch (IOException e) { throw e; } finally { if (context != null) context.release(); } } private void printBeanMap(PrintWriter out, Map<String, JsfDeveloperAid.Bean> map) { for (String key : map.keySet()) { JsfDeveloperAid.Bean bean = map.get(key); if (bean == null) { out.println(" <tr><td><em>" + key + "</em></td><td><em>null</em></td></tr>"); } else if (bean.isSimple()) { out.println(" <tr><td><em>" + key + "</em></td><td><em>" + bean.getClassName() + '(' + bean.getToString() + ')' + "</em></td></tr>"); } else if (bean.isArray()) { out.println(" <tr><td><em>" + key + "</em></td><td><em>" + bean.getClassName() + '[' + bean.getLength() + ']' + "</em></td></tr>"); } else { out.print(" <tr><td><em>" + key + "</em></td><td><em>" + bean.getClassName() + '(' + bean.getToString() + ')'); out.print(": </em>"); Map<String, String> beanAttributes = bean.getAttributes(); if (beanAttributes != null) { for (String attribute : beanAttributes.keySet()) { out.print("<br/>   <em>" + attribute + "</em>="); String value = beanAttributes.get(attribute); if (value == null) { out.print("<em>null</em>"); } else { out.print("<em>"); printEscaped(out, value); out.print("</em>"); } } } out.println("</td></tr>"); } } } private void serveAidMap(Map<String, com.caucho.jsf.dev.JsfDeveloperAid.JsfRequestSnapshot> aidMap, ServletResponse res) throws IOException { res.setContentType("application/x-caucho-jsf-developer-aid"); Hessian2Output out = new Hessian2Output(res.getOutputStream()); out.writeObject(aidMap); out.flush(); } private void printEvaluated(FacesContext context, HttpServletRequest request, PrintWriter out, String expression, String viewId, String phaseId ) throws UnsupportedEncodingException { ELContext elContext = context.getELContext(); ValueExpression valueExpression = context.getApplication() .getExpressionFactory() .createValueExpression(elContext, expression, Object.class); Object obj = valueExpression.getValue(elContext); out.print("<strong>"); out.print(expression); out.print("</strong>="); if (obj == null) { out.println("null"); out.println("<br/>"); } else { if (obj instanceof String || obj instanceof Boolean || obj instanceof Character || obj instanceof Number || obj instanceof Date ) { out.println(obj.toString()); } else { out.print("<strong>"); out.print(obj.getClass().toString()); out.print("[" + obj.toString() + "]"); out.println("</strong>"); Field []fields = obj.getClass().getDeclaredFields(); out.println("<br/>"); for (Field field : fields) { try { field.setAccessible(true); Object value = field.get(obj); out.print("   "); printAttribute(request, out, field.getName(), String.valueOf(value), viewId, phaseId); out.println("<br/>"); } catch (IllegalAccessException e) { } } } } } private void printComponentTree(HttpServletRequest request, PrintWriter out, com.caucho.jsf.dev.JsfDeveloperAid.Component component, String facetName, String viewId, String phaseId, int depth) throws UnsupportedEncodingException { for (int i = 0; i < depth * 3; i++) out.print(" "); out.print("<<strong>" + component.getUiComponentClass() + "</strong>"); printAttribute(request, out, "clientId", component.getClientId(), viewId, phaseId); if (component.isValueHolder()) { printAttribute(request, out, "value", component.getValue(), viewId, phaseId); printAttribute(request, out, "localValue", component.getLocalValue(), viewId, phaseId); } if (component.isEditableValueHolder()) printAttribute(request, out, "submittedValue", component.getSubmittedValue(), viewId, phaseId ); if (facetName != null) printAttribute(request, out, "enclosingFacet", facetName, viewId, phaseId); Map<String, String> attributes = component.getAttributes(); if (attributes != null) for (String attr : attributes.keySet()) { String value = attributes.get(attr); if (value != null) printAttribute(request, out, attr, value, viewId, phaseId); } out.println("><br/>"); List<com.caucho.jsf.dev.JsfDeveloperAid.Component> children = component.getChildren(); Map<String, com.caucho.jsf.dev.JsfDeveloperAid.Component> facets = component .getFacets(); if (children != null) for (com.caucho.jsf.dev.JsfDeveloperAid.Component child : children) printComponentTree(request, out, child, null, viewId, phaseId, depth + 1); if (facets != null) for (String facet : facets.keySet()) printComponentTree(request, out, facets.get(facet), facet, viewId, phaseId, depth + 1); } private void printAttribute(HttpServletRequest request, PrintWriter out, String name, String value, String viewId, String phaseId) throws UnsupportedEncodingException { out.print(' '); out.print("<em>" + name); out.print("</em>=\""); if (value == null) out.print("null"); else if (value.indexOf("#{") > -1 && value.indexOf("}") > -1) { out.print("<a href=\"" + request.getContextPath() + "/caucho.jsf.developer.aid;jsessionid=" + request.getSession().getId() + "?viewId=" + viewId + "&phaseId=" + phaseId + "&valueExpression=" + URLEncoder.encode(value, "UTF-8") + "\">" + value + "</a>"); } else printEscaped(out, value); out.print("\""); } public void printEscaped(PrintWriter out, String value) { char []valueChars = value.toCharArray(); boolean wasSpace = false; for (char valueChar : valueChars) { switch (valueChar) { case ' ': wasSpace = true; break; case '\n': if (wasSpace) out.print(' '); wasSpace = false; out.print('\n'); break; case '\r': if (wasSpace) out.print(' '); wasSpace = false; out.print('\r'); break; case '<': if (wasSpace) out.print(' '); wasSpace = false; out.print("<"); break; default: if (wasSpace) out.print(' '); wasSpace = false; out.write(valueChar); } } } }