/* * Copyright 2017 OmniFaces * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ package org.omnifaces.taghandler; import static java.lang.Boolean.TRUE; import static java.lang.String.format; import static org.omnifaces.util.Faces.setApplicationAttribute; import static org.omnifaces.util.FacesLocal.getApplicationAttribute; import java.io.IOException; import javax.faces.application.ViewExpiredException; import javax.faces.application.ViewHandler; import javax.faces.component.UIComponent; import javax.faces.component.UIViewRoot; import javax.faces.context.FacesContext; import javax.faces.view.facelets.ComponentHandler; import javax.faces.view.facelets.FaceletContext; import javax.faces.view.facelets.TagConfig; import javax.faces.view.facelets.TagHandler; import org.omnifaces.viewhandler.OmniViewHandler; /** * <p> * The <code><o:enableRestorableView></code> taghandler instructs the view handler to recreate the entire view * whenever the view has been expired, i.e. whenever {@link ViewHandler#restoreView(FacesContext, String)} returns * <code>null</code> and the current request is a postback. This effectively prevents {@link ViewExpiredException} on * the view. This tag needs to be placed in <code><f:metadata></code> of the view. * <p> * There are however technical design limitations: the recreated view is <b>exactly</b> the same as during the initial * request. In other words, the view has lost its state. Any modifications which were made after the original initial * request, either by taghandlers or (ajax) conditionally rendered components based on some view or even session * scoped variables, are completely lost. Thus, the view should be designed that way that it can be used with a request * scoped bean. You <em>can</em> use it with a view scoped bean, but then you should add a <code>@PostConstruct</code> * which checks if the request is a postback and then fill the missing bean properties based on request parameters. * * <h3>Usage</h3> * <p> * To enable the restorable view, just add the <code><enableRestorableView></code> to the view metadata. * <pre> * <f:metadata> * <o:enableRestorableView/> * </f:metadata> * </pre> * * <h3>Mojarra's new stateless mode</h3> * <p> * Since Mojarra 2.1.19, about 2 months after OmniFaces introduced the <code><o:enableRestorableView></code>, * it's possible to enable a stateless mode on the view by simply setting its <code>transient</code> attribute to * <code>true</code>: * <pre> * <f:view transient="true"> * ... * </f:view> * </pre> * <p> * This goes actually a step further than <code><o:enableRestorableView></code> as no state would be saved at all. * However, on those kind of pages where <code><o:enableRestorableView></code> would work just fine, this * statelessness should not form any problem at all. So, if you have at least Mojarra 2.1.19 at hands, use the * <code>transient="true"</code> instead. * * @author Bauke Scholtz * @since 1.3 * @see OmniViewHandler */ public class EnableRestorableView extends TagHandler { // Constants ------------------------------------------------------------------------------------------------------ private static final String ERROR_INVALID_PARENT = "EnableRestorableView must be a child of UIViewRoot. Encountered parent of type '%s'." + " It is recommended to enclose o:enableRestorableView in f:metadata."; // Constructors --------------------------------------------------------------------------------------------------- /** * The tag constructor. * @param config The tag config. */ public EnableRestorableView(TagConfig config) { super(config); setApplicationAttribute(EnableRestorableView.class.getName(), TRUE); } // Actions -------------------------------------------------------------------------------------------------------- /** * Enable the current view to be restorable. This basically sets a specific view attribute which the * {@link OmniViewHandler} could intercept on. * @throws IllegalStateException When given parent is not an instance of {@link UIViewRoot}. */ @Override public void apply(FaceletContext context, final UIComponent parent) throws IOException { if (!(parent instanceof UIViewRoot)) { throw new IllegalStateException( format(ERROR_INVALID_PARENT, parent != null ? parent.getClass().getName() : null)); } if (!ComponentHandler.isNew(parent)) { return; } parent.getAttributes().put(EnableRestorableView.class.getName(), TRUE); } // Helpers -------------------------------------------------------------------------------------------------------- /** * Returns true if given view is null, and this is a postback, and {@link EnableRestorableView} has been activated. * @param context The involved faces context. * @param view The involved view. * @return true if given view is null, and this is a postback, and {@link EnableRestorableView} has been activated. */ public static boolean isRestorableViewRequest(FacesContext context, UIViewRoot view) { return view == null && context.isPostback() && TRUE.equals(getApplicationAttribute(context, EnableRestorableView.class.getName())); } /** * Returns true if given view indeed contains {@link EnableRestorableView}. * @param view The involved view. * @return true if given view indeed contains {@link EnableRestorableView}. */ public static boolean isRestorableView(UIViewRoot view) { return TRUE.equals(view.getAttributes().get(EnableRestorableView.class.getName())); } }