You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by ja...@apache.org on 2010/05/12 16:55:39 UTC

svn commit: r943533 - in /myfaces/core/trunk: api/src/main/java/javax/faces/component/ impl/src/main/java/org/apache/myfaces/renderkit/ impl/src/main/java/org/apache/myfaces/view/facelets/tag/ui/ impl/src/main/resources/META-INF/rsc/

Author: jakobk
Date: Wed May 12 14:55:38 2010
New Revision: 943533

URL: http://svn.apache.org/viewvc?rev=943533&view=rev
Log:
MYFACES-2676 Include the whole component tree with all real values in the debug page (added support for components that do not extend UIInput via the DebugPhaseListener and made the call stack togglable)

Modified:
    myfaces/core/trunk/api/src/main/java/javax/faces/component/UIInput.java
    myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/renderkit/ErrorPageWriter.java
    myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/view/facelets/tag/ui/DebugPhaseListener.java
    myfaces/core/trunk/impl/src/main/resources/META-INF/rsc/myfaces-dev-debug.xml

Modified: myfaces/core/trunk/api/src/main/java/javax/faces/component/UIInput.java
URL: http://svn.apache.org/viewvc/myfaces/core/trunk/api/src/main/java/javax/faces/component/UIInput.java?rev=943533&r1=943532&r2=943533&view=diff
==============================================================================
--- myfaces/core/trunk/api/src/main/java/javax/faces/component/UIInput.java (original)
+++ myfaces/core/trunk/api/src/main/java/javax/faces/component/UIInput.java Wed May 12 14:55:38 2010
@@ -22,6 +22,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 
@@ -933,16 +934,16 @@ public class UIInput extends UIOutput im
      * @return
      */
     @SuppressWarnings("unchecked")
-    private Map<String, List<String>> _getDebugInfoMap()
+    private Map<String, List<Object[]>> _getDebugInfoMap()
     {
         final Map<String, Object> requestMap = getFacesContext()
                 .getExternalContext().getRequestMap();
-        Map<String, List<String>> debugInfo = (Map<String, List<String>>) 
+        Map<String, List<Object[]>> debugInfo = (Map<String, List<Object[]>>) 
                 requestMap.get(DEBUG_INFO_KEY + getClientId());
         if (debugInfo == null)
         {
             // no debug info available yet, create one and put it on the attributes map
-            debugInfo = new HashMap<String, List<String>>();
+            debugInfo = new HashMap<String, List<Object[]>>();
             requestMap.put(DEBUG_INFO_KEY + getClientId(), debugInfo);
         }
         return debugInfo;
@@ -953,14 +954,14 @@ public class UIInput extends UIOutput im
      * @param field
      * @return
      */
-    private List<String> _getFieldDebugInfos(final String field)
+    private List<Object[]> _getFieldDebugInfos(final String field)
     {
-        Map<String, List<String>> debugInfo = _getDebugInfoMap();
-        List<String> fieldDebugInfo = debugInfo.get(field);
+        Map<String, List<Object[]>> debugInfo = _getDebugInfoMap();
+        List<Object[]> fieldDebugInfo = debugInfo.get(field);
         if (fieldDebugInfo == null)
         {
             // no field debug-infos yet, create them and store it in the Map
-            fieldDebugInfo = new ArrayList<String>();
+            fieldDebugInfo = new ArrayList<Object[]>();
             debugInfo.put(field, fieldDebugInfo);
         }
         return fieldDebugInfo;
@@ -998,26 +999,15 @@ public class UIInput extends UIOutput im
             newValue = Arrays.deepToString((Object[]) newValue);
         }
         
-        StringBuilder sb = new StringBuilder();
-        sb.append("<b>");
-        sb.append(field);
-        sb.append("</b> set from <b>");
-        sb.append(oldValue);
-        sb.append("</b> to <b>");
-        sb.append(newValue);
-        sb.append("</b> in Phase ");
-        sb.append(facesContext.getCurrentPhaseId());
-        sb.append(" with call from:<ul>");
-        
         // use Throwable to get the current call stack
         Throwable throwableHelper = new Throwable();
         StackTraceElement[] stackTraceElements = throwableHelper.getStackTrace();
+        List<StackTraceElement> debugStackTraceElements = new LinkedList<StackTraceElement>();
+        
         // + 1 because this method should also be skipped
         for (int i = skipStackTaceElements + 1; i < stackTraceElements.length; i++)
         {
-            sb.append("<li>");
-            sb.append(stackTraceElements[i].toString());
-            sb.append("</li>");
+            debugStackTraceElements.add(stackTraceElements[i]);
             
             if (FacesServlet.class.getCanonicalName()
                     .equals(stackTraceElements[i].getClassName()))
@@ -1026,12 +1016,25 @@ public class UIInput extends UIOutput im
                 break;
             }
         }
-        sb.append("</ul>");
+        
+        // create the debug-info array
+        // structure:
+        //     - 0: phase
+        //     - 1: old value
+        //     - 2: new value
+        //     - 3: StackTraceElement List
+        // NOTE that we cannot create a class here to encapsulate this data,
+        // because this is not on the spec and the class would not be available in impl.
+        Object[] debugInfo = new Object[4];
+        debugInfo[0] = facesContext.getCurrentPhaseId();
+        debugInfo[1] = oldValue;
+        debugInfo[2] = newValue;
+        debugInfo[3] = debugStackTraceElements;
         
         // add the debug info
-        _getFieldDebugInfos(field).add(sb.toString());
+        _getFieldDebugInfos(field).add(debugInfo);
     }
-
+    
     /**
      * Check if a value is empty or not. Since we don't know the class of
      * value we have to check and deal with it properly.

Modified: myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/renderkit/ErrorPageWriter.java
URL: http://svn.apache.org/viewvc/myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/renderkit/ErrorPageWriter.java?rev=943533&r1=943532&r2=943533&view=diff
==============================================================================
--- myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/renderkit/ErrorPageWriter.java (original)
+++ myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/renderkit/ErrorPageWriter.java Wed May 12 14:55:38 2010
@@ -18,16 +18,41 @@
  */
 package org.apache.myfaces.renderkit;
 
-import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFWebConfigParam;
-import org.apache.myfaces.shared_impl.renderkit.html.HtmlResponseWriterImpl;
-import org.apache.myfaces.shared_impl.util.ClassUtils;
-import org.apache.myfaces.shared_impl.webapp.webxml.WebXml;
-import org.apache.myfaces.view.facelets.component.UIRepeat;
+import java.beans.BeanInfo;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.io.Serializable;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.lang.reflect.Method;
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.EnumSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import javax.el.Expression;
 import javax.el.ValueExpression;
 import javax.faces.FacesException;
-import javax.faces.component.*;
+import javax.faces.component.EditableValueHolder;
+import javax.faces.component.UIColumn;
+import javax.faces.component.UIComponent;
+import javax.faces.component.UIData;
+import javax.faces.component.UIViewRoot;
 import javax.faces.component.visit.VisitCallback;
 import javax.faces.component.visit.VisitContext;
 import javax.faces.component.visit.VisitHint;
@@ -41,17 +66,12 @@ import javax.faces.el.ValueBinding;
 import javax.faces.render.Renderer;
 import javax.faces.view.Location;
 import javax.servlet.http.HttpServletResponse;
-import java.beans.BeanInfo;
-import java.beans.Introspector;
-import java.beans.PropertyDescriptor;
-import java.io.*;
-import java.lang.reflect.Method;
-import java.text.DateFormat;
-import java.util.*;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
+
+import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFWebConfigParam;
+import org.apache.myfaces.shared_impl.renderkit.html.HtmlResponseWriterImpl;
+import org.apache.myfaces.shared_impl.util.ClassUtils;
+import org.apache.myfaces.shared_impl.webapp.webxml.WebXml;
+import org.apache.myfaces.view.facelets.component.UIRepeat;
 
 /**
  * This class provides utility methods to generate the
@@ -726,12 +746,13 @@ public final class ErrorPageWriter
                     }
                     _writer.write(">");
                     
-                    Map<String, List<String>> debugInfo = null;
-                    // is the target a UIInput component?
-                    if (target instanceof UIInput)
+                    Map<String, List<Object[]>> debugInfos = null;
+                    // is the target a EditableValueHolder component?
+                    // If so, debug infos from DebugPhaseListener should be available
+                    if (target instanceof EditableValueHolder)
                     {
                         // get the debug info
-                        debugInfo = (Map<String, List<String>>) requestMap
+                        debugInfos = (Map<String, List<Object[]>>) requestMap
                                 .get(DEBUG_INFO_KEY + target.getClientId());
                     }
                     
@@ -753,7 +774,7 @@ public final class ErrorPageWriter
                     }
                     
                     // write the component start
-                    _writeStart(_writer, target, (hasChildren || debugInfo != null || renderer != null), false);
+                    _writeStart(_writer, target, (hasChildren || debugInfos != null || renderer != null), false);
                     _writer.write("</dt>");
                     
                     if (renderer != null)
@@ -763,7 +784,7 @@ public final class ErrorPageWriter
                         _writer.write(renderer.getClass().getCanonicalName());
                         _writer.write("</div>");
                         
-                        if (!hasChildren && debugInfo == null)
+                        if (!hasChildren && debugInfos == null)
                         {
                             // close the component
                             _writer.write("<dt>");
@@ -772,7 +793,7 @@ public final class ErrorPageWriter
                         }
                     }
                         
-                    if (debugInfo != null)
+                    if (debugInfos != null)
                     {
                         final String fieldid = target.getClientId() + "_lifecycle";
                         _writer.write("<div class=\"lifecycle_values_wrapper\">");
@@ -788,16 +809,61 @@ public final class ErrorPageWriter
                         _writer.write("\" class=\"lifecycle_values\">");
                         
                         // process any available debug info
-                        for (Map.Entry<String, List<String>> entry : debugInfo.entrySet())
+                        for (Map.Entry<String, List<Object[]>> entry : debugInfos.entrySet())
                         {
                             _writer.write("<span>");
                             _writer.write(entry.getKey());
                             _writer.write("</span><ol>");
-                            for (String change : entry.getValue())
+                            int i = 0;
+                            for (Object[] debugInfo : entry.getValue())
                             {
-                                _writer.write("<li>");
-                                _writer.write(change);
+                                // structure of the debug-info array:
+                                //     - 0: phase
+                                //     - 1: old value
+                                //     - 2: new value
+                                //     - 3: StackTraceElement List
+                                
+                                // oldValue and newValue could be null
+                                String oldValue = debugInfo[1] == null ? "null" : debugInfo[1].toString();
+                                String newValue = debugInfo[2] == null ? "null" : debugInfo[2].toString();
+                                _writer.write("<li><b>");
+                                _writer.write(entry.getKey());
+                                _writer.write("</b> set from <b>");
+                                _writer.write(oldValue);
+                                _writer.write("</b> to <b>");
+                                _writer.write(newValue);
+                                _writer.write("</b> in Phase ");
+                                _writer.write(debugInfo[0].toString());
+                                
+                                // check if a call stack is available
+                                if (debugInfo[3] != null)
+                                {
+                                    final String stackTraceId = fieldid + "_" + entry.getKey() + "_" + i;
+                                    _writer.write("<div class=\"stacktrace_wrapper\">");
+                                    _writer.write("<a href=\"#\" onclick=\"toggle('");
+                                    _writer.write(stackTraceId);
+                                    _writer.write("'); return false;\"><span id=\"");
+                                    _writer.write(stackTraceId);
+                                    _writer.write("Off\">+</span><span id=\"");
+                                    _writer.write(stackTraceId);
+                                    _writer.write("On\" style=\"display: none;\">-</span> Call Stack</a>");
+                                    _writer.write("<div id=\"");
+                                    _writer.write(stackTraceId);
+                                    _writer.write("\" class=\"stacktrace_values\">");
+                                    _writer.write("<ul>");
+                                    for (StackTraceElement stackTraceElement 
+                                            : (List<StackTraceElement>) debugInfo[3])
+                                    {
+                                        _writer.write("<li>");
+                                        _writer.write(stackTraceElement.toString());
+                                        _writer.write("</li>");
+                                    }
+                                    _writer.write("</ul></div></div>");
+                                }
+                                
                                 _writer.write("</li>");
+                                
+                                i++;
                             }
                             _writer.write("</ol>");
                         }

Modified: myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/view/facelets/tag/ui/DebugPhaseListener.java
URL: http://svn.apache.org/viewvc/myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/view/facelets/tag/ui/DebugPhaseListener.java?rev=943533&r1=943532&r2=943533&view=diff
==============================================================================
--- myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/view/facelets/tag/ui/DebugPhaseListener.java (original)
+++ myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/view/facelets/tag/ui/DebugPhaseListener.java Wed May 12 14:55:38 2010
@@ -18,15 +18,27 @@
  */
 package org.apache.myfaces.view.facelets.tag.ui;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.el.ELContext;
+import javax.el.ValueExpression;
+import javax.faces.component.EditableValueHolder;
 import javax.faces.component.UIComponent;
 import javax.faces.component.UIViewRoot;
 import javax.faces.component.visit.VisitCallback;
 import javax.faces.component.visit.VisitContext;
 import javax.faces.component.visit.VisitResult;
+import javax.faces.context.FacesContext;
 import javax.faces.event.PhaseEvent;
 import javax.faces.event.PhaseId;
 import javax.faces.event.PhaseListener;
 
+import org.apache.myfaces.renderkit.ErrorPageWriter;
+
 /**
  * PhaseListener to create extended debug information.
  * Installed in FacesConfigurator.configureLifecycle() if ProjectStage is Development.
@@ -38,6 +50,106 @@ public class DebugPhaseListener implemen
 {
     
     private static final long serialVersionUID = -1517198431551012882L;
+    
+    private static final String SUBMITTED_VALUE_FIELD = "submittedValue";
+    private static final String LOCAL_VALUE_FIELD = "localValue";
+    private static final String VALUE_FIELD = "value";
+    
+    /**
+     * Returns the debug-info Map for the given component.
+     * ATTENTION: this method is duplicate in UIInput.
+     * @param clientId
+     * @return
+     */
+    @SuppressWarnings("unchecked")
+    public static Map<String, List<Object[]>> getDebugInfoMap(String clientId)
+    {
+        final Map<String, Object> requestMap = FacesContext.getCurrentInstance()
+                .getExternalContext().getRequestMap();
+        Map<String, List<Object[]>> debugInfo = (Map<String, List<Object[]>>) 
+                requestMap.get(ErrorPageWriter.DEBUG_INFO_KEY + clientId);
+        if (debugInfo == null)
+        {
+            // no debug info available yet, create one and put it on the attributes map
+            debugInfo = new HashMap<String, List<Object[]>>();
+            requestMap.put(ErrorPageWriter.DEBUG_INFO_KEY + clientId, debugInfo);
+        }
+        return debugInfo;
+    }
+    
+    /**
+     * Returns the field's debug-infos from the component's debug-info Map.
+     * ATTENTION: this method is duplicate in UIInput.
+     * @param field
+     * @param clientId
+     * @return
+     */
+    public static List<Object[]> getFieldDebugInfos(final String field, String clientId)
+    {
+        Map<String, List<Object[]>> debugInfo = getDebugInfoMap(clientId);
+        List<Object[]> fieldDebugInfo = debugInfo.get(field);
+        if (fieldDebugInfo == null)
+        {
+            // no field debug-infos yet, create them and store it in the Map
+            fieldDebugInfo = new ArrayList<Object[]>();
+            debugInfo.put(field, fieldDebugInfo);
+        }
+        return fieldDebugInfo;
+    }
+    
+    /**
+     * Creates the field debug-info for the given field, which changed
+     * from oldValue to newValue in the given component.
+     * ATTENTION: this method is duplicate in UIInput.
+     * @param facesContext
+     * @param field
+     * @param oldValue
+     * @param newValue
+     * @param clientId
+     */
+    public static void createFieldDebugInfo(FacesContext facesContext,
+            final String field, Object oldValue, 
+            Object newValue, String clientId)
+    {
+        if ((oldValue == null && newValue == null)
+                || (oldValue != null && oldValue.equals(newValue)))
+        {
+            // nothing changed - NOTE that this is a difference to the method in 
+            // UIInput, because in UIInput every call to this method comes from
+            // setSubmittedValue or setLocalValue and here every call comes
+            // from the VisitCallback of the PhaseListener.
+            return;
+        }
+        
+        // convert Array values into a more readable format
+        if (oldValue != null && oldValue.getClass().isArray())
+        {
+            oldValue = Arrays.deepToString((Object[]) oldValue);
+        }
+        if (newValue != null && newValue.getClass().isArray())
+        {
+            newValue = Arrays.deepToString((Object[]) newValue);
+        }
+        
+        // NOTE that the call stack does not make much sence here
+        
+        // create the debug-info array
+        // structure:
+        //     - 0: phase
+        //     - 1: old value
+        //     - 2: new value
+        //     - 3: StackTraceElement List
+        // NOTE that we cannot create a class here to encapsulate this data,
+        // because this is not on the spec and the class would not be available in impl.
+        Object[] debugInfo = new Object[4];
+        debugInfo[0] = facesContext.getCurrentPhaseId();
+        debugInfo[1] = oldValue;
+        debugInfo[2] = newValue;
+        debugInfo[3] = null; // here we have no call stack (only in UIInput)
+        
+        // add the debug info
+        getFieldDebugInfos(field, clientId).add(debugInfo);
+    }
 
     /**
      * VisitCallback used for visitTree()  
@@ -49,11 +161,118 @@ public class DebugPhaseListener implemen
 
         public VisitResult visit(VisitContext context, UIComponent target)
         {
-            // TODO implement for components that do not extend UIInput
+            if (target instanceof EditableValueHolder)
+            {
+                EditableValueHolder evh = (EditableValueHolder) target;
+                final String clientId = target.getClientId(context.getFacesContext());
+                Map<String, Object> requestMap = context.getFacesContext()
+                        .getExternalContext().getRequestMap();
+                
+                if (_afterPhase)
+                {
+                    // afterPhase - check for value changes
+                    
+                    // submittedValue
+                    _createFieldDebugInfosIfNecessary(SUBMITTED_VALUE_FIELD, clientId,
+                            evh.getSubmittedValue(), requestMap, context.getFacesContext());
+                    
+                    // localValue
+                    final Object localValue = evh.getLocalValue();
+                    _createFieldDebugInfosIfNecessary(LOCAL_VALUE_FIELD, clientId,
+                            localValue, requestMap, context.getFacesContext());
+                    
+                    // value
+                    final Object value = _getRealValue(evh, target, localValue,
+                            context.getFacesContext().getELContext());
+                    _createFieldDebugInfosIfNecessary(VALUE_FIELD, clientId,
+                            value, requestMap, context.getFacesContext());
+                }
+                else
+                {
+                    // beforePhase - save the current value state
+                    
+                    // submittedValue
+                    requestMap.put(ErrorPageWriter.DEBUG_INFO_KEY + clientId 
+                            + SUBMITTED_VALUE_FIELD, evh.getSubmittedValue());
+                    
+                    // localValue
+                    final Object localValue = evh.getLocalValue();
+                    requestMap.put(ErrorPageWriter.DEBUG_INFO_KEY + clientId 
+                            + LOCAL_VALUE_FIELD, localValue);
+                    
+                    // value
+                    final Object value = _getRealValue(evh, target, localValue,
+                            context.getFacesContext().getELContext());
+                    requestMap.put(ErrorPageWriter.DEBUG_INFO_KEY + clientId 
+                            + VALUE_FIELD, value);
+                }
+            }
             
             return VisitResult.ACCEPT;
         }
         
+        /**
+         * Checks if there are debug infos available for the given field and the
+         * current Phase and if NOT, it creates and adds them to the request Map.
+         * @param field
+         * @param clientId
+         * @param newValue
+         * @param requestMap
+         * @param facesContext
+         */
+        private void _createFieldDebugInfosIfNecessary(final String field, 
+                final String clientId, Object newValue,
+                Map<String, Object> requestMap, FacesContext facesContext)
+        {
+            // check if there are already debugInfos from UIInput
+            List<Object[]> fieldDebugInfos = getFieldDebugInfos(field, clientId);
+            boolean found = false;
+            for (Object[] debugInfo : fieldDebugInfos)
+            {
+                if (debugInfo[0].equals(_currentPhase))
+                {
+                    found = true;
+                    break;
+                }
+            }
+            if (!found)
+            {
+                // there are no debug infos for this field in this lifecycle phase yet
+                // --> create them
+                Object oldValue = requestMap.remove(ErrorPageWriter.DEBUG_INFO_KEY 
+                        + clientId + field);
+                createFieldDebugInfo(facesContext, field,
+                        oldValue, newValue, clientId);
+            }
+        }
+        
+        /**
+         * Gets the real value of the EditableValueHolder component.
+         * This is necessary, because if the localValue is set, getValue()
+         * normally returns the localValue and not the real value.
+         * @param evh
+         * @param target
+         * @param localValue
+         * @param elCtx
+         * @return
+         */
+        private Object _getRealValue(EditableValueHolder evh, UIComponent target,
+                final Object localValue, ELContext elCtx)
+        {
+            Object value = evh.getValue();
+            if (localValue != null && localValue.equals(value))
+            {
+                // getValue() normally returns the localValue, if it is set
+                // --> try to get the real value from the ValueExpression
+                ValueExpression valueExpression = target.getValueExpression("value");
+                if (valueExpression != null)
+                {
+                    value = valueExpression.getValue(elCtx);
+                }
+            }
+            return value;
+        }
+        
     }
     
     private boolean _afterPhase = false;

Modified: myfaces/core/trunk/impl/src/main/resources/META-INF/rsc/myfaces-dev-debug.xml
URL: http://svn.apache.org/viewvc/myfaces/core/trunk/impl/src/main/resources/META-INF/rsc/myfaces-dev-debug.xml?rev=943533&r1=943532&r2=943533&view=diff
==============================================================================
--- myfaces/core/trunk/impl/src/main/resources/META-INF/rsc/myfaces-dev-debug.xml (original)
+++ myfaces/core/trunk/impl/src/main/resources/META-INF/rsc/myfaces-dev-debug.xml Wed May 12 14:55:38 2010
@@ -24,6 +24,7 @@ tbody tr td { padding: 10px 6px; }
 table caption { text-align: left; padding: 10px 0; font-size: large; }
 .lifecycle_values { display:none; margin: 5px 5px 5px 20px; }
 .lifecycle_values_wrapper, .renderer { margin: 5px 0px 5px 40px; }
+.stacktrace_values { display:none; }
 </style>
 <style type="text/css" media="print">
 #trace, #tree, #vars { display: block; }