1 /*
2 * $Id: RestoreViewPhase.java,v 1.51.4.2 2007/10/03 20:37:27 rlubke Exp $
3 */
4
5 /*
6 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
7 *
8 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
9 *
10 * The contents of this file are subject to the terms of either the GNU
11 * General Public License Version 2 only ("GPL") or the Common Development
12 * and Distribution License("CDDL") (collectively, the "License"). You
13 * may not use this file except in compliance with the License. You can obtain
14 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
15 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
16 * language governing permissions and limitations under the License.
17 *
18 * When distributing the software, include this License Header Notice in each
19 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
20 * Sun designates this particular file as subject to the "Classpath" exception
21 * as provided by Sun in the GPL Version 2 section of the License file that
22 * accompanied this code. If applicable, add the following below the License
23 * Header, with the fields enclosed by brackets [] replaced by your own
24 * identifying information: "Portions Copyrighted [year]
25 * [name of copyright owner]"
26 *
27 * Contributor(s):
28 *
29 * If you wish your version of this file to be governed by only the CDDL or
30 * only the GPL Version 2, indicate your decision by adding "[Contributor]
31 * elects to include this software in this distribution under the [CDDL or GPL
32 * Version 2] license." If you don't indicate a single choice of license, a
33 * recipient has the option to distribute your version of this file under
34 * either the CDDL, the GPL Version 2 or to extend the choice of license to
35 * its licensees as provided above. However, if you add GPL Version 2 code
36 * and therefore, elected the GPL Version 2 license, then the option applies
37 * only if the new code is made subject to such option by the copyright
38 * holder.
39 */
40
41 // RestoreViewPhase.java
42
43 package com.sun.faces.lifecycle;
44
45 import java.util.Iterator;
46 import java.util.ListIterator;
47 import java.util.Map;
48 import java.util.logging.Level;
49 import java.util.logging.Logger;
50
51 import javax.el.ValueExpression;
52 import javax.faces.FacesException;
53 import javax.faces.application.ViewExpiredException;
54 import javax.faces.application.ViewHandler;
55 import javax.faces.component.UIComponent;
56 import javax.faces.component.UIViewRoot;
57 import javax.faces.context.FacesContext;
58 import javax.faces.event.PhaseId;
59 import javax.faces.event.PhaseListener;
60 import javax.faces.lifecycle.Lifecycle;
61 import javax.faces.render.ResponseStateManager;
62 import javax.servlet.http.HttpServletRequest;
63
64 import com.sun.faces.config.WebConfiguration;
65 import com.sun.faces.config.WebConfiguration.BooleanWebContextInitParameter;
66 import com.sun.faces.renderkit.RenderKitUtils;
67 import com.sun.faces.util.DebugUtil;
68 import com.sun.faces.util.FacesLogger;
69 import com.sun.faces.util.MessageUtils;
70 import com.sun.faces.util.Util;
71
72 /**
73 * <B>Lifetime And Scope</B> <P> Same lifetime and scope as
74 * DefaultLifecycleImpl.
75 *
76 * @version $Id: RestoreViewPhase.java,v 1.51.4.2 2007/10/03 20:37:27 rlubke Exp $
77 */
78
79 public class RestoreViewPhase extends Phase {
80
81 private static final String WEBAPP_ERROR_PAGE_MARKER =
82 "javax.servlet.error.message";
83
84 private static Logger LOGGER = FacesLogger.LIFECYCLE.getLogger();
85
86 private WebConfiguration webConfig;
87
88
89 // ---------------------------------------------------------- Public Methods
90
91
92 public PhaseId getId() {
93
94 return PhaseId.RESTORE_VIEW;
95
96 }
97
98
99 public void doPhase(FacesContext context,
100 Lifecycle lifecycle,
101 ListIterator<PhaseListener> listeners) {
102
103 Util.getViewHandler(context).initView(context);
104 super.doPhase(context, lifecycle, listeners);
105
106 }
107
108 /**
109 * PRECONDITION: the necessary factories have been installed in the
110 * ServletContext attr set. <P>
111 * <p/>
112 * POSTCONDITION: The facesContext has been initialized with a tree.
113 */
114
115 public void execute(FacesContext facesContext) throws FacesException {
116
117 if (LOGGER.isLoggable(Level.FINE)) {
118 LOGGER.fine("Entering RestoreViewPhase");
119 }
120 if (null == facesContext) {
121 throw new FacesException(MessageUtils.getExceptionMessageString(
122 MessageUtils.NULL_CONTEXT_ERROR_MESSAGE_ID));
123 }
124
125 // If an app had explicitely set the tree in the context, use that;
126 //
127 UIViewRoot viewRoot = facesContext.getViewRoot();
128 if (viewRoot != null) {
129 if (LOGGER.isLoggable(Level.FINE)) {
130 LOGGER.fine("Found a pre created view in FacesContext");
131 }
132 facesContext.getViewRoot().setLocale(
133 facesContext.getExternalContext().getRequestLocale());
134 doPerComponentActions(facesContext, viewRoot);
135 if (!isPostback(facesContext)) {
136 facesContext.responseComplete();
137 }
138 return;
139 }
140
141 // Reconstitute or create the request tree
142 Map requestMap = facesContext.getExternalContext().getRequestMap();
143 String viewId = (String)
144 requestMap.get("javax.servlet.include.path_info");
145 if (viewId == null) {
146 viewId = facesContext.getExternalContext().getRequestPathInfo();
147 }
148
149 // It could be that this request was mapped using
150 // a prefix mapping in which case there would be no
151 // path_info. Query the servlet path.
152 if (viewId == null) {
153 viewId = (String)
154 requestMap.get("javax.servlet.include.servlet_path");
155 }
156
157 if (viewId == null) {
158 Object request = facesContext.getExternalContext().getRequest();
159 if (request instanceof HttpServletRequest) {
160 viewId = ((HttpServletRequest) request).getServletPath();
161 }
162 }
163
164 if (viewId == null) {
165 if (LOGGER.isLoggable(Level.WARNING)) {
166 LOGGER.warning("viewId is null");
167 }
168 throw new FacesException(MessageUtils.getExceptionMessageString(
169 MessageUtils.NULL_REQUEST_VIEW_ERROR_MESSAGE_ID));
170 }
171
172 boolean isPostBack = (isPostback(facesContext) && !isErrorPage(facesContext));
173 if (isPostBack) {
174 // try to restore the view
175 ViewHandler viewHandler = Util.getViewHandler(facesContext);
176 viewRoot = viewHandler.restoreView(facesContext, viewId);
177
178 if (viewRoot == null) {
179 if (is11CompatEnabled(facesContext)) {
180 // 1.1 -> create a new view and flag that the response should
181 // be immediately rendered
182 viewRoot = viewHandler.createView(facesContext, viewId);
183 facesContext.renderResponse();
184 } else {
185 Object[] params = {viewId};
186 throw new ViewExpiredException(
187 MessageUtils.getExceptionMessageString(
188 MessageUtils.RESTORE_VIEW_ERROR_MESSAGE_ID,
189 params),
190 viewId);
191 }
192 }
193
194 facesContext.setViewRoot(viewRoot);
195 doPerComponentActions(facesContext, viewRoot);
196
197 if (LOGGER.isLoggable(Level.FINE)) {
198 LOGGER.fine("Postback: Restored view for " + viewId);
199 }
200 } else {
201 if (LOGGER.isLoggable(Level.FINE)) {
202 LOGGER.fine("New request: creating a view for " + viewId);
203 }
204 // if that fails, create one
205 viewRoot = (Util.getViewHandler(facesContext)).
206 createView(facesContext, viewId);
207 facesContext.setViewRoot(viewRoot);
208 facesContext.renderResponse();
209 }
210 assert(null != viewRoot);
211
212 if (isPostBack && LOGGER.isLoggable(Level.FINEST)) {
213 LOGGER.log(Level.FINEST, "+=+=+=+=+=+= Restored View Printout for " + viewId);
214 DebugUtil.printTree(viewRoot, LOGGER, Level.FINEST);
215 }
216
217 if (LOGGER.isLoggable(Level.FINE)) {
218 LOGGER.fine("Exiting RestoreViewPhase");
219 }
220
221 }
222
223
224 // ------------------------------------------------------- Protected Methods
225
226
227 /**
228 * <p>Do any per-component actions necessary during reconstitute</p>
229 * @param context the <code>FacesContext</code> for the current request
230 * @param uic the <code>UIComponent</code> to process the
231 * <code>binding</code> attribute
232 */
233 protected void doPerComponentActions(FacesContext context,
234 UIComponent uic) {
235
236 // if this component has a component value reference expression,
237 // make sure to populate the ValueExpression for it.
238 ValueExpression valueExpression;
239 if (null != (valueExpression = uic.getValueExpression("binding"))) {
240 valueExpression.setValue(context.getELContext(), uic);
241 }
242
243 for (Iterator<UIComponent> kids = uic.getFacetsAndChildren();
244 kids.hasNext(); ) {
245 doPerComponentActions(context, kids.next());
246 }
247
248 }
249
250 // --------------------------------------------------------- Private Methods
251
252
253 /**
254 * @param context the <code>FacesContext</code> for the current request
255 * @return <code>true</code> if the {@link ResponseStateManager#isPostback(javax.faces.context.FacesContext)}
256 * returns <code>true</code> <em>and</em> the request doesn't contain the
257 * attribute <code>javax.servlet.error.message</code> which indicates we've been
258 * forwarded to an error page.
259 */
260
261 private boolean isPostback(FacesContext context) {
262
263 // Get the renderKitId by calling viewHandler.calculateRenderKitId().
264 String renderkitId =
265 context.getApplication().getViewHandler().
266 calculateRenderKitId(context);
267 ResponseStateManager rsm =
268 RenderKitUtils.getResponseStateManager(context,
269 renderkitId);
270 return rsm.isPostback(context);
271
272 }
273
274
275 /**
276 * The Servlet specification states that if an error occurs
277 * in the application and there is a matching error-page declaration,
278 * the that original request the cause the error is forwarded
279 * to the error page.
280 *
281 * If the error occurred during a post-back and a matching
282 * error-page definition was found, then an attempt to restore
283 * the error view would be made as the javax.faces.ViewState
284 * marker would still be in the request parameters.
285 *
286 * Use this method to determine if the current request is
287 * an error page to avoid the above condition.
288 *
289 * @param context the FacesContext for the current request
290 * @return <code>true</code> if <code>WEBAPP_ERROR_PAGE_MARKER</code>
291 * is found in the request, otherwise return <code>false</code>
292 */
293 private static boolean isErrorPage(FacesContext context) {
294
295 return (context.getExternalContext().
296 getRequestMap().get(WEBAPP_ERROR_PAGE_MARKER) != null);
297
298 }
299
300
301 private WebConfiguration getWebConfig(FacesContext context) {
302
303 if (webConfig == null) {
304 webConfig = WebConfiguration.getInstance(context.getExternalContext());
305 }
306 return webConfig;
307
308 }
309
310 private boolean is11CompatEnabled(FacesContext context) {
311
312 return (getWebConfig(context).isOptionEnabled(
313 BooleanWebContextInitParameter.EnableRestoreView11Compatibility));
314
315 }
316
317 // The testcase for this class is TestRestoreViewPhase.java
318
319 } // end of class RestoreViewPhase