View Javadoc

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: jakobk $)
49   * @version $Revision: 955754 $ $Date: 2010-06-17 16:37:31 -0500 (Thu, 17 Jun 2010) $
50   */
51  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          final Map<String, Object> requestMap = FacesContext.getCurrentInstance()
70                  .getExternalContext().getRequestMap();
71          Map<String, List<Object[]>> debugInfo = (Map<String, List<Object[]>>) 
72                  requestMap.get(ErrorPageWriter.DEBUG_INFO_KEY + clientId);
73          if (debugInfo == null)
74          {
75              // no debug info available yet, create one and put it on the attributes map
76              debugInfo = new HashMap<String, List<Object[]>>();
77              requestMap.put(ErrorPageWriter.DEBUG_INFO_KEY + clientId, debugInfo);
78          }
79          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          Map<String, List<Object[]>> debugInfo = getDebugInfoMap(clientId);
92          List<Object[]> fieldDebugInfo = debugInfo.get(field);
93          if (fieldDebugInfo == null)
94          {
95              // no field debug-infos yet, create them and store it in the Map
96              fieldDebugInfo = new ArrayList<Object[]>();
97              debugInfo.put(field, fieldDebugInfo);
98          }
99          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         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             return;
124         }
125         
126         // convert Array values into a more readable format
127         if (oldValue != null && oldValue.getClass().isArray())
128         {
129             oldValue = Arrays.deepToString((Object[]) oldValue);
130         }
131         if (newValue != null && newValue.getClass().isArray())
132         {
133             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         Object[] debugInfo = new Object[4];
147         debugInfo[0] = facesContext.getCurrentPhaseId();
148         debugInfo[1] = oldValue;
149         debugInfo[2] = newValue;
150         debugInfo[3] = null; // here we have no call stack (only in UIInput)
151         
152         // add the debug info
153         getFieldDebugInfos(field, clientId).add(debugInfo);
154     }
155 
156     /**
157      * VisitCallback used for visitTree()  
158      *  
159      * @author Jakob Korherr
160      */
161     private class DebugVisitCallback implements VisitCallback
162     {
163 
164         public VisitResult visit(VisitContext context, UIComponent target)
165         {
166             if (target instanceof EditableValueHolder)
167             {
168                 EditableValueHolder evh = (EditableValueHolder) target;
169                 final String clientId = target.getClientId(context.getFacesContext());
170                 Map<String, Object> requestMap = context.getFacesContext()
171                         .getExternalContext().getRequestMap();
172                 
173                 if (_afterPhase)
174                 {
175                     // afterPhase - check for value changes
176                     
177                     // submittedValue
178                     _createFieldDebugInfosIfNecessary(SUBMITTED_VALUE_FIELD, clientId,
179                             evh.getSubmittedValue(), requestMap, context.getFacesContext());
180                     
181                     // localValue
182                     final Object localValue = evh.getLocalValue();
183                     _createFieldDebugInfosIfNecessary(LOCAL_VALUE_FIELD, clientId,
184                             localValue, requestMap, context.getFacesContext());
185                     
186                     // value
187                     final Object value = _getRealValue(evh, target, localValue,
188                             context.getFacesContext().getELContext());
189                     _createFieldDebugInfosIfNecessary(VALUE_FIELD, clientId,
190                             value, requestMap, context.getFacesContext());
191                 }
192                 else
193                 {
194                     // beforePhase - save the current value state
195                     
196                     // submittedValue
197                     requestMap.put(ErrorPageWriter.DEBUG_INFO_KEY + clientId 
198                             + SUBMITTED_VALUE_FIELD, evh.getSubmittedValue());
199                     
200                     // localValue
201                     final Object localValue = evh.getLocalValue();
202                     requestMap.put(ErrorPageWriter.DEBUG_INFO_KEY + clientId 
203                             + LOCAL_VALUE_FIELD, localValue);
204                     
205                     // value
206                     final Object value = _getRealValue(evh, target, localValue,
207                             context.getFacesContext().getELContext());
208                     requestMap.put(ErrorPageWriter.DEBUG_INFO_KEY + clientId 
209                             + VALUE_FIELD, value);
210                 }
211             }
212             
213             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             List<Object[]> fieldDebugInfos = getFieldDebugInfos(field, clientId);
231             boolean found = false;
232             for (Object[] debugInfo : fieldDebugInfos)
233             {
234                 if (debugInfo[0].equals(_currentPhase))
235                 {
236                     found = true;
237                     break;
238                 }
239             }
240             if (!found)
241             {
242                 // there are no debug infos for this field in this lifecycle phase yet
243                 // --> create them
244                 Object oldValue = requestMap.remove(ErrorPageWriter.DEBUG_INFO_KEY 
245                         + clientId + field);
246                 createFieldDebugInfo(facesContext, field,
247                         oldValue, newValue, clientId);
248             }
249         }
250         
251         /**
252          * Gets the real value of the EditableValueHolder component.
253          * This is necessary, because if the localValue is set, getValue()
254          * normally returns the localValue and not the real value.
255          * @param evh
256          * @param target
257          * @param localValue
258          * @param elCtx
259          * @return
260          */
261         private Object _getRealValue(EditableValueHolder evh, UIComponent target,
262                 final Object localValue, ELContext elCtx)
263         {
264             Object value = evh.getValue();
265             if (localValue != null && localValue.equals(value))
266             {
267                 // getValue() normally returns the localValue, if it is set
268                 // --> try to get the real value from the ValueExpression
269                 ValueExpression valueExpression = target.getValueExpression("value");
270                 if (valueExpression != null)
271                 {
272                     value = valueExpression.getValue(elCtx);
273                 }
274             }
275             return value;
276         }
277         
278     }
279     
280     private boolean _afterPhase = false;
281     private PhaseId _currentPhase;
282     private DebugVisitCallback _visitCallback = new DebugVisitCallback();
283 
284     public void afterPhase(PhaseEvent event)
285     {
286         _doTreeVisit(event, true);
287     }
288 
289     public void beforePhase(PhaseEvent event)
290     {
291         _doTreeVisit(event, false);
292     }
293 
294     public PhaseId getPhaseId()
295     {
296         return PhaseId.ANY_PHASE;
297     }
298     
299     private void _doTreeVisit(PhaseEvent event, boolean afterPhase)
300     {
301         _afterPhase = afterPhase;
302         _currentPhase = event.getPhaseId();
303         
304         // visitTree() on the UIViewRoot
305         UIViewRoot viewroot = event.getFacesContext().getViewRoot();
306         if (viewroot != null)
307         {
308             // skip all unrendered components to really only show
309             // the rendered components and to circumvent data access problems
310             viewroot.visitTree(VisitContext.createVisitContext(
311                     event.getFacesContext(), null, 
312                     EnumSet.of(VisitHint.SKIP_UNRENDERED)),
313                     _visitCallback);
314         }
315     }
316 
317 }