/*
* 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.cdi;
import java.io.Serializable;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.UUID;
import javax.annotation.PreDestroy;
import javax.enterprise.context.NormalScope;
import javax.enterprise.util.Nonbinding;
import javax.faces.component.UIViewRoot;
import org.omnifaces.cdi.viewscope.ViewScopeContext;
import org.omnifaces.cdi.viewscope.ViewScopeEventListener;
import org.omnifaces.cdi.viewscope.ViewScopeExtension;
import org.omnifaces.cdi.viewscope.ViewScopeManager;
import org.omnifaces.cdi.viewscope.ViewScopeStorage;
import org.omnifaces.cdi.viewscope.ViewScopeStorageInSession;
import org.omnifaces.cdi.viewscope.ViewScopeStorageInViewState;
import org.omnifaces.context.OmniExternalContext;
import org.omnifaces.context.OmniExternalContextFactory;
import org.omnifaces.viewhandler.OmniViewHandler;
/**
* <p>
* The CDI view scope annotation, with more optimal handling of bean destroy as compared to standard JSF one.
* <p>
* In standard JSF 2.0/2.1, the <code>@</code>{@link PreDestroy} annotated method on a view scoped bean was never
* invoked when the session expires. Since OmniFaces 1.6, this CDI view scope annotation will guarantee that the
* <code>@PreDestroy</code> annotated method is also invoked on session expire. Since JSF 2.2, this problem is
* solved on native JSF view scoped beans, hereby making this annotation superflous in JSF 2.2.
* <p>
* However, there may be cases when it's desirable to immediately destroy a view scoped bean as well when the browser
* <code>unload</code> event is invoked. I.e. when the user navigates away by GET, or closes the browser tab/window.
* None of the both JSF 2.2 view scope annotations support this. Since OmniFaces 2.2, this CDI view scope annotation
* will guarantee that the <code>@PreDestroy</code> annotated method is also invoked on browser unload. This trick
* is done by a synchronous XHR request via an automatically included helper script <code>omnifaces:unload.js</code>.
* There's however a small caveat: on slow network and/or poor server hardware, there may be a noticeable lag between
* the enduser action of unloading the page and the desired result. If this is undesireable, then better stick to JSF
* 2.2's own view scope annotations and accept the postponed destroy.
* <p>
* Since OmniFaces 2.3, the unload has been further improved to also physically remove the associated JSF view state
* from JSF implementation's internal LRU map in case of server side state saving, hereby further decreasing the risk
* at <code>ViewExpiredException</code> on the other views which were created/opened earlier. As side effect of this
* change, the <code>@PreDestroy</code> annotated method of any standard JSF view scoped beans referenced in the
* same view as the OmniFaces CDI view scoped bean will also guaranteed be invoked on browser unload.
* <p>
* Since OmniFaces 2.6, this annotation got a new attribute: <code>saveInViewState</code>. When using client side state
* saving, this attribute can be set to <code>true</code> in order to force JSF to store whole view scoped bean
* instances annotated with this annotation in the JSF view state instead of in the HTTP session. For more detail, see
* the {@link #saveInViewState()}.
* <p>
* In a nutshell: if you're on JSF 2.0/2.1, and you can't upgrade to JSF 2.2, and you want the
* <code>@PreDestroy</code> to be invoked on sesison expire too, then use OmniFaces 1.6+ with this view scope
* annotation. Or, if you're on JSF 2.2 already, and you want the <code>@PreDestroy</code> to be invoked on browser
* unload too, then use OmniFaces 2.2+ with this view scope annotation. Or, if you want to store whole view scoped beans
* in the JSF view state when using client side state saving, then use OmniFaces 2.6+ with this view scope annotation
* and the <code>saveInViewState</code> attribute set to <code>true</code>.
* <p>
* Related JSF issues:
* <ul>
* <li><a href="https://java.net/jira/browse/JAVASERVERFACES-1351">Mojarra issue 1351</a>
* <li><a href="https://java.net/jira/browse/JAVASERVERFACES-1839">Mojarra issue 1839</a>
* <li><a href="https://java.net/jira/browse/JAVASERVERFACES_SPEC_PUBLIC-905">JSF spec issue 905</a>
* </ul>
*
* <h3>Usage</h3>
* <p>
* Just use it the usual way as all other CDI scopes. Watch out with IDE autocomplete on import that you don't
* accidentally import standard JSF's own one.
* <pre>
* import javax.inject.Named;
* import org.omnifaces.cdi.ViewScoped;
*
* @Named
* @ViewScoped
* public class OmniCDIViewScopedBean implements Serializable {}
* </pre>
* <p>
* Please note that the bean <strong>must</strong> implement {@link Serializable}, otherwise the CDI implementation
* will throw an exception about the bean not being passivation capable.
* <p>
* Under the covers, CDI managed beans with this scope are via {@link ViewScopeManager} by default stored in the session
* scope by an {@link UUID} based key which is referenced in JSF's own view map as available by
* {@link UIViewRoot#getViewMap()}. They are not stored in the JSF view state itself as that would be rather expensive
* in case of client side state saving.
* <p>
* In case you are using client side state saving by having the <code>javax.faces.STATE_SAVING_METHOD</code> context
* parameter set to <code>true</code> along with a valid <code>jsf/ClientSideSecretKey</code> in <code>web.xml</code>
* as below,
* <pre>
* <context-param>
* <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
* <param-value>client</param-value>
* </context-param>
* <env-entry>
* <env-entry-name>jsf/ClientSideSecretKey</env-entry-name>
* <env-entry-type>java.lang.String</env-entry-type>
* <env-entry-value><!-- See http://stackoverflow.com/q/35102645/157882 --></env-entry-value>
* </env-entry>
* </pre>
* <p>
* And you explicitly want to store the whole view scoped bean instance in the JSF view state, then set the annotation's
* <code>saveInViewState</code> attribute to <code>true</code>.
* <pre>
* import javax.inject.Named;
* import org.omnifaces.cdi.ViewScoped;
*
* @Named
* @ViewScoped(saveInViewState=true)
* public class OmniCDIViewScopedBean implements Serializable {}
* </pre>
* <p>
* It's very important that you understand that this setting has potentially a major impact in the size of the JSF view
* state, certainly when the view scoped bean instance holds "too much" data, such as a collection of entities for a
* data table, and that such beans will in fact <strong>never</strong> expire as they are stored entirely in the
* <code>javax.faces.ViewState</code> hidden input field in the HTML page. Moreover, the
* <code>@</code>{@link PreDestroy} annotated method on such bean will explicitly <strong>never</strong> be invoked,
* even not on an unload as it's quite possible to save or cache the page source and re-execute it at a (much) later
* moment.
* <p>
* It's therefore strongly recommended to use this setting only on a view scoped bean instance which is exclusively used
* to keep track of the dynamically controlled form state, such as <code>disabled</code>, <code>readonly</code> and
* <code>rendered</code> attributes which are controlled by ajax events.
* <p>
* This setting is NOT recommended when using server side state saving. It has basically no effect and it only adds
* unnecessary serialization overhead. The system will therefore throw an {@link IllegalStateException} on such
* condition.
*
* <h3>Configuration</h3>
* <p>
* By default, the maximum number of active view scopes is hold in a LRU map in HTTP session with a default size equal
* to the first non-null value of the following context parameters:
* <ul>
* <li>{@value org.omnifaces.cdi.viewscope.ViewScopeManager#PARAM_NAME_MAX_ACTIVE_VIEW_SCOPES} (OmniFaces)</li>
* <li>{@value org.omnifaces.cdi.viewscope.ViewScopeManager#PARAM_NAME_MOJARRA_NUMBER_OF_VIEWS} (Mojarra-specific)</li>
* <li>{@value org.omnifaces.cdi.viewscope.ViewScopeManager#PARAM_NAME_MYFACES_NUMBER_OF_VIEWS} (MyFaces-specific)</li>
* </ul>
* <p>If none of those context parameters are present, then a default size of
* {@value org.omnifaces.cdi.viewscope.ViewScopeManager#DEFAULT_MAX_ACTIVE_VIEW_SCOPES} will be used. When a view scoped
* bean is evicted from the LRU map, then its <code>@PreDestroy</code> will also guaranteed to be invoked.
* <p>
* This setting has no effect when <code>saveInViewState</code> attribute is set to <code>true</code>.
*
* <h3>Using window.onbeforeunload</h3>
* <p>
* If you have a custom <code>onbeforeunload</code> handler, then it's strongly recommended to use plain vanilla JS
* <code>window.onbeforeunload = function</code> instead of e.g. jQuery <code>$(window).on("beforeunload", function)</code>
* or DOM <code>window.addEventListener("beforeunload", function)</code> for this. This way the <code>@ViewScoped</code>
* unload can detect it and take it into account and continue to work properly. Otherwise the view scoped bean will
* still be destroyed in background even when the user cancels and decides to stay in the same page.
* <p>
* Below is a kickoff example how to properly register it, assuming jQuery is available, and that "stateless" forms
* and inputs (for which you don't want to trigger the unsaved data warning) have the class <code>stateless</code> set:
* <pre>
* $(document).on("change", "form:not(.stateless) :input:not(.stateless)", function() {
* $("body").data("unsavedchanges", true);
* });
* OmniFaces.Util.addSubmitListener(function() { // This hooks on Mojarra/MyFaces/PrimeFaces ajax submit events too.
* $("body").data("unsavedchanges", false);
* });
* window.onbeforeunload = function() {
* return $("body").data("unsavedchanges") ? "You have unsaved data. Are you sure you wish to leave this page?" : null;
* };
* </pre>
*
* @author Radu Creanga {@literal <rdcrng@gmail.com>}
* @author Bauke Scholtz
* @see ViewScopeExtension
* @see ViewScopeContext
* @see ViewScopeManager
* @see ViewScopeStorage
* @see ViewScopeStorageInSession
* @see ViewScopeStorageInViewState
* @see ViewScopeEventListener
* @see BeanStorage
* @see OmniViewHandler
* @see OmniExternalContext
* @see OmniExternalContextFactory
* @since 1.6
*/
@Inherited
@Documented
@NormalScope(passivating = true)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
public @interface ViewScoped {
/**
* <p>
* Sets whether to save the view scoped bean instance in JSF view state instead of in HTTP session. By default,
* concrete view scoped bean instances are saved in HTTP session, also when client side state saving is enabled.
* This means, when the HTTP session expires, then the view scoped bean instances will also implicitly expire and
* be newly recreated upon a request in a new session. This may be undesirable when using client side state saving
* as it's intuitively expected that concrete view scoped beans are also saved in JSF view state.
*
* @return Whether to save the view scoped bean instance in JSF view state instead of in HTTP session.
* @throws IllegalStateException When enabled while not using client side state saving.
* @since 2.6
*/
@Nonbinding boolean saveInViewState() default false;
}