001    package org.apache.myfaces.tobago.component;
002    
003    /*
004     * Licensed to the Apache Software Foundation (ASF) under one or more
005     * contributor license agreements.  See the NOTICE file distributed with
006     * this work for additional information regarding copyright ownership.
007     * The ASF licenses this file to You under the Apache License, Version 2.0
008     * (the "License"); you may not use this file except in compliance with
009     * the License.  You may obtain a copy of the License at
010     *
011     *      http://www.apache.org/licenses/LICENSE-2.0
012     *
013     * Unless required by applicable law or agreed to in writing, software
014     * distributed under the License is distributed on an "AS IS" BASIS,
015     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016     * See the License for the specific language governing permissions and
017     * limitations under the License.
018     */
019    
020    import org.slf4j.Logger;
021    import org.slf4j.LoggerFactory;
022    import org.apache.myfaces.tobago.ajax.AjaxUtils;
023    import org.apache.myfaces.tobago.compat.FacesUtils;
024    import org.apache.myfaces.tobago.compat.InvokeOnComponent;
025    import org.apache.myfaces.tobago.context.ClientProperties;
026    import org.apache.myfaces.tobago.context.TobagoFacesContext;
027    import org.apache.myfaces.tobago.internal.ajax.AjaxInternalUtils;
028    import org.apache.myfaces.tobago.internal.ajax.AjaxResponseRenderer;
029    import org.apache.myfaces.tobago.internal.component.AbstractUIPage;
030    import org.apache.myfaces.tobago.util.ApplyRequestValuesCallback;
031    import org.apache.myfaces.tobago.util.ComponentUtils;
032    import org.apache.myfaces.tobago.util.ProcessValidationsCallback;
033    import org.apache.myfaces.tobago.util.TobagoCallback;
034    import org.apache.myfaces.tobago.util.UpdateModelValuesCallback;
035    import org.apache.myfaces.tobago.util.VariableResolverUtils;
036    
037    import javax.faces.FacesException;
038    import javax.faces.component.ContextCallback;
039    import javax.faces.component.UIComponent;
040    import javax.faces.context.FacesContext;
041    import javax.faces.event.AbortProcessingException;
042    import javax.faces.event.FacesEvent;
043    import javax.faces.event.PhaseId;
044    import java.io.IOException;
045    import java.util.ArrayList;
046    import java.util.ConcurrentModificationException;
047    import java.util.List;
048    import java.util.ListIterator;
049    import java.util.Locale;
050    import java.util.Map;
051    
052    public class UIViewRoot extends javax.faces.component.UIViewRoot implements InvokeOnComponent {
053    
054      private static final Logger LOG = LoggerFactory.getLogger(UIViewRoot.class);
055    
056      private static final TobagoCallback APPLY_REQUEST_VALUES_CALLBACK = new ApplyRequestValuesCallback();
057      private static final ContextCallback PROCESS_VALIDATION_CALLBACK = new ProcessValidationsCallback();
058      private static final ContextCallback UPDATE_MODEL_VALUES_CALLBACK = new UpdateModelValuesCallback();
059    
060      private List<FacesEvent> events;
061    
062    // XXX check if TOBAGO-811 is still an issue
063    //  private int nextUniqueId;
064    
065      @Override
066      public void setLocale(Locale locale) {
067        super.setLocale(locale);
068        ClientProperties clientProperties = VariableResolverUtils.resolveClientProperties(getFacesContext());
069        clientProperties.setLocale(locale);
070      }
071    
072    /*
073    // XXX check if TOBAGO-811 is still an issue
074    
075      @Override
076      public Object saveState(FacesContext facesContext) {
077        if (FacesVersion.supports12()) {
078          return super.saveState(facesContext);
079        } else {
080          Object[] state = new Object[2];
081          state[0] = super.saveState(facesContext);
082          state[1] = nextUniqueId;
083          return state;
084        }
085      }
086    
087      @Override
088      public void restoreState(FacesContext facesContext, Object o) {
089        if (FacesVersion.supports12()) {
090          super.restoreState(facesContext, o);
091        } else {
092          Object[] state = (Object[]) o;
093          super.restoreState(facesContext, state[0]);
094          nextUniqueId = (Integer) state[1];
095        }
096      }
097    
098      @Override
099      public String createUniqueId() {
100        if (FacesVersion.supports12()) {
101          return super.createUniqueId();
102        } else {
103          ExternalContext extCtx = FacesContext.getCurrentInstance().getExternalContext();
104          return extCtx.encodeNamespace(UNIQUE_ID_PREFIX + nextUniqueId++);
105        }
106      }
107    */
108    
109      // XXX begin of JSF 2.0 like code
110    
111      public void broadcastEventsForPhase(FacesContext context, PhaseId phaseId) {
112        broadcastForPhase(phaseId);
113        if (context.getRenderResponse() || context.getResponseComplete()) {
114          clearEvents();
115        }
116      }
117      
118    // -----------------------------------------------------------------------------
119    // -----------------------------------------------------------------------------
120    //
121    //  The following code is copied from myfaces implementation!
122    //  In suns jsf-api 1.1.01 are the events not cleared if renderResponse is true
123    //  after processUpdates, seems to be a bug. This is fixed at least in
124    //  Nightly Snapshot from 15.08.2005, but not in stable yet.
125    //  Events are private member of UIViewRoot, so we have to copy anny code
126    //  accessing them.
127    //
128      // TODO: remove if fixed in stable release! In 1.1_02 this seems to be fixed.
129    
130      public void queueEvent(FacesEvent event) {
131        if (event == null) {
132          throw new NullPointerException("event");
133        }
134        if (events == null) {
135          events = new ArrayList<FacesEvent>();
136        }
137        events.add(event);
138      }
139    
140    
141      private void broadcastForPhase(PhaseId phaseId) {
142        if (events == null) {
143          return;
144        }
145    
146        boolean abort = false;
147    
148        int phaseIdOrdinal = phaseId.getOrdinal();
149        for (ListIterator<FacesEvent> listiterator = events.listIterator(); listiterator.hasNext();) {
150          FacesEvent event = listiterator.next();
151          int ordinal = event.getPhaseId().getOrdinal();
152          if (ordinal == PhaseId.ANY_PHASE.getOrdinal() || ordinal == phaseIdOrdinal) {
153            UIComponent source = event.getComponent();
154            try {
155              source.broadcast(event);
156            } catch (FacesException e) {
157              Throwable fe = e;
158              while (fe != null) {
159                if (fe instanceof AbortProcessingException) {
160                  if (LOG.isTraceEnabled()) {
161                    LOG.trace("AbortProcessingException caught!");
162                  }
163                  // abort event processing
164                  // Page 3-30 of JSF 1.1 spec: "Throw an AbortProcessingException, to tell the JSF implementation
165                  //  that no further broadcast of this event, or any further events, should take place."
166                  abort = true;
167                  break;
168                }
169                fe = fe.getCause();
170              }
171              if (!abort) {
172                throw e;
173              } else {
174                break;
175              }
176            } finally {
177    
178              try {
179                listiterator.remove();
180              } catch (ConcurrentModificationException cme) {
181                int eventIndex = listiterator.previousIndex();
182                events.remove(eventIndex);
183                //listiterator = events.listIterator();
184              }
185            }
186          }
187        }
188    
189        if (abort) {
190          // TODO: abort processing of any event of any phase or just of any event of the current phase???
191          clearEvents();
192        }
193      }
194    
195    
196      private void clearEvents() {
197        events = null;
198      }
199    
200    
201      @Override
202      public void processDecodes(FacesContext context) {
203        if (context == null) {
204          throw new NullPointerException("context");
205        }
206        Map<String, UIComponent> ajaxComponents = AjaxInternalUtils.parseAndStoreComponents(context);
207        if (ajaxComponents != null) {
208          // first decode the page
209          AbstractUIPage page = ComponentUtils.findPage(context);
210          page.decode(context);
211          page.markSubmittedForm(context);
212          if (context instanceof TobagoFacesContext) {
213            ((TobagoFacesContext) context).setAjax(true);
214          }
215          // decode the action if actionComponent not inside one of the ajaxComponents
216          // otherwise it is decoded there
217          decodeActionComponent(context, page, ajaxComponents);
218    
219          // and all ajax components
220          for (Map.Entry<String, UIComponent> entry : ajaxComponents.entrySet()) {
221            if (context instanceof TobagoFacesContext) {
222              ((TobagoFacesContext) context).setAjaxComponentId(entry.getKey());
223            }
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        }
242        if (actionComponent == null) {
243          return;
244        }
245        for (UIComponent ajaxComponent : ajaxComponents.values()) {
246          UIComponent component = actionComponent;
247          while (component != null) {
248            if (component == ajaxComponent) {
249              return;
250            }
251            component = component.getParent();
252          }
253        }
254        invokeOnComponent(facesContext, actionId, APPLY_REQUEST_VALUES_CALLBACK);
255      }
256    
257    
258      @Override
259      public void processValidators(FacesContext context) {
260        if (context == null) {
261          throw new NullPointerException("context");
262        }
263    
264        Map<String, UIComponent> ajaxComponents = AjaxInternalUtils.getAjaxComponents(context);
265        if (ajaxComponents != null) {
266          for (Map.Entry<String, UIComponent> entry : ajaxComponents.entrySet()) {
267            if (context instanceof TobagoFacesContext) {
268              ((TobagoFacesContext) context).setAjaxComponentId(entry.getKey());
269            }
270            invokeOnComponent(context, entry.getKey(), PROCESS_VALIDATION_CALLBACK);
271          }
272        } else {
273          super.processValidators(context);
274        }
275        broadcastForPhase(PhaseId.PROCESS_VALIDATIONS);
276        if (context.getRenderResponse() || context.getResponseComplete()) {
277          clearEvents();
278        }
279      }
280    
281      @Override
282      public void processUpdates(FacesContext context) {
283        if (context == null) {
284          throw new NullPointerException("context");
285        }
286        Map<String, UIComponent> ajaxComponents = AjaxInternalUtils.getAjaxComponents(context);
287        if (ajaxComponents != null) {
288          for (Map.Entry<String, UIComponent> entry : ajaxComponents.entrySet()) {
289            invokeOnComponent(context, entry.getKey(), UPDATE_MODEL_VALUES_CALLBACK);
290          }
291        } else {
292          super.processUpdates(context);
293        }
294        broadcastForPhase(PhaseId.UPDATE_MODEL_VALUES);
295        if (context.getRenderResponse() || context.getResponseComplete()) {
296          clearEvents();
297        }
298      }
299    
300      @Override
301      public void processApplication(FacesContext context) {
302        if (context == null) {
303          throw new NullPointerException("context");
304        }
305        broadcastForPhase(PhaseId.INVOKE_APPLICATION);
306        if (context.getRenderResponse() || context.getResponseComplete()) {
307          clearEvents();
308        }
309      }
310    
311      // XXX end of JSF 2.0 like code
312    
313      @Override
314      public boolean getRendersChildren() {
315        if (AjaxUtils.isAjaxRequest(FacesContext.getCurrentInstance())) {
316          return true;
317        } else {
318          return super.getRendersChildren();
319        }
320      }
321    
322      @Override
323      public void encodeChildren(FacesContext context) throws IOException {
324        if (AjaxUtils.isAjaxRequest(context)) {
325          new AjaxResponseRenderer().renderResponse(context);
326    
327        } else {
328          super.encodeChildren(context);
329        }
330      }
331    
332      // todo: after removing jsf 1.1: @Override
333      public boolean invokeOnComponent(FacesContext context, String clientId, ContextCallback callback)
334          throws FacesException {
335        return FacesUtils.invokeOnComponent(context, this, clientId, callback);
336      }
337    }