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.internal.ajax;
21  
22  import org.apache.myfaces.tobago.ajax.AjaxUtils;
23  
24  import org.slf4j.Logger;
25  import org.slf4j.LoggerFactory;
26  
27  import javax.faces.application.FacesMessage;
28  import javax.faces.application.ViewExpiredException;
29  import javax.faces.component.UIViewRoot;
30  import javax.faces.context.ExternalContext;
31  import javax.faces.context.FacesContext;
32  import javax.faces.event.ExceptionQueuedEvent;
33  import java.io.IOException;
34  import java.util.ArrayList;
35  import java.util.HashMap;
36  import java.util.Iterator;
37  import java.util.List;
38  import java.util.Map;
39  
40  public final class AjaxNavigationState {
41  
42    private static final Logger LOG = LoggerFactory.getLogger(AjaxNavigationState.class);
43  
44    private static final String SESSION_KEY = "tobago-AjaxNavigationState";
45  
46    private static final String VIEW_ROOT_KEY = "tobago-AjaxNavigationState-VIEW_ROOT_KEY";
47  
48    private UIViewRoot viewRoot;
49  
50    private Map<String, List<FacesMessage>> messages;
51  
52    private AjaxNavigationState(final FacesContext facesContext) {
53      final ExternalContext externalContext = facesContext.getExternalContext();
54      externalContext.getSessionMap().put(SESSION_KEY, this);
55      viewRoot = facesContext.getViewRoot();
56      messages = new HashMap<String, List<FacesMessage>>();
57      final Iterator<String> iterator = facesContext.getClientIdsWithMessages();
58      while (iterator.hasNext()) {
59        addFacesMessages(facesContext, iterator.next());
60      }
61      if (LOG.isTraceEnabled()) {
62        LOG.trace("Saved viewRoot.getViewId() = \"{}\"", viewRoot.getViewId());
63        for (final Map.Entry<String, List<FacesMessage>> entry : messages.entrySet()) {
64          for (final FacesMessage message : entry.getValue()) {
65            LOG.trace("Saved message \"{}\" : \"{}\"", entry.getKey(), message);
66          }
67        }
68      }
69    }
70  
71    private void addFacesMessages(final FacesContext facesContext, final String clientId) {
72      final Iterator<FacesMessage> facesMessages = facesContext.getMessages(clientId);
73      while (facesMessages.hasNext()) {
74        addFacesMessage(clientId, facesMessages.next());
75      }
76    }
77  
78    private void addFacesMessage(final String clientId, final FacesMessage facesMessage) {
79      List<FacesMessage> facesMessages = messages.get(clientId);
80      if (facesMessages == null) {
81        facesMessages = new ArrayList<FacesMessage>();
82        messages.put(clientId, facesMessages);
83      }
84      facesMessages.add(facesMessage);
85    }
86  
87    private void restoreView(final FacesContext facesContext) {
88      facesContext.setViewRoot(viewRoot);
89      for (final Map.Entry<String, List<FacesMessage>> entry : messages.entrySet()) {
90        for (final FacesMessage facesMessage : entry.getValue()) {
91          facesContext.addMessage(entry.getKey(), facesMessage);
92        }
93      }
94      facesContext.renderResponse();
95      if (LOG.isTraceEnabled()) {
96        LOG.trace("Restored viewRoot.getViewId() = \"{}\"", viewRoot.getViewId());
97        for (final Map.Entry<String, List<FacesMessage>> entry : messages.entrySet()) {
98          for (final FacesMessage message : entry.getValue()) {
99            LOG.trace("Restored message \"{}\" : \"{}\"", entry.getKey(), message);
100         }
101       }
102     }
103 
104   }
105 
106   public static void storeIncomingView(final FacesContext facesContext) {
107     final UIViewRoot viewRoot = facesContext.getViewRoot();
108     if (LOG.isTraceEnabled()) {
109       if (viewRoot != null) {
110         LOG.trace("incoming viewId = '{}'", viewRoot.getViewId());
111       } else {
112         LOG.trace("incoming viewRoot is null");
113       }
114     }
115     facesContext.getExternalContext().getRequestMap().put(AjaxNavigationState.VIEW_ROOT_KEY, viewRoot);
116   }
117 
118   public static boolean isNavigation(final FacesContext facesContext) {
119 
120     final UIViewRoot viewRoot = facesContext.getViewRoot();
121     if (viewRoot != null) {
122       if (LOG.isDebugEnabled()) {
123         LOG.debug("current viewId = '{}'", viewRoot.getViewId());
124       }
125     } else {
126       LOG.warn("current viewRoot is null");
127       return false;
128     }
129 
130     final Map<String, Object> requestMap = facesContext.getExternalContext().getRequestMap();
131     final UIViewRoot incomingViewRoot = (UIViewRoot) requestMap.get(VIEW_ROOT_KEY);
132     if (viewRoot != incomingViewRoot) {
133       if (LOG.isDebugEnabled()) {
134         LOG.debug("requesting full page reload because of navigation to {} from {}",
135             viewRoot.getViewId(), incomingViewRoot.getViewId());
136       }
137       return true;
138     }
139     return false;
140   }
141 
142   public static void beforeRestoreView(final FacesContext facesContext) {
143     if (facesContext.getExternalContext().getSessionMap().get(AjaxNavigationState.SESSION_KEY) != null) {
144       // restoreView phase and afterPhaseListener are executed even if renderResponse is set
145       // so we can't call navigationState.restoreView(...) here in before Phase
146       // set empty UIViewRoot to prevent executing restore state logic
147       facesContext.setViewRoot(new UIViewRoot());
148     }
149   }
150 
151   public static void afterRestoreView(final FacesContext facesContext) {
152     if (isViewExpiredExceptionThrown(facesContext)) {
153       try {
154         facesContext.getExceptionHandler().handle();
155       } catch (ViewExpiredException e) {
156         LOG.error("Caught: " + e.getMessage(), e);
157         try {
158           final ExternalContext externalContext = facesContext.getExternalContext();
159           final String url = externalContext.getRequestContextPath()
160                        + externalContext.getRequestServletPath() + externalContext.getRequestPathInfo();
161           AjaxUtils.redirect(facesContext, url);
162           facesContext.responseComplete();
163         } catch (IOException e1) {
164           LOG.error("Caught: " + e1.getMessage(), e);
165         }
166       }
167     }
168 
169     final ExternalContext externalContext = facesContext.getExternalContext();
170     if (externalContext.getSessionMap().get(AjaxNavigationState.SESSION_KEY) == null) {
171       storeIncomingView(facesContext);
172     } else {
173       final AjaxNavigationState navigationState
174           = (AjaxNavigationState) externalContext.getSessionMap().remove(AjaxNavigationState.SESSION_KEY);
175       navigationState.restoreView(facesContext);
176       LOG.trace("force render requested navigation view");
177     }
178   }
179 
180   private static boolean isViewExpiredExceptionThrown(FacesContext facesContext) {
181     final Iterator<ExceptionQueuedEvent> eventIterator
182         = facesContext.getExceptionHandler().getUnhandledExceptionQueuedEvents().iterator();
183     if (eventIterator.hasNext()) {
184       Throwable throwable = eventIterator.next().getContext().getException();
185       if (throwable instanceof ViewExpiredException) {
186         return true;
187       }
188     }
189     return false;
190   }
191 
192   public static void afterInvokeApplication(FacesContext facesContext) {
193     if (AjaxUtils.isAjaxRequest(facesContext) && isNavigation(facesContext)) {
194       try {
195         facesContext.getExternalContext().getSessionMap().put(SESSION_KEY, new AjaxNavigationState(facesContext));
196         AjaxInternalUtils.requestNavigationReload(facesContext);
197       } catch (IOException e) {
198         LOG.error("Caught: " + e.getMessage(), e);
199       }
200     }
201   }
202 }