Coverage Report - org.apache.myfaces.view.facelets.tag.ui.DebugPhaseListener
 
Classes in this File Line Coverage Branch Coverage Complexity
DebugPhaseListener
0%
0/39
0%
0/22
3
DebugPhaseListener$1
N/A
N/A
3
DebugPhaseListener$DebugVisitCallback
0%
0/35
0%
0/16
3
 
 1  
 /*
 2  
  * Licensed to the Apache Software Foundation (ASF) under one
 3  
  * or more contributor license agreements.  See the NOTICE file
 4  
  * distributed with this work for additional information
 5  
  * regarding copyright ownership.  The ASF licenses this file
 6  
  * to you under the Apache License, Version 2.0 (the
 7  
  * "License"); you may not use this file except in compliance
 8  
  * with the License.  You may obtain a copy of the License at
 9  
  *
 10  
  *   http://www.apache.org/licenses/LICENSE-2.0
 11  
  *
 12  
  * Unless required by applicable law or agreed to in writing,
 13  
  * software distributed under the License is distributed on an
 14  
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 15  
  * KIND, either express or implied.  See the License for the
 16  
  * specific language governing permissions and limitations
 17  
  * under the License.
 18  
  */
 19  
 package org.apache.myfaces.view.facelets.tag.ui;
 20  
 
 21  
 import java.util.ArrayList;
 22  
 import java.util.Arrays;
 23  
 import java.util.EnumSet;
 24  
 import java.util.HashMap;
 25  
 import java.util.List;
 26  
 import java.util.Map;
 27  
 
 28  
 import javax.el.ELContext;
 29  
 import javax.el.ValueExpression;
 30  
 import javax.faces.component.EditableValueHolder;
 31  
 import javax.faces.component.UIComponent;
 32  
 import javax.faces.component.UIViewRoot;
 33  
 import javax.faces.component.visit.VisitCallback;
 34  
 import javax.faces.component.visit.VisitContext;
 35  
 import javax.faces.component.visit.VisitHint;
 36  
 import javax.faces.component.visit.VisitResult;
 37  
 import javax.faces.context.FacesContext;
 38  
 import javax.faces.event.PhaseEvent;
 39  
 import javax.faces.event.PhaseId;
 40  
 import javax.faces.event.PhaseListener;
 41  
 
 42  
 import org.apache.myfaces.renderkit.ErrorPageWriter;
 43  
 
 44  
 /**
 45  
  * PhaseListener to create extended debug information.
 46  
  * Installed in FacesConfigurator.configureLifecycle() if ProjectStage is Development.
 47  
  * 
 48  
  * @author Jakob Korherr (latest modification by $Author$)
 49  
  * @version $Revision$ $Date$
 50  
  */
 51  0
 public class DebugPhaseListener implements PhaseListener
 52  
 {
 53  
     
 54  
     private static final long serialVersionUID = -1517198431551012882L;
 55  
     
 56  
     private static final String SUBMITTED_VALUE_FIELD = "submittedValue";
 57  
     private static final String LOCAL_VALUE_FIELD = "localValue";
 58  
     private static final String VALUE_FIELD = "value";
 59  
     
 60  
     /**
 61  
      * Returns the debug-info Map for the given component.
 62  
      * ATTENTION: this method is duplicate in UIInput.
 63  
      * @param clientId
 64  
      * @return
 65  
      */
 66  
     @SuppressWarnings("unchecked")
 67  
     public static Map<String, List<Object[]>> getDebugInfoMap(String clientId)
 68  
     {
 69  0
         final Map<String, Object> requestMap = FacesContext.getCurrentInstance()
 70  
                 .getExternalContext().getRequestMap();
 71  0
         Map<String, List<Object[]>> debugInfo = (Map<String, List<Object[]>>) 
 72  
                 requestMap.get(ErrorPageWriter.DEBUG_INFO_KEY + clientId);
 73  0
         if (debugInfo == null)
 74  
         {
 75  
             // no debug info available yet, create one and put it on the attributes map
 76  0
             debugInfo = new HashMap<String, List<Object[]>>();
 77  0
             requestMap.put(ErrorPageWriter.DEBUG_INFO_KEY + clientId, debugInfo);
 78  
         }
 79  0
         return debugInfo;
 80  
     }
 81  
     
 82  
     /**
 83  
      * Returns the field's debug-infos from the component's debug-info Map.
 84  
      * ATTENTION: this method is duplicate in UIInput.
 85  
      * @param field
 86  
      * @param clientId
 87  
      * @return
 88  
      */
 89  
     public static List<Object[]> getFieldDebugInfos(final String field, String clientId)
 90  
     {
 91  0
         Map<String, List<Object[]>> debugInfo = getDebugInfoMap(clientId);
 92  0
         List<Object[]> fieldDebugInfo = debugInfo.get(field);
 93  0
         if (fieldDebugInfo == null)
 94  
         {
 95  
             // no field debug-infos yet, create them and store it in the Map
 96  0
             fieldDebugInfo = new ArrayList<Object[]>();
 97  0
             debugInfo.put(field, fieldDebugInfo);
 98  
         }
 99  0
         return fieldDebugInfo;
 100  
     }
 101  
     
 102  
     /**
 103  
      * Creates the field debug-info for the given field, which changed
 104  
      * from oldValue to newValue in the given component.
 105  
      * ATTENTION: this method is duplicate in UIInput.
 106  
      * @param facesContext
 107  
      * @param field
 108  
      * @param oldValue
 109  
      * @param newValue
 110  
      * @param clientId
 111  
      */
 112  
     public static void createFieldDebugInfo(FacesContext facesContext,
 113  
             final String field, Object oldValue, 
 114  
             Object newValue, String clientId)
 115  
     {
 116  0
         if ((oldValue == null && newValue == null)
 117  
                 || (oldValue != null && oldValue.equals(newValue)))
 118  
         {
 119  
             // nothing changed - NOTE that this is a difference to the method in 
 120  
             // UIInput, because in UIInput every call to this method comes from
 121  
             // setSubmittedValue or setLocalValue and here every call comes
 122  
             // from the VisitCallback of the PhaseListener.
 123  0
             return;
 124  
         }
 125  
         
 126  
         // convert Array values into a more readable format
 127  0
         if (oldValue != null && oldValue.getClass().isArray())
 128  
         {
 129  0
             oldValue = Arrays.deepToString((Object[]) oldValue);
 130  
         }
 131  0
         if (newValue != null && newValue.getClass().isArray())
 132  
         {
 133  0
             newValue = Arrays.deepToString((Object[]) newValue);
 134  
         }
 135  
         
 136  
         // NOTE that the call stack does not make much sence here
 137  
         
 138  
         // create the debug-info array
 139  
         // structure:
 140  
         //     - 0: phase
 141  
         //     - 1: old value
 142  
         //     - 2: new value
 143  
         //     - 3: StackTraceElement List
 144  
         // NOTE that we cannot create a class here to encapsulate this data,
 145  
         // because this is not on the spec and the class would not be available in impl.
 146  0
         Object[] debugInfo = new Object[4];
 147  0
         debugInfo[0] = facesContext.getCurrentPhaseId();
 148  0
         debugInfo[1] = oldValue;
 149  0
         debugInfo[2] = newValue;
 150  0
         debugInfo[3] = null; // here we have no call stack (only in UIInput)
 151  
         
 152  
         // add the debug info
 153  0
         getFieldDebugInfos(field, clientId).add(debugInfo);
 154  0
     }
 155  
 
 156  
     /**
 157  
      * VisitCallback used for visitTree()  
 158  
      *  
 159  
      * @author Jakob Korherr
 160  
      */
 161  0
     private class DebugVisitCallback implements VisitCallback
 162  
     {
 163  
 
 164  
         public VisitResult visit(VisitContext context, UIComponent target)
 165  
         {
 166  0
             if (target instanceof EditableValueHolder)
 167  
             {
 168  0
                 EditableValueHolder evh = (EditableValueHolder) target;
 169  0
                 final String clientId = target.getClientId(context.getFacesContext());
 170  0
                 Map<String, Object> requestMap = context.getFacesContext()
 171  
                         .getExternalContext().getRequestMap();
 172  
                 
 173  0
                 if (_afterPhase)
 174  
                 {
 175  
                     // afterPhase - check for value changes
 176  
                     
 177  
                     // submittedValue
 178  0
                     _createFieldDebugInfosIfNecessary(SUBMITTED_VALUE_FIELD, clientId,
 179  
                             evh.getSubmittedValue(), requestMap, context.getFacesContext());
 180  
                     
 181  
                     // localValue
 182  0
                     final Object localValue = evh.getLocalValue();
 183  0
                     _createFieldDebugInfosIfNecessary(LOCAL_VALUE_FIELD, clientId,
 184  
                             localValue, requestMap, context.getFacesContext());
 185  
                     
 186  
                     // value
 187  0
                     final Object value = _getRealValue(evh, target, localValue,
 188  
                             context.getFacesContext().getELContext());
 189  0
                     _createFieldDebugInfosIfNecessary(VALUE_FIELD, clientId,
 190  
                             value, requestMap, context.getFacesContext());
 191  0
                 }
 192  
                 else
 193  
                 {
 194  
                     // beforePhase - save the current value state
 195  
                     
 196  
                     // submittedValue
 197  0
                     requestMap.put(ErrorPageWriter.DEBUG_INFO_KEY + clientId 
 198  
                             + SUBMITTED_VALUE_FIELD, evh.getSubmittedValue());
 199  
                     
 200  
                     // localValue
 201  0
                     final Object localValue = evh.getLocalValue();
 202  0
                     requestMap.put(ErrorPageWriter.DEBUG_INFO_KEY + clientId 
 203  
                             + LOCAL_VALUE_FIELD, localValue);
 204  
                     
 205  
                     // value
 206  0
                     final Object value = _getRealValue(evh, target, localValue,
 207  
                             context.getFacesContext().getELContext());
 208  0
                     requestMap.put(ErrorPageWriter.DEBUG_INFO_KEY + clientId 
 209  
                             + VALUE_FIELD, value);
 210  
                 }
 211  
             }
 212  
             
 213  0
             return VisitResult.ACCEPT;
 214  
         }
 215  
         
 216  
         /**
 217  
          * Checks if there are debug infos available for the given field and the
 218  
          * current Phase and if NOT, it creates and adds them to the request Map.
 219  
          * @param field
 220  
          * @param clientId
 221  
          * @param newValue
 222  
          * @param requestMap
 223  
          * @param facesContext
 224  
          */
 225  
         private void _createFieldDebugInfosIfNecessary(final String field, 
 226  
                 final String clientId, Object newValue,
 227  
                 Map<String, Object> requestMap, FacesContext facesContext)
 228  
         {
 229  
             // check if there are already debugInfos from UIInput
 230  0
             List<Object[]> fieldDebugInfos = getFieldDebugInfos(field, clientId);
 231  0
             boolean found = false;
 232  0
             for (int i = 0, size = fieldDebugInfos.size(); i < size; i++)
 233  
             {
 234  0
                 Object[] debugInfo = fieldDebugInfos.get(i);
 235  0
                 if (debugInfo[0].equals(_currentPhase))
 236  
                 {
 237  0
                     found = true;
 238  0
                     break;
 239  
                 }
 240  
             }
 241  0
             if (!found)
 242  
             {
 243  
                 // there are no debug infos for this field in this lifecycle phase yet
 244  
                 // --> create them
 245  0
                 Object oldValue = requestMap.remove(ErrorPageWriter.DEBUG_INFO_KEY 
 246  
                         + clientId + field);
 247  0
                 createFieldDebugInfo(facesContext, field,
 248  
                         oldValue, newValue, clientId);
 249  
             }
 250  0
         }
 251  
         
 252  
         /**
 253  
          * Gets the real value of the EditableValueHolder component.
 254  
          * This is necessary, because if the localValue is set, getValue()
 255  
          * normally returns the localValue and not the real value.
 256  
          * @param evh
 257  
          * @param target
 258  
          * @param localValue
 259  
          * @param elCtx
 260  
          * @return
 261  
          */
 262  
         private Object _getRealValue(EditableValueHolder evh, UIComponent target,
 263  
                 final Object localValue, ELContext elCtx)
 264  
         {
 265  0
             Object value = evh.getValue();
 266  0
             if (localValue != null && localValue.equals(value))
 267  
             {
 268  
                 // getValue() normally returns the localValue, if it is set
 269  
                 // --> try to get the real value from the ValueExpression
 270  0
                 ValueExpression valueExpression = target.getValueExpression("value");
 271  0
                 if (valueExpression != null)
 272  
                 {
 273  0
                     value = valueExpression.getValue(elCtx);
 274  
                 }
 275  
             }
 276  0
             return value;
 277  
         }
 278  
         
 279  
     }
 280  
     
 281  0
     private boolean _afterPhase = false;
 282  
     private PhaseId _currentPhase;
 283  0
     private DebugVisitCallback _visitCallback = new DebugVisitCallback();
 284  
 
 285  
     public void afterPhase(PhaseEvent event)
 286  
     {
 287  0
         _doTreeVisit(event, true);
 288  0
     }
 289  
 
 290  
     public void beforePhase(PhaseEvent event)
 291  
     {
 292  0
         _doTreeVisit(event, false);
 293  0
     }
 294  
 
 295  
     public PhaseId getPhaseId()
 296  
     {
 297  0
         return PhaseId.ANY_PHASE;
 298  
     }
 299  
     
 300  
     private void _doTreeVisit(PhaseEvent event, boolean afterPhase)
 301  
     {
 302  0
         _afterPhase = afterPhase;
 303  0
         _currentPhase = event.getPhaseId();
 304  
         
 305  
         // visitTree() on the UIViewRoot
 306  0
         UIViewRoot viewroot = event.getFacesContext().getViewRoot();
 307  0
         if (viewroot != null)
 308  
         {
 309  
             // skip all unrendered components to really only show
 310  
             // the rendered components and to circumvent data access problems
 311  0
             viewroot.visitTree(VisitContext.createVisitContext(
 312  
                     event.getFacesContext(), null, 
 313  
                     EnumSet.of(VisitHint.SKIP_UNRENDERED)),
 314  
                     _visitCallback);
 315  
         }
 316  0
     }
 317  
 
 318  
 }