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$)
49   * @version $Revision$ $Date$
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 (int i = 0, size = fieldDebugInfos.size(); i < size; i++)
233             {
234                 Object[] debugInfo = fieldDebugInfos.get(i);
235                 if (debugInfo[0].equals(_currentPhase))
236                 {
237                     found = true;
238                     break;
239                 }
240             }
241             if (!found)
242             {
243                 // there are no debug infos for this field in this lifecycle phase yet
244                 // --> create them
245                 Object oldValue = requestMap.remove(ErrorPageWriter.DEBUG_INFO_KEY 
246                         + clientId + field);
247                 createFieldDebugInfo(facesContext, field,
248                         oldValue, newValue, clientId);
249             }
250         }
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             Object value = evh.getValue();
266             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                 ValueExpression valueExpression = target.getValueExpression("value");
271                 if (valueExpression != null)
272                 {
273                     value = valueExpression.getValue(elCtx);
274                 }
275             }
276             return value;
277         }
278         
279     }
280     
281     private boolean _afterPhase = false;
282     private PhaseId _currentPhase;
283     private DebugVisitCallback _visitCallback = new DebugVisitCallback();
284 
285     public void afterPhase(PhaseEvent event)
286     {
287         _doTreeVisit(event, true);
288     }
289 
290     public void beforePhase(PhaseEvent event)
291     {
292         _doTreeVisit(event, false);
293     }
294 
295     public PhaseId getPhaseId()
296     {
297         return PhaseId.ANY_PHASE;
298     }
299     
300     private void _doTreeVisit(PhaseEvent event, boolean afterPhase)
301     {
302         _afterPhase = afterPhase;
303         _currentPhase = event.getPhaseId();
304         
305         // visitTree() on the UIViewRoot
306         UIViewRoot viewroot = event.getFacesContext().getViewRoot();
307         if (viewroot != null)
308         {
309             // skip all unrendered components to really only show
310             // the rendered components and to circumvent data access problems
311             viewroot.visitTree(VisitContext.createVisitContext(
312                     event.getFacesContext(), null, 
313                     EnumSet.of(VisitHint.SKIP_UNRENDERED)),
314                     _visitCallback);
315         }
316     }
317 
318 }