All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jboss.seam.faces.component.UIViewAction Maven / Gradle / Ivy

/*
 * JBoss, Home of Professional Open Source
 * Copyright 2010, Red Hat, Inc., and individual contributors
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.seam.faces.component;

import javax.el.MethodExpression;
import javax.faces.FacesException;
import javax.faces.FactoryFinder;
import javax.faces.application.NavigationHandler;
import javax.faces.component.ActionSource2;
import javax.faces.component.FacesComponent;
import javax.faces.component.UICommand;
import javax.faces.component.UIComponent;
import javax.faces.component.UIComponentBase;
import javax.faces.component.UIViewParameter;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.faces.context.FacesContextWrapper;
import javax.faces.el.MethodBinding;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.ActionEvent;
import javax.faces.event.ActionListener;
import javax.faces.event.FacesEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PreRenderViewEvent;
import javax.faces.lifecycle.Lifecycle;
import javax.faces.lifecycle.LifecycleFactory;
import javax.faces.view.ViewMetadata;
import javax.faces.webapp.FacesServlet;

/**
 * 

* UIViewAction is an {@link ActionSource2} {@link UIComponent} * that specifies an application-specific command (or action)--defined as an EL * method expression--to be invoked during one of the JSF lifecycle phases that * proceeds view rendering. This component must be declared as a child of the * {@link ViewMetadata} facet of the {@link UIViewRoot} so that it gets * incorporated into the JSF lifecycle on both non-faces (initial) requests and * faces (postback) requests. *

* *

* The purpose of this component is to provide a light-weight front-controller * solution for executing code upon the loading of a JSF view to support the * integration of system services, content retrieval, view management, and * navigation. This functionality is especially useful for non-faces (initial) * requests. *

* *

* The {@link UIViewAction} component is closely tied to the * {@link UIViewParameter} component. The {@link UIViewParameter} component * binds a request parameter to a model property. Most of the time, this binding * is used to populate the model with data that supports the method being * invoked by a {@link UIViewAction} component, much like form inputs populate * the model with data to support the method being invoked by a * {@link UICommand} component. *

* *

* When the decode() method of the {@link UIViewAction} is * invoked, it will queue an {@link ActionEvent} to be broadcast to all * interested listeners when the broadcast() method is * invoked. *

* *

* If the value of the component's immediate attribute is * true, the action will be invoked during the Apply Request * Values JSF lifecycle phase. Otherwise, the action will be invoked during the * Invoke Application phase, the default behavior. The phase cannot be set * explicitly in the phase attribute, which takes precedence * over the immediate attribute. *

* *

* The invocation of the action is normally suppressed (meaning the * {@link ActionEvent} is not queued) on a faces request. It can be enabled by * setting the component's onPostback attribute to * true. Execution of the method can be subject to a required * condition for all requests by assigning an EL value expression of expected * type boolean to the component's if attribute, which must * evaluate to true for the action to be invoked. *

* *

* The {@link NavigationHandler} is consulted after the action is invoked to * carry out the navigation case that matches the action signature and outcome. * If a navigation case is matched, or the response is marked complete by the * action, subsequent {@link UIViewAction} components associated with the * current view are short-circuited. The lifecycle then advances appropriately. *

* *

* It's important to note that the full component tree is not built before the * UIViewAction components are processed on an non-faces (initial) request. * Rather, the component tree only contains the {@link ViewMetadata}, an * important part of the optimization of this component and what sets it apart * from a {@link PreRenderViewEvent} listener. *

* * @author Dan Allen * @author Andy Schwartz * * @see UIViewParameter */ @FacesComponent( // tagName = "viewAction", // namespace = "http://jboss.org/seam/faces", // (see // https://javaserverfaces-spec-public.dev.java.net/issues/show_bug.cgi?id=594) value = UIViewAction.COMPONENT_TYPE) public class UIViewAction extends UIComponentBase implements ActionSource2 { // ------------------------------------------------------ Manifest Constants /** *

* The standard component type for this component. *

*/ public static final String COMPONENT_TYPE = "org.jboss.seam.faces.ViewAction"; /** *

* The standard component family for this component. *

*/ public static final String COMPONENT_FAMILY = "org.jboss.seam.faces.ViewAction"; /** * Properties that are tracked by state saving. */ enum PropertyKeys { onPostback, actionExpression, immediate, phase, ifAttr("if"); private String name; PropertyKeys() { } PropertyKeys(final String name) { this.name = name; } @Override public String toString() { return name != null ? name : super.toString(); } } // ------------------------------------------------------------ Constructors /** *

* Create a new {@link UIViewAction} instance with default property values. *

*/ public UIViewAction() { super(); setRendererType(null); } // -------------------------------------------------------------- Properties @Override public String getFamily() { return COMPONENT_FAMILY; } /** * {@inheritDoc} * * @deprecated This has been replaced by {@link #getActionExpression}. */ @Deprecated public MethodBinding getAction() { MethodBinding result = null; MethodExpression me; if (null != (me = getActionExpression())) { result = new MethodBindingMethodExpressionAdapter(me); } return result; } /** * {@inheritDoc} * * @deprecated This has been replaced by * {@link #setActionExpression(javax.el.MethodExpression)}. * @throws UnsupportedOperationException if called */ @Deprecated public void setAction(final MethodBinding action) { throw new UnsupportedOperationException("Not supported."); } /** * Action listeners are not supported by the {@link UIViewAction} component. * * @throws UnsupportedOperationException if called */ @SuppressWarnings("deprecation") public MethodBinding getActionListener() { throw new UnsupportedOperationException("Not supported."); } /** * Action listeners are not supported by the {@link UIViewAction} component. * * @throws UnsupportedOperationException if called */ @SuppressWarnings("deprecation") public void setActionListener(final MethodBinding actionListener) { throw new UnsupportedOperationException("Not supported."); } /** * Returns the value which dictates the JSF lifecycle phase in which the * action is invoked. If the value of this attribute is * true, the action will be invoked in the Apply Request * Values phase. If the value of this attribute is false, * the default, the action will be invoked in the Invoke Application Phase. */ public boolean isImmediate() { return (Boolean) getStateHelper().eval(PropertyKeys.immediate, false); } /** * Sets the immediate flag, which controls the JSF lifecycle in which the * action is invoked. */ public void setImmediate(final boolean immediate) { getStateHelper().put(PropertyKeys.immediate, immediate); } /** *

* Returns the name of the phase in which the action is to be queued. Only * the following phases are supported (case does not matter): *

*
    *
  • APPLY_REQUEST_VALUES
  • *
  • PROCESS_VALIDATIONS
  • *
  • UPDATE_MODEL_VALUES
  • *
  • INVOKE_APPLICATION
  • *
*

* If the phase is set, it takes precedence over the immediate flag. *

*/ public String getPhase() { String phase = (String) getStateHelper().eval(PropertyKeys.phase); if (phase != null) { phase = phase.toUpperCase(); } return phase; } /** * Set the name of the phase in which the action is to be queued. */ public void setPhase(final String phase) { getStateHelper().put(PropertyKeys.phase, phase); } public PhaseId getPhaseId() { String phase = getPhase(); if (phase == null) { return null; } if ("APPLY_REQUEST_VALUES".equals(phase)) { return PhaseId.APPLY_REQUEST_VALUES; } else if ("PROCESS_VALIDATIONS".equals(phase)) { return PhaseId.PROCESS_VALIDATIONS; } else if ("UPDATE_MODEL_VALUES".equals(phase)) { return PhaseId.UPDATE_MODEL_VALUES; } else if ("INVOKE_APPLICATION".equals(phase)) { return PhaseId.INVOKE_APPLICATION; } else if ("ANY_PHASE".equals(phase) || "RESTORE_VIEW".equals(phase) || "RENDER_REPONSE".equals(phase)) { throw new FacesException("View actions cannot be executed in specified phase: [" + phase + "]"); } else { throw new FacesException("Not a valid phase [" + phase + "]"); } } /** * Action listeners are not supported by the {@link UIViewAction} component. * * @throws UnsupportedOperationException if called */ public void addActionListener(final ActionListener listener) { throw new UnsupportedOperationException("Not supported."); } /** * Action listeners are not supported by the {@link UIViewAction} component. */ public ActionListener[] getActionListeners() { return new ActionListener[0]; } /** * Action listeners are not supported by the {@link UIViewAction} component. * * @throws UnsupportedOperationException if called */ public void removeActionListener(final ActionListener listener) { throw new UnsupportedOperationException("Not supported."); } /** * Returns the action, represented as an EL method expression, to invoke. */ public MethodExpression getActionExpression() { return (MethodExpression) getStateHelper().get(PropertyKeys.actionExpression); } /** * Sets the action, represented as an EL method expression, to invoke. */ public void setActionExpression(final MethodExpression actionExpression) { getStateHelper().put(PropertyKeys.actionExpression, actionExpression); } /** * Returns a boolean value that controls whether the action is invoked during * faces (postback) request. The default is false. */ public boolean isOnPostback() { return (Boolean) getStateHelper().eval(PropertyKeys.onPostback, false); } /** * Set the bookean flag that controls whether the action is invoked during a * faces (postback) request. */ public void setOnPostback(final boolean onPostback) { getStateHelper().put(PropertyKeys.onPostback, onPostback); } /** * Returns a condition, represented as an EL value expression, that must * evaluate to true for the action to be invoked. */ public boolean isIf() { return (Boolean) getStateHelper().eval(PropertyKeys.ifAttr, true); } /** * Sets the condition, represented as an EL value expression, that must * evaluate to true for the action to be invoked. */ public void setIf(final boolean condition) { getStateHelper().put(PropertyKeys.ifAttr, condition); } // ----------------------------------------------------- UIComponent Methods /** *

* In addition to to the default {@link UIComponent#broadcast} processing, * pass the {@link ActionEvent} being broadcast to the default * {@link ActionListener} registered on the * {@link javax.faces.application.Application}. *

* * @param event {@link FacesEvent} to be broadcast * * @throws AbortProcessingException Signal the JavaServer Faces * implementation that no further processing on the current event * should be performed * @throws IllegalArgumentException if the implementation class of this * {@link FacesEvent} is not supported by this component * @throws NullPointerException if event is null */ @Override public void broadcast(final FacesEvent event) throws AbortProcessingException { super.broadcast(event); FacesContext context = getFacesContext(); // OPEN QUESTION: should we consider a navigation to the same view as a // no-op navigation? // only proceed if the response has not been marked complete and // navigation to another view has not occurred if ((event instanceof ActionEvent) && !context.getResponseComplete() && (context.getViewRoot() == getViewRootOf(event))) { ActionListener listener = context.getApplication().getActionListener(); if (listener != null) { UIViewRoot viewRootBefore = context.getViewRoot(); InstrumentedFacesContext instrumentedContext = new InstrumentedFacesContext(context); // defer the call to renderResponse() that happens in // ActionListener#processAction(ActionEvent) instrumentedContext.disableRenderResponseControl().set(); listener.processAction((ActionEvent) event); instrumentedContext.restore(); // if the response is marked complete, the story is over if (!context.getResponseComplete()) { UIViewRoot viewRootAfter = context.getViewRoot(); // if the view id changed as a result of navigation, then execute // the JSF lifecycle for the new view id if (viewRootBefore != viewRootAfter) { /* * // execute the JSF lifecycle by dispatching a forward * request // this approach is problematic because it throws a * wrench in the event broadcasting try { * context.getExternalContext * ().dispatch(context.getApplication() * .getViewHandler().getActionURL(context, * viewRootAfter.getViewId()) * .substring(context.getExternalContext * ().getRequestContextPath().length())); // kill this * lifecycle execution context.responseComplete(); } catch * (IOException e) { throw new * FacesException("Dispatch to viewId failed: " + * viewRootAfter.getViewId(), e); } */ // manually execute the JSF lifecycle on the new view id // certain tweaks have to be made to the FacesContext to allow // us to reset the lifecycle Lifecycle lifecycle = getLifecycle(context); instrumentedContext = new InstrumentedFacesContext(context); instrumentedContext.pushViewIntoRequestMap().clearViewRoot().clearPostback().set(); lifecycle.execute(instrumentedContext); instrumentedContext.restore(); /* * Another approach would be to register a phase listener in * the decode() method for the phase in which the action is * set to invoke. The phase listener would performs a servlet * forward if a non-redirect navigation occurs after the * phase. */ } else { // apply the deferred call (relevant when immediate is true) context.renderResponse(); } } } } } /** * First, determine if the action should be invoked by evaluating this * components pre-conditions. If this is a faces (postback) request and the * evaluated value of the postback attribute is false, take no action. If the * evaluated value of the if attribute is false, take no action. If both * conditions pass, proceed with creating an {@link ActionEvent}. * * Set the phaseId in which the queued {@link ActionEvent} should be * broadcast by assigning the appropriate value to the phaseId property of * the {@link ActionEvent} according to the evaluated value of the immediate * attribute. If the value is true, set the phaseId to * {@link PhaseId#APPLY_REQUEST_VALUES}. Otherwise, set the phaseId to to * {@link PhaseId#INVOKE_APPLICATION}. * * Finally, queue the event by calling queueEvent() and * passing the {@link ActionEvent} just created. */ @Override public void decode(final FacesContext context) { if (context == null) { throw new NullPointerException(); } if ((context.isPostback() && !isOnPostback()) || !isIf()) { return; } ActionEvent e = new ActionEvent(this); PhaseId phaseId = getPhaseId(); if (phaseId != null) { e.setPhaseId(phaseId); } else if (isImmediate()) { e.setPhaseId(PhaseId.APPLY_REQUEST_VALUES); } else { e.setPhaseId(PhaseId.INVOKE_APPLICATION); } queueEvent(e); } private UIViewRoot getViewRootOf(final FacesEvent e) { UIComponent c = e.getComponent(); do { if (c instanceof UIViewRoot) { return (UIViewRoot) c; } c = c.getParent(); } while (c != null); return null; } private Lifecycle getLifecycle(final FacesContext context) { LifecycleFactory lifecycleFactory = (LifecycleFactory) FactoryFinder.getFactory(FactoryFinder.LIFECYCLE_FACTORY); String lifecycleId = context.getExternalContext().getInitParameter(FacesServlet.LIFECYCLE_ID_ATTR); if (lifecycleId == null) { lifecycleId = LifecycleFactory.DEFAULT_LIFECYCLE; } return lifecycleFactory.getLifecycle(lifecycleId); } /** * A FacesContext delegator that gives us the necessary controls over the * FacesContext to allow the execution of the lifecycle to accomodate the * UIViewAction sequence. */ private class InstrumentedFacesContext extends FacesContextWrapper { private final FacesContext wrapped; private boolean viewRootCleared = false; private boolean renderedResponseControlDisabled = false; private Boolean postback = null; public InstrumentedFacesContext(final FacesContext wrapped) { this.wrapped = wrapped; } @Override public FacesContext getWrapped() { return wrapped; } @Override public UIViewRoot getViewRoot() { if (viewRootCleared) { return null; } return wrapped.getViewRoot(); } @Override public void setViewRoot(final UIViewRoot viewRoot) { viewRootCleared = false; wrapped.setViewRoot(viewRoot); } @Override public boolean isPostback() { return postback == null ? wrapped.isPostback() : postback; } @Override public void renderResponse() { if (!renderedResponseControlDisabled) { wrapped.renderResponse(); } } /** * Make it look like we have dispatched a request using the include * method. */ public InstrumentedFacesContext pushViewIntoRequestMap() { getExternalContext().getRequestMap().put("javax.servlet.include.servlet_path", wrapped.getViewRoot().getViewId()); return this; } public InstrumentedFacesContext clearPostback() { postback = false; return this; } public InstrumentedFacesContext clearViewRoot() { viewRootCleared = true; return this; } public InstrumentedFacesContext disableRenderResponseControl() { renderedResponseControlDisabled = true; return this; } public void set() { setCurrentInstance(this); } public void restore() { setCurrentInstance(wrapped); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy