001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     *
010     *   http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing,
013     * software distributed under the License is distributed on an
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     * KIND, either express or implied.  See the License for the
016     * specific language governing permissions and limitations
017     * under the License.
018     */
019    
020    package org.apache.myfaces.tobago.component;
021    
022    import org.apache.commons.logging.Log;
023    import org.apache.commons.logging.LogFactory;
024    import org.apache.myfaces.tobago.context.ClientProperties;
025    import org.apache.myfaces.tobago.context.ResourceManagerImpl;
026    
027    import javax.faces.FacesException;
028    import javax.faces.component.UIComponent;
029    import javax.faces.context.FacesContext;
030    import javax.faces.event.AbortProcessingException;
031    import javax.faces.event.FacesEvent;
032    import javax.faces.event.PhaseId;
033    import java.util.ArrayList;
034    import java.util.List;
035    import java.util.ListIterator;
036    import java.util.Locale;
037    import java.util.Map;
038    
039    public class UIViewRoot extends javax.faces.component.UIViewRoot {
040    
041      private static final Log LOG = LogFactory.getLog(UIViewRoot.class);
042    
043      private static final String EVENT_LIST_KEY = UIViewRoot.class.getName() + ".EventList";
044      public static final int ANY_PHASE_ORDINAL = PhaseId.ANY_PHASE.getOrdinal();
045    
046      private ResourceManagerImpl.CacheKey rendererCacheKey;
047    
048      private ClientProperties clientProperties;
049    
050      /**
051       * <p>Create a new {@link UIViewRoot} instance with default property
052       * values.</p>
053       */
054      public UIViewRoot() {
055        super();
056        updateRendererCachePrefix();
057      }
058    
059      public ClientProperties getClientProperties() {
060        return clientProperties;
061      }
062    
063      public void setClientProperties(final ClientProperties clientProperties) {
064        this.clientProperties = clientProperties;
065        updateRendererCachePrefix();
066      }
067    
068      @Override
069      public void setLocale(final Locale locale) {
070        super.setLocale(locale);
071        updateRendererCachePrefix();
072      }
073    
074      public ResourceManagerImpl.CacheKey getRendererCacheKey() {
075        return rendererCacheKey;
076      }
077    
078    
079      public void updateRendererCachePrefix() {
080        rendererCacheKey = ResourceManagerImpl.getRendererCacheKey(
081            clientProperties != null ? clientProperties.getId() : "null", getLocale());
082    //    LOG.info("updateRendererCachePrefix :" + rendererCachePrefix);
083      }
084    
085      public void broadcastEventsForPhase(final FacesContext context, final PhaseId phaseId) {
086        broadcastForPhase(phaseId);
087        if (context.getRenderResponse() || context.getResponseComplete()) {
088          clearEvents(context);
089        }
090      }
091    
092    // -----------------------------------------------------------------------------
093    // -----------------------------------------------------------------------------
094    //
095    //  The following code is copied from myfaces implementation!
096    //  In suns jsf-api 1.1.01 are the events not cleared if renderResponse is true
097    //  after processUpdates, seems to be a bug. This is fixed at least in
098    //  Nightly Snapshot from 15.08.2005, but not in stable yet.
099    //  Events are private member of UIViewRoot, so we have to copy anny code
100    //  accessing them.
101    //
102      // TODO: remove if fixed in stable release!
103    
104      @Override
105      public void queueEvent(final FacesEvent event) {
106        if (event == null) {
107          throw new NullPointerException("event");
108        }
109        getEvents(FacesContext.getCurrentInstance(), true).add(event);
110      }
111    
112    
113      private void broadcastForPhase(final PhaseId phaseId) {
114        List<FacesEvent> events = getEvents(FacesContext.getCurrentInstance(), false);
115        if (events == null) {
116          return;
117        }
118    
119        boolean abort = false;
120    
121        int phaseIdOrdinal = phaseId.getOrdinal();
122        for (ListIterator listiterator = events.listIterator(); listiterator.hasNext();) {
123          FacesEvent event = (FacesEvent) listiterator.next();
124          int ordinal = event.getPhaseId().getOrdinal();
125          if (ordinal == ANY_PHASE_ORDINAL
126              || ordinal == phaseIdOrdinal) {
127            UIComponent source = event.getComponent();
128            try {
129              source.broadcast(event);
130            } catch (FacesException e) {
131              Throwable fe = e;
132              while (fe != null) {
133                if (fe instanceof AbortProcessingException) {
134                  if (LOG.isTraceEnabled()) {
135                    LOG.trace("AbortProcessingException caught!");
136                  }
137                  // abort event processing
138                  // Page 3-30 of JSF 1.1 spec: "Throw an AbortProcessingException, to tell the JSF implementation
139                  //  that no further broadcast of this event, or any further events, should take place."
140                  abort = true;
141                  break;
142                }
143                fe = fe.getCause();
144              }
145              if (!abort) {
146                throw e;
147              } else {
148                break;
149              }
150            } finally {
151              listiterator.remove();
152            }
153          }
154        }
155    
156        if (abort) {
157          // TODO: abort processing of any event of any phase or just of any event of the current phase???
158          clearEvents(FacesContext.getCurrentInstance());
159        }
160      }
161    
162    
163      private void clearEvents(final FacesContext context) {
164        context.getExternalContext().getRequestMap().remove(EVENT_LIST_KEY);
165      }
166    
167    
168      @Override
169      public void processDecodes(final FacesContext context) {
170        if (context == null) {
171          throw new NullPointerException("context");
172        }
173        super.processDecodes(context);
174        broadcastForPhase(PhaseId.APPLY_REQUEST_VALUES);
175        if (context.getRenderResponse() || context.getResponseComplete()) {
176          clearEvents(context);
177        }
178      }
179    
180      @Override
181      public void processValidators(final FacesContext context) {
182        if (context == null) {
183          throw new NullPointerException("context");
184        }
185        super.processValidators(context);
186        broadcastForPhase(PhaseId.PROCESS_VALIDATIONS);
187        if (context.getRenderResponse() || context.getResponseComplete()) {
188          clearEvents(context);
189        }
190      }
191    
192      @Override
193      public void processUpdates(final FacesContext context) {
194        if (context == null) {
195          throw new NullPointerException("context");
196        }
197        super.processUpdates(context);
198        broadcastForPhase(PhaseId.UPDATE_MODEL_VALUES);
199        if (context.getRenderResponse() || context.getResponseComplete()) {
200          clearEvents(context);
201        }
202      }
203    
204      @Override
205      public void processApplication(final FacesContext context) {
206        if (context == null) {
207          throw new NullPointerException("context");
208        }
209        broadcastForPhase(PhaseId.INVOKE_APPLICATION);
210        if (context.getRenderResponse() || context.getResponseComplete()) {
211          clearEvents(context);
212        }
213      }
214    
215      private List<FacesEvent> getEvents(final FacesContext context, boolean create) {
216        final Map requestMap = context.getExternalContext().getRequestMap();
217        List<FacesEvent> events = (List<FacesEvent>) requestMap.get(EVENT_LIST_KEY);
218        if (events == null && create) {
219          events = new ArrayList<FacesEvent>();
220          requestMap.put(EVENT_LIST_KEY, events);
221        }
222        return events;
223      }
224    }