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  
20  package org.apache.myfaces.tobago.component;
21  
22  import org.apache.myfaces.tobago.ajax.AjaxUtils;
23  import org.apache.myfaces.tobago.compat.InvokeOnComponent;
24  import org.apache.myfaces.tobago.context.ClientProperties;
25  import org.apache.myfaces.tobago.internal.ajax.AjaxInternalUtils;
26  import org.apache.myfaces.tobago.internal.ajax.AjaxResponseRenderer;
27  import org.apache.myfaces.tobago.internal.component.AbstractUIPage;
28  import org.apache.myfaces.tobago.internal.util.FacesContextUtils;
29  import org.apache.myfaces.tobago.util.ApplyRequestValuesCallback;
30  import org.apache.myfaces.tobago.util.ComponentUtils;
31  import org.apache.myfaces.tobago.util.FacesVersion;
32  import org.apache.myfaces.tobago.util.ProcessValidationsCallback;
33  import org.apache.myfaces.tobago.util.TobagoCallback;
34  import org.apache.myfaces.tobago.util.UpdateModelValuesCallback;
35  import org.apache.myfaces.tobago.util.VariableResolverUtils;
36  import org.slf4j.Logger;
37  import org.slf4j.LoggerFactory;
38  
39  import javax.faces.FacesException;
40  import javax.faces.component.ContextCallback;
41  import javax.faces.component.UIComponent;
42  import javax.faces.context.FacesContext;
43  import javax.faces.event.AbortProcessingException;
44  import javax.faces.event.FacesEvent;
45  import javax.faces.event.PhaseId;
46  import java.io.IOException;
47  import java.util.ArrayList;
48  import java.util.ConcurrentModificationException;
49  import java.util.List;
50  import java.util.ListIterator;
51  import java.util.Locale;
52  import java.util.Map;
53  
54  public class UIViewRoot extends javax.faces.component.UIViewRoot implements InvokeOnComponent {
55  
56    private static final Logger LOG = LoggerFactory.getLogger(UIViewRoot.class);
57  
58    private static final TobagoCallback APPLY_REQUEST_VALUES_CALLBACK = new ApplyRequestValuesCallback();
59    private static final ContextCallback PROCESS_VALIDATION_CALLBACK = new ProcessValidationsCallback();
60    private static final ContextCallback UPDATE_MODEL_VALUES_CALLBACK = new UpdateModelValuesCallback();
61  
62    private List<FacesEvent> events;
63  
64  // XXX check if TOBAGO-811 is still an issue
65  //  private int nextUniqueId;
66  
67    @Override
68    public void setLocale(Locale locale) {
69      super.setLocale(locale);
70      ClientProperties clientProperties = VariableResolverUtils.resolveClientProperties(getFacesContext());
71      clientProperties.setLocale(locale);
72      clientProperties.updateUserAgent(getFacesContext());
73    }
74  
75  /*
76  // XXX check if TOBAGO-811 is still an issue
77  
78    @Override
79    public Object saveState(FacesContext facesContext) {
80      if (FacesVersion.supports12()) {
81        return super.saveState(facesContext);
82      } else {
83        Object[] state = new Object[2];
84        state[0] = super.saveState(facesContext);
85        state[1] = nextUniqueId;
86        return state;
87      }
88    }
89  
90    @Override
91    public void restoreState(FacesContext facesContext, Object o) {
92      if (FacesVersion.supports12()) {
93        super.restoreState(facesContext, o);
94      } else {
95        Object[] state = (Object[]) o;
96        super.restoreState(facesContext, state[0]);
97        nextUniqueId = (Integer) state[1];
98      }
99    }
100 
101   @Override
102   public String createUniqueId() {
103     if (FacesVersion.supports12()) {
104       return super.createUniqueId();
105     } else {
106       ExternalContext extCtx = FacesContext.getCurrentInstance().getExternalContext();
107       return extCtx.encodeNamespace(UNIQUE_ID_PREFIX + nextUniqueId++);
108     }
109   }
110 */
111 
112   // XXX begin of JSF 2.0 like code
113 
114   public void broadcastEventsForPhase(FacesContext context, PhaseId phaseId) {
115     broadcastForPhase(phaseId);
116     if (context.getRenderResponse() || context.getResponseComplete()) {
117       clearEvents();
118     }
119   }
120   
121 // -----------------------------------------------------------------------------
122 // -----------------------------------------------------------------------------
123 //
124 //  The following code is copied from myfaces implementation!
125 //  In suns jsf-api 1.1.01 are the events not cleared if renderResponse is true
126 //  after processUpdates, seems to be a bug. This is fixed at least in
127 //  Nightly Snapshot from 15.08.2005, but not in stable yet.
128 //  Events are private member of UIViewRoot, so we have to copy anny code
129 //  accessing them.
130 //
131   // TODO: remove if fixed in stable release! In 1.1_02 this seems to be fixed.
132 
133   public void queueEvent(FacesEvent event) {
134     if (event == null) {
135       throw new NullPointerException("event");
136     }
137     if (events == null) {
138       events = new ArrayList<FacesEvent>();
139     }
140     events.add(event);
141   }
142 
143 
144   private void broadcastForPhase(PhaseId phaseId) {
145     if (events == null) {
146       return;
147     }
148 
149     boolean abort = false;
150 
151     int phaseIdOrdinal = phaseId.getOrdinal();
152     for (ListIterator<FacesEvent> listiterator = events.listIterator(); listiterator.hasNext();) {
153       FacesEvent event = listiterator.next();
154       int ordinal = event.getPhaseId().getOrdinal();
155       if (ordinal == PhaseId.ANY_PHASE.getOrdinal() || ordinal == phaseIdOrdinal) {
156         UIComponent source = event.getComponent();
157         try {
158           source.broadcast(event);
159         } catch (FacesException e) {
160           Throwable fe = e;
161           while (fe != null) {
162             if (fe instanceof AbortProcessingException) {
163               if (LOG.isTraceEnabled()) {
164                 LOG.trace("AbortProcessingException caught!");
165               }
166               // abort event processing
167               // Page 3-30 of JSF 1.1 spec: "Throw an AbortProcessingException, to tell the JSF implementation
168               //  that no further broadcast of this event, or any further events, should take place."
169               abort = true;
170               break;
171             }
172             fe = fe.getCause();
173           }
174           if (!abort) {
175             throw e;
176           } else {
177             break;
178           }
179         } finally {
180 
181           try {
182             listiterator.remove();
183           } catch (ConcurrentModificationException cme) {
184             int eventIndex = listiterator.previousIndex();
185             events.remove(eventIndex);
186             //listiterator = events.listIterator();
187           }
188         }
189       }
190     }
191 
192     if (abort) {
193       // TODO: abort processing of any event of any phase or just of any event of the current phase???
194       clearEvents();
195     }
196   }
197 
198 
199   private void clearEvents() {
200     events = null;
201   }
202 
203 
204   @Override
205   public void processDecodes(FacesContext context) {
206     if (context == null) {
207       throw new NullPointerException("context");
208     }
209     Map<String, UIComponent> ajaxComponents = AjaxInternalUtils.parseAndStoreComponents(context);
210     if (ajaxComponents != null) {
211       // first decode the page
212       AbstractUIPage page = ComponentUtils.findPage(context);
213       page.decode(context);
214       page.markSubmittedForm(context);
215       FacesContextUtils.setAjax(context, true);
216 
217       // decode the action if actionComponent not inside one of the ajaxComponents
218       // otherwise it is decoded there
219       decodeActionComponent(context, page, ajaxComponents);
220 
221       // and all ajax components
222       for (Map.Entry<String, UIComponent> entry : ajaxComponents.entrySet()) {
223         FacesContextUtils.setAjaxComponentId(context, entry.getKey());
224         invokeOnComponent(context, entry.getKey(), APPLY_REQUEST_VALUES_CALLBACK);
225       }
226     } else {
227       super.processDecodes(context);
228     }
229     broadcastForPhase(PhaseId.APPLY_REQUEST_VALUES);
230     if (context.getRenderResponse() || context.getResponseComplete()) {
231       clearEvents();
232     }
233   }
234 
235   private void decodeActionComponent(FacesContext facesContext, AbstractUIPage page, Map<String,
236       UIComponent> ajaxComponents) {
237     String actionId = page.getActionId();
238     UIComponent actionComponent = null;
239     if (actionId != null) {
240       actionComponent = findComponent(actionId);
241       if (actionComponent == null && FacesVersion.supports20() && FacesVersion.isMyfaces()) {
242         String bugActionId = actionId.replaceAll(":\\d+:", ":");
243         try {
244           actionComponent = findComponent(bugActionId);
245           //LOG.info("command = \"" + actionComponent + "\"", new Exception());
246         } catch (Exception e) {
247           // ignore
248         }
249       }
250     }
251     if (actionComponent == null) {
252       return;
253     }
254     for (UIComponent ajaxComponent : ajaxComponents.values()) {
255       UIComponent component = actionComponent;
256       while (component != null) {
257         if (component == ajaxComponent) {
258           return;
259         }
260         component = component.getParent();
261       }
262     }
263     invokeOnComponent(facesContext, actionId, APPLY_REQUEST_VALUES_CALLBACK);
264   }
265 
266 
267   @Override
268   public void processValidators(FacesContext context) {
269     if (context == null) {
270       throw new NullPointerException("context");
271     }
272 
273     Map<String, UIComponent> ajaxComponents = AjaxInternalUtils.getAjaxComponents(context);
274     if (ajaxComponents != null) {
275       for (Map.Entry<String, UIComponent> entry : ajaxComponents.entrySet()) {
276         FacesContextUtils.setAjaxComponentId(context, entry.getKey());
277         invokeOnComponent(context, entry.getKey(), PROCESS_VALIDATION_CALLBACK);
278       }
279     } else {
280       super.processValidators(context);
281     }
282     broadcastForPhase(PhaseId.PROCESS_VALIDATIONS);
283     if (context.getRenderResponse() || context.getResponseComplete()) {
284       clearEvents();
285     }
286   }
287 
288   @Override
289   public void processUpdates(FacesContext context) {
290     if (context == null) {
291       throw new NullPointerException("context");
292     }
293     Map<String, UIComponent> ajaxComponents = AjaxInternalUtils.getAjaxComponents(context);
294     if (ajaxComponents != null) {
295       for (Map.Entry<String, UIComponent> entry : ajaxComponents.entrySet()) {
296         invokeOnComponent(context, entry.getKey(), UPDATE_MODEL_VALUES_CALLBACK);
297       }
298     } else {
299       super.processUpdates(context);
300     }
301     broadcastForPhase(PhaseId.UPDATE_MODEL_VALUES);
302     if (context.getRenderResponse() || context.getResponseComplete()) {
303       clearEvents();
304     }
305   }
306 
307   @Override
308   public void processApplication(FacesContext context) {
309     if (context == null) {
310       throw new NullPointerException("context");
311     }
312     broadcastForPhase(PhaseId.INVOKE_APPLICATION);
313     if (context.getRenderResponse() || context.getResponseComplete()) {
314       clearEvents();
315     }
316   }
317 
318   // XXX end of JSF 2.0 like code
319 
320   @Override
321   public boolean getRendersChildren() {
322     if (AjaxUtils.isAjaxRequest(FacesContext.getCurrentInstance())) {
323       return true;
324     } else {
325       return super.getRendersChildren();
326     }
327   }
328 
329   @Override
330   public void encodeChildren(FacesContext context) throws IOException {
331     if (AjaxUtils.isAjaxRequest(context)) {
332       new AjaxResponseRenderer().renderResponse(context);
333 
334     } else {
335       super.encodeChildren(context);
336     }
337   }
338 
339   @Override
340   public boolean invokeOnComponent(FacesContext context, String clientId, ContextCallback callback)
341       throws FacesException {
342     return ComponentUtils.invokeOnComponent(context, this, clientId, callback);
343   }
344 }