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