You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by lu...@apache.org on 2013/01/15 21:22:32 UTC

svn commit: r1433615 - in /myfaces/core/branches/2.2.x: api/src/main/java/javax/faces/component/ api/src/main/java/javax/faces/event/ impl/src/main/java/org/apache/myfaces/application/ impl/src/main/java/org/apache/myfaces/view/facelets/tag/jsf/core/

Author: lu4242
Date: Tue Jan 15 20:22:32 2013
New Revision: 1433615

URL: http://svn.apache.org/viewvc?rev=1433615&view=rev
Log:
MYFACES-3674 Implement f:viewAction 

Added:
    myfaces/core/branches/2.2.x/api/src/main/java/javax/faces/component/UIViewAction.java   (with props)
Modified:
    myfaces/core/branches/2.2.x/api/src/main/java/javax/faces/event/PhaseId.java
    myfaces/core/branches/2.2.x/impl/src/main/java/org/apache/myfaces/application/NavigationHandlerImpl.java
    myfaces/core/branches/2.2.x/impl/src/main/java/org/apache/myfaces/view/facelets/tag/jsf/core/CoreLibrary.java

Added: myfaces/core/branches/2.2.x/api/src/main/java/javax/faces/component/UIViewAction.java
URL: http://svn.apache.org/viewvc/myfaces/core/branches/2.2.x/api/src/main/java/javax/faces/component/UIViewAction.java?rev=1433615&view=auto
==============================================================================
--- myfaces/core/branches/2.2.x/api/src/main/java/javax/faces/component/UIViewAction.java (added)
+++ myfaces/core/branches/2.2.x/api/src/main/java/javax/faces/component/UIViewAction.java Tue Jan 15 20:22:32 2013
@@ -0,0 +1,401 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 javax.faces.component;
+
+import javax.el.MethodExpression;
+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 org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFComponent;
+import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFListener;
+import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFProperty;
+
+/**
+ * 
+ * @author Leonardo Uribe
+ * @since 2.2
+ */
+@JSFComponent(name = "f:viewAction")
+public class UIViewAction extends UIComponentBase implements ActionSource2
+{
+    //private static final Logger log = Logger.getLogger(UIViewAction.class.getName());
+    public static final String COMPONENT_FAMILY = "javax.faces.ViewAction";
+    public static final String COMPONENT_TYPE = "javax.faces.ViewAction";
+    
+    /**
+     * Key in facesContext attribute map to check if a viewAction broadcast is 
+     * being processed. This is used to check when a JSF lifecycle restart is required
+     * by the NavigationHandler implementation.
+     */
+    private static final String BROADCAST_PROCESSING_KEY = "oam.viewAction.broadcast";
+    
+    /**
+     * Key in facesContext attribute map to count the number of viewAction events that 
+     * remains to be processed.
+     */
+    private static final String EVENT_COUNT_KEY = "oam.viewAction.eventCount";
+
+    public UIViewAction()
+    {
+        setRendererType(null);
+    }
+
+    @Override
+    public void broadcast(FacesEvent event) throws AbortProcessingException
+    {
+        super.broadcast(event);
+        
+        FacesContext context = getFacesContext();
+        
+        if (context.getResponseComplete())
+        {
+            return;
+        }
+        
+        UIComponent c = event.getComponent();
+        UIViewRoot sourceViewRoot = null;
+        do
+        {
+            if (c instanceof UIViewRoot)
+            {
+                sourceViewRoot = (UIViewRoot) c;
+                break;
+            }
+            else
+            {
+                c = c.getParent();
+            }
+        } while (c != null);
+        
+        if (!context.getViewRoot().equals(sourceViewRoot))
+        {
+            return;
+        }
+        
+        if (event instanceof ActionEvent)
+        {
+            ActionListener defaultActionListener = context.getApplication().getActionListener();
+            if (defaultActionListener != null)
+            {
+                String  viewIdBeforeAction = context.getViewRoot().getViewId();
+                Boolean oldBroadcastProcessing = (Boolean) context.getAttributes().
+                    get(BROADCAST_PROCESSING_KEY);
+                try
+                {
+                    context.getAttributes().put(BROADCAST_PROCESSING_KEY, Boolean.TRUE);
+
+                    ViewActionFacesContextWrapper wrappedFacesContext = new ViewActionFacesContextWrapper(context);
+
+                    try
+                    {
+                        wrappedFacesContext.setWrapperAsCurrentFacesContext();
+
+                        /* Note f:viewAction does not have actionListener property defined.
+                        MethodBinding mb = getActionListener();
+                        if (mb != null)
+                        {
+                            mb.invoke(context, new Object[]
+                            { event });
+                        }*/
+
+                        if (defaultActionListener != null)
+                        {
+                            defaultActionListener.processAction((ActionEvent) event);
+                        }
+                        
+                        // Decrement count
+                        Integer count = (Integer) context.getAttributes().get(EVENT_COUNT_KEY);
+                        count = (count == null) ? 0 : count - 1;
+                        context.getAttributes().put(EVENT_COUNT_KEY, count);
+                    }
+                    finally
+                    {
+                        wrappedFacesContext.restoreCurrentFacesContext();
+                    }
+                }
+                finally
+                {
+                    context.getAttributes().put(BROADCAST_PROCESSING_KEY, 
+                        oldBroadcastProcessing == null ? Boolean.FALSE : oldBroadcastProcessing);
+                }
+
+                if (context.getResponseComplete())
+                {
+                    return;
+                }
+                else
+                {
+                    Integer count = (Integer) context.getAttributes().get(EVENT_COUNT_KEY);
+                    count = (count == null) ? 0 : count;
+                    String viewIdAfterAction = context.getViewRoot().getViewId();
+
+                    if (viewIdBeforeAction.equals(viewIdAfterAction) && count == 0)
+                    {
+                        context.renderResponse();
+                    }
+                    // "... Otherwise, execute the lifecycle on the new UIViewRoot ..."
+                    // Note these words are implemented in the NavigationHandler, but 
+                    // the original proposal from seam s:viewAction did a trick here 
+                    // to restart the JSF lifecycle.
+                }
+            }
+        }
+    }
+
+    @Override
+    public void decode(FacesContext context)
+    {
+        super.decode(context);
+        
+        if (context.isPostback() && !isOnPostback())
+        {
+            return;
+        }
+        
+        if (!isRendered())
+        {
+            return;
+        }
+        
+        ActionEvent evt = new ActionEvent(this);
+        String phase = getPhase();
+        PhaseId phaseId = (phase != null) ? PhaseId.phaseIdValueOf(phase) :
+            isImmediate() ? PhaseId.APPLY_REQUEST_VALUES : PhaseId.INVOKE_APPLICATION;
+        evt.setPhaseId(phaseId);
+        this.queueEvent(evt);
+        
+        // "... Keep track of the number of events that are queued in this way 
+        // on this run through the lifecycle. ...". The are two options:
+        // 1. Use an attribute over FacesContext attribute map
+        // 2. Use an attribute over the component
+        // If the view is recreated again with the same viewId, the component 
+        // state get lost, so the option 1 is preferred.
+        Integer count = (Integer) context.getAttributes().get(EVENT_COUNT_KEY);
+        count = (count == null) ? 1 : count + 1;
+        context.getAttributes().put(EVENT_COUNT_KEY, count);
+    }
+
+    @Override
+    public void queueEvent(FacesEvent event)
+    {
+        if (event != null && event instanceof ActionEvent)
+        {
+            UIComponent component = event.getComponent();
+            if (component instanceof ActionSource)
+            {
+                if (((ActionSource)component).isImmediate())
+                {
+                    event.setPhaseId(PhaseId.APPLY_REQUEST_VALUES);
+                }
+                else
+                {
+                    event.setPhaseId(PhaseId.INVOKE_APPLICATION);
+                }
+            }
+        }
+        super.queueEvent(event);
+    }
+    
+    public MethodBinding getAction()
+    {
+        MethodExpression actionExpression = getActionExpression();
+        if (actionExpression instanceof _MethodBindingToMethodExpression)
+        {
+            return ((_MethodBindingToMethodExpression) actionExpression)
+                    .getMethodBinding();
+        }
+        if (actionExpression != null)
+        {
+            return new _MethodExpressionToMethodBinding(actionExpression);
+        }
+        return null;
+    }
+
+    /**
+     * @deprecated Use setActionExpression instead.
+     */
+    public void setAction(MethodBinding action)
+    {
+        if (action != null)
+        {
+            setActionExpression(new _MethodBindingToMethodExpression(action));
+        }
+        else
+        {
+            setActionExpression(null);
+        }
+    }
+
+    @JSFProperty
+    public boolean isImmediate()
+    {
+        return (Boolean) getStateHelper().eval(PropertyKeys.immediate, Boolean.FALSE);
+    }
+
+    public void setImmediate(boolean immediate)
+    {
+        getStateHelper().put(PropertyKeys.immediate, immediate );
+    }
+
+    @JSFProperty
+    public Object getValue()
+    {
+        return  getStateHelper().eval(PropertyKeys.value);
+    }
+
+    public void setValue(Object value)
+    {
+        getStateHelper().put(PropertyKeys.value, value );
+    }
+
+    @JSFProperty(stateHolder=true, returnSignature = "java.lang.Object", jspName = "action", clientEvent="action")
+    public MethodExpression getActionExpression()
+    {
+        return (MethodExpression) getStateHelper().eval(PropertyKeys.actionExpression);
+    }
+
+    public void setActionExpression(MethodExpression actionExpression)
+    {
+        getStateHelper().put(PropertyKeys.actionExpression, actionExpression);
+    }
+
+    //@JSFProperty(stateHolder=true, returnSignature = "void", methodSignature = "javax.faces.event.ActionEvent")
+    public MethodBinding getActionListener()
+    {
+        return (MethodBinding) getStateHelper().eval(PropertyKeys.actionListener);
+        // Note f:viewAction does not have actionListener property defined.
+        //throw new UnsupportedOperationException();
+    }
+
+    /**
+     * @deprecated
+     */
+    //@JSFProperty(returnSignature="void",methodSignature="javax.faces.event.ActionEvent")
+    public void setActionListener(MethodBinding actionListener)
+    {
+        getStateHelper().put(PropertyKeys.actionListener, actionListener);
+        // Note f:viewAction does not have actionListener property defined.
+        //throw new UnsupportedOperationException();
+    }
+
+    public void addActionListener(ActionListener listener)
+    {
+        addFacesListener(listener);
+    }
+
+    public void removeActionListener(ActionListener listener)
+    {
+        removeFacesListener(listener);
+    }
+
+    @JSFListener(event="javax.faces.event.ActionEvent",
+            phases="Invoke Application, Apply Request Values")
+    public ActionListener[] getActionListeners()
+    {
+        return (ActionListener[]) getFacesListeners(ActionListener.class);
+    }
+    
+    @JSFProperty
+    public String getPhase()
+    {
+        return (String) getStateHelper().get(PropertyKeys.phase);
+    }
+    
+    public void setPhase(String phase)
+    {
+        getStateHelper().put(PropertyKeys.phase, phase);
+    }
+    
+    @JSFProperty
+    public boolean isOnPostback()
+    {
+        return (Boolean) getStateHelper().eval(PropertyKeys.onPostback, Boolean.FALSE);
+    }
+
+    public void setOnPostback(boolean onPostback)
+    {
+        getStateHelper().put(PropertyKeys.onPostback, onPostback );
+    }    
+    
+    public static boolean isProcessingBroadcast(FacesContext context)
+    {
+        return Boolean.TRUE.equals(context.getAttributes().get(BROADCAST_PROCESSING_KEY));
+    }
+
+    enum PropertyKeys
+    {
+         immediate
+        , value
+        , actionExpression
+        , actionListener
+        , phase
+        , onPostback
+    }
+
+    @Override
+    public String getFamily()
+    {
+        return COMPONENT_FAMILY;
+    }
+    
+    private static class ViewActionFacesContextWrapper extends FacesContextWrapper
+    {
+        private FacesContext delegate;
+        private boolean renderResponseCalled;
+
+        public ViewActionFacesContextWrapper(FacesContext delegate)
+        {
+            this.delegate = delegate;
+            this.renderResponseCalled = false;
+        }
+
+        @Override
+        public void renderResponse()
+        {
+            //Take no action
+            renderResponseCalled = true;
+        }
+        
+        public boolean isRenderResponseCalled()
+        {
+            return renderResponseCalled;
+        }
+        
+        @Override
+        public FacesContext getWrapped()
+        {
+            return delegate;
+        }
+        
+        void setWrapperAsCurrentFacesContext()
+        {
+            setCurrentInstance(this);
+        }
+        
+        void restoreCurrentFacesContext()
+        {
+            setCurrentInstance(delegate);
+        }
+    }
+}

Propchange: myfaces/core/branches/2.2.x/api/src/main/java/javax/faces/component/UIViewAction.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: myfaces/core/branches/2.2.x/api/src/main/java/javax/faces/event/PhaseId.java
URL: http://svn.apache.org/viewvc/myfaces/core/branches/2.2.x/api/src/main/java/javax/faces/event/PhaseId.java?rev=1433615&r1=1433614&r2=1433615&view=diff
==============================================================================
--- myfaces/core/branches/2.2.x/api/src/main/java/javax/faces/event/PhaseId.java (original)
+++ myfaces/core/branches/2.2.x/api/src/main/java/javax/faces/event/PhaseId.java Tue Jan 15 20:22:32 2013
@@ -21,6 +21,7 @@ package javax.faces.event;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import javax.faces.FacesException;
 
 /**
  * see Javadoc of <a href="http://java.sun.com/javaee/javaserverfaces/1.2/docs/api/index.html">JSF Specification</a>
@@ -88,5 +89,29 @@ public class PhaseId implements Comparab
     {
         return _name + "(" + _ordinal + ")";
     }
+    
+    /*
+     * @since 2.2
+     */
+    public String getName()
+    {
+        return this._name;
+    }
 
+    public static PhaseId phaseIdValueOf(String phase)
+    {
+        if (phase == null)
+        {
+            throw new NullPointerException("phase");
+        }
+        for (int i = 0; i < VALUES.size(); i++)
+        {
+            PhaseId phaseId = VALUES.get(i);
+            if (phaseId.getName().equals(phase))
+            {
+                return phaseId;
+            }
+        }
+        throw new FacesException("Phase "+phase+" is invalid");
+    }
 }

Modified: myfaces/core/branches/2.2.x/impl/src/main/java/org/apache/myfaces/application/NavigationHandlerImpl.java
URL: http://svn.apache.org/viewvc/myfaces/core/branches/2.2.x/impl/src/main/java/org/apache/myfaces/application/NavigationHandlerImpl.java?rev=1433615&r1=1433614&r2=1433615&view=diff
==============================================================================
--- myfaces/core/branches/2.2.x/impl/src/main/java/org/apache/myfaces/application/NavigationHandlerImpl.java (original)
+++ myfaces/core/branches/2.2.x/impl/src/main/java/org/apache/myfaces/application/NavigationHandlerImpl.java Tue Jan 15 20:22:32 2013
@@ -39,6 +39,7 @@ import javax.faces.application.Navigatio
 import javax.faces.application.ProjectStage;
 import javax.faces.application.ViewHandler;
 import javax.faces.component.UIComponent;
+import javax.faces.component.UIViewAction;
 import javax.faces.component.UIViewRoot;
 import javax.faces.component.visit.VisitCallback;
 import javax.faces.component.visit.VisitContext;
@@ -103,7 +104,30 @@ public class NavigationHandlerImpl
                           " toViewId =" + navigationCase.getToViewId(facesContext) +
                           " redirect=" + navigationCase.isRedirect());
             }
-            if (navigationCase.isRedirect())
+            boolean isViewActionProcessingBroadcastAndRequiresRedirect = false;
+            if (UIViewAction.isProcessingBroadcast(facesContext))
+            {
+                // f:viewAction tag always triggers a redirect to enforce execution of 
+                // the lifecycle again. Note this requires enables flash scope 
+                // keepMessages automatically, because a view action can add messages
+                // and these ones requires to be renderer afterwards.
+                facesContext.getExternalContext().getFlash().setKeepMessages(true);
+                String fromViewId = (facesContext.getViewRoot() == null) ? null :
+                    facesContext.getViewRoot().getViewId();
+                String toViewId = navigationCase.getToViewId(facesContext);
+                // A redirect is required only if the viewId changes. If the viewId
+                // does not change, section 7.4.2 says that a redirect/restart JSF
+                // lifecycle is not necessary.
+                if (fromViewId == null && toViewId != null)
+                {
+                    isViewActionProcessingBroadcastAndRequiresRedirect = true;
+                }
+                else if (fromViewId != null && !fromViewId.equals(toViewId))
+                {
+                    isViewActionProcessingBroadcastAndRequiresRedirect = true;
+                }
+            }
+            if (navigationCase.isRedirect() || isViewActionProcessingBroadcastAndRequiresRedirect)
             { 
                 //&& (!PortletUtil.isPortletRequest(facesContext)))
                 // Spec section 7.4.2 says "redirects not possible" in this case for portlets

Modified: myfaces/core/branches/2.2.x/impl/src/main/java/org/apache/myfaces/view/facelets/tag/jsf/core/CoreLibrary.java
URL: http://svn.apache.org/viewvc/myfaces/core/branches/2.2.x/impl/src/main/java/org/apache/myfaces/view/facelets/tag/jsf/core/CoreLibrary.java?rev=1433615&r1=1433614&r2=1433615&view=diff
==============================================================================
--- myfaces/core/branches/2.2.x/impl/src/main/java/org/apache/myfaces/view/facelets/tag/jsf/core/CoreLibrary.java (original)
+++ myfaces/core/branches/2.2.x/impl/src/main/java/org/apache/myfaces/view/facelets/tag/jsf/core/CoreLibrary.java Tue Jan 15 20:22:32 2013
@@ -21,6 +21,7 @@ package org.apache.myfaces.view.facelets
 import javax.faces.component.UIParameter;
 import javax.faces.component.UISelectItem;
 import javax.faces.component.UISelectItems;
+import javax.faces.component.UIViewAction;
 import javax.faces.component.UIViewParameter;
 import javax.faces.convert.DateTimeConverter;
 import javax.faces.convert.NumberConverter;
@@ -101,6 +102,8 @@ public final class CoreLibrary extends A
 
         this.addTagHandler("view", ViewHandler.class);
         
+        this.addComponent("viewAction", UIViewAction.COMPONENT_TYPE, null);
+        
         this.addComponent("viewParam", UIViewParameter.COMPONENT_TYPE, null);
 
         this.addComponent("verbatim", "javax.faces.HtmlOutputText", "javax.faces.Text", VerbatimHandler.class);