[seam-commits] Seam SVN: r11052 - in modules/trunk/faces/src/main: java/org/jboss/seam/faces/component and 3 other directories.

seam-commits at lists.jboss.org seam-commits at lists.jboss.org
Sat May 30 01:55:58 EDT 2009


Author: dan.j.allen
Date: 2009-05-30 01:55:58 -0400 (Sat, 30 May 2009)
New Revision: 11052

Added:
   modules/trunk/faces/src/main/java/org/jboss/seam/faces/component/UIImport.java
   modules/trunk/faces/src/main/java/org/jboss/seam/faces/lifecycle/AbstractViewMetadataProcesssor.java
   modules/trunk/faces/src/main/java/org/jboss/seam/faces/lifecycle/ConvertStatusMessagesProcessor.java
   modules/trunk/faces/src/main/java/org/jboss/seam/faces/lifecycle/EnforceViewRestrictionsProcessor.java
   modules/trunk/faces/src/main/java/org/jboss/seam/faces/lifecycle/FacesSystemEventProcessor.java
   modules/trunk/faces/src/main/java/org/jboss/seam/faces/lifecycle/ImportNamespacesProcessor.java
   modules/trunk/faces/src/main/java/org/jboss/seam/faces/lifecycle/SeamPreRenderViewListener.java
Removed:
   modules/trunk/faces/src/main/java/org/jboss/seam/faces/lifecycle/ConvertStatusMessagesListener.java
Modified:
   modules/trunk/faces/src/main/java/org/jboss/seam/faces/application/SeamViewHandler.java
   modules/trunk/faces/src/main/java/org/jboss/seam/faces/component/UIRestrictView.java
   modules/trunk/faces/src/main/java/org/jboss/seam/faces/component/UIViewAction.java
   modules/trunk/faces/src/main/java/org/jboss/seam/faces/el/SeamFacesELResolver.java
   modules/trunk/faces/src/main/java/org/jboss/seam/faces/lifecycle/ExecuteViewActionsListener.java
   modules/trunk/faces/src/main/resources/META-INF/faces-config.xml
   modules/trunk/faces/src/main/resources/META-INF/seam-faces.taglib.xml
Log:
refactor all PreRenderViewEvent listeners into a processing chain
listeners are now proper contextual beans
implement the <s:import> tag to import EL namespaces for the SeamFacesELResolver


Modified: modules/trunk/faces/src/main/java/org/jboss/seam/faces/application/SeamViewHandler.java
===================================================================
--- modules/trunk/faces/src/main/java/org/jboss/seam/faces/application/SeamViewHandler.java	2009-05-30 05:53:53 UTC (rev 11051)
+++ modules/trunk/faces/src/main/java/org/jboss/seam/faces/application/SeamViewHandler.java	2009-05-30 05:55:58 UTC (rev 11052)
@@ -2,11 +2,14 @@
 
 import java.util.List;
 import java.util.Map;
+
 import javax.faces.application.ViewHandler;
 import javax.faces.application.ViewHandlerWrapper;
 import javax.faces.context.FacesContext;
-import org.jboss.seam.faces.lifecycle.ConvertStatusMessagesListener;
 
+import org.jboss.seam.bridge.ManagerBridge;
+import org.jboss.seam.faces.lifecycle.ConvertStatusMessagesProcessor;
+
 /**
  * Wrap the standard JSF view handler to capture the
  * request for a redirect URL so that the JSF messages
@@ -34,7 +37,7 @@
     * Take this opportunity to store the JSF messages in the flash scope so that they
     * live past the redirect.
     *
-    * @see ViewHandler#getRedirectURL(javax.faces.context.FacesContext, java.lang.String, java.util.Map, boolean) 
+    * @see ViewHandler#getRedirectURL(javax.faces.context.FacesContext, java.lang.String, java.util.Map, boolean)
     */
    @Override
    public String getRedirectURL(FacesContext context, String viewId, Map<String, List<String>> parameters, boolean includeViewParams)
@@ -43,7 +46,7 @@
       if (context.getExternalContext().getSession(false) != null)
       {
          // QUESTION hmmm, we have to convert to faces messages now to leverage JSF's flash feature...I suppose that is okay
-         new ConvertStatusMessagesListener().execute();
+         ManagerBridge.getProvider().getCurrentManager().getInstanceByType(ConvertStatusMessagesProcessor.class).execute();
          // should I move this next step into TransferStatusMessagesListener?
          if (context.getMessages().hasNext())
          {

Added: modules/trunk/faces/src/main/java/org/jboss/seam/faces/component/UIImport.java
===================================================================
--- modules/trunk/faces/src/main/java/org/jboss/seam/faces/component/UIImport.java	                        (rev 0)
+++ modules/trunk/faces/src/main/java/org/jboss/seam/faces/component/UIImport.java	2009-05-30 05:55:58 UTC (rev 11052)
@@ -0,0 +1,65 @@
+package org.jboss.seam.faces.component;
+
+import javax.faces.component.UIComponentBase;
+
+/**
+ * @author Dan Allen
+ */
+public class UIImport extends UIComponentBase
+{
+
+   // ------------------------------------------------------ Manifest Constants
+
+   /**
+    * <p>
+    * The standard component type for this component.
+    * </p>
+    */
+   public static final String COMPONENT_TYPE = "org.jboss.seam.faces.Import";
+
+   /**
+    * <p>
+    * The standard component type for this component.
+    * </p>
+    */
+   public static final String COMPONENT_FAMILY = "org.jboss.seam.faces.Import";
+   
+   /**
+    * Properties that are tracked by state saving.
+    */
+   enum PropertyKeys {
+       namespaces
+   }
+   
+   // ------------------------------------------------------------ Constructors
+
+   /**
+    * <p>
+    * Create a new {@link UIImport} instance with default property values.
+    * </p>
+    */
+   public UIImport()
+   {
+      super();
+      setRendererType(null);
+   }
+
+   // -------------------------------------------------------------- Properties
+
+   @Override
+   public String getFamily()
+   {
+      return COMPONENT_FAMILY;
+   }
+
+   public Object getNamespaces()
+   {
+      return getStateHelper().eval(PropertyKeys.namespaces);
+   }
+   
+   public void setNamespaces(Object namespaces)
+   {
+      getStateHelper().put(PropertyKeys.namespaces, namespaces);
+   }
+   
+}

Modified: modules/trunk/faces/src/main/java/org/jboss/seam/faces/component/UIRestrictView.java
===================================================================
--- modules/trunk/faces/src/main/java/org/jboss/seam/faces/component/UIRestrictView.java	2009-05-30 05:53:53 UTC (rev 11051)
+++ modules/trunk/faces/src/main/java/org/jboss/seam/faces/component/UIRestrictView.java	2009-05-30 05:55:58 UTC (rev 11052)
@@ -4,6 +4,10 @@
 import javax.faces.component.UIComponentBase;
 
 /**
+ * 
+ * TODO add login-required attribute. If require fails, we only force them to login
+ * if that helps them get to the page.
+ * 
  * @author Dan Allen
  */
 public class UIRestrictView extends UIComponentBase

Modified: modules/trunk/faces/src/main/java/org/jboss/seam/faces/component/UIViewAction.java
===================================================================
--- modules/trunk/faces/src/main/java/org/jboss/seam/faces/component/UIViewAction.java	2009-05-30 05:53:53 UTC (rev 11051)
+++ modules/trunk/faces/src/main/java/org/jboss/seam/faces/component/UIViewAction.java	2009-05-30 05:55:58 UTC (rev 11052)
@@ -4,6 +4,8 @@
 import javax.faces.component.UIComponentBase;
 
 /**
+ * TODO add conditional (if attribute)
+ * 
  * @author Dan Allen
  */
 public class UIViewAction extends UIComponentBase
@@ -28,9 +30,26 @@
    /**
     * Properties that are tracked by state saving.
     */
-   enum PropertyKeys {
-       onPostback,
-       execute
+   enum PropertyKeys
+   {
+      onPostback, execute, ifAttr("if");
+
+      private String name;
+
+      PropertyKeys()
+      {
+      }
+
+      PropertyKeys(String name)
+      {
+         this.name = name;
+      }
+
+      @Override
+      public String toString()
+      {
+         return name != null ? name : super.toString();
+      }
    }
    
    // ------------------------------------------------------------ Constructors
@@ -73,5 +92,15 @@
    {
       getStateHelper().put(PropertyKeys.onPostback, onPostback);
    }
+   
+   public boolean isIf()
+   {
+      return (Boolean) getStateHelper().eval(PropertyKeys.ifAttr, true);
+   }
 
+   public void setIf(boolean condition)
+   {
+      getStateHelper().put(PropertyKeys.ifAttr, condition);
+   }
+
 }

Modified: modules/trunk/faces/src/main/java/org/jboss/seam/faces/el/SeamFacesELResolver.java
===================================================================
--- modules/trunk/faces/src/main/java/org/jboss/seam/faces/el/SeamFacesELResolver.java	2009-05-30 05:53:53 UTC (rev 11051)
+++ modules/trunk/faces/src/main/java/org/jboss/seam/faces/el/SeamFacesELResolver.java	2009-05-30 05:55:58 UTC (rev 11052)
@@ -1,4 +1,4 @@
-/* 
+/*
  * JBoss, Home of Professional Open Source
  * Copyright 2009, Red Hat Middleware LLC, and individual contributors
  * by the @authors tag. See the copyright.txt in the distribution for a
@@ -23,36 +23,60 @@
  */
 package org.jboss.seam.faces.el;
 
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
 import javax.el.ELContext;
+import javax.el.ELResolver;
+import javax.enterprise.inject.spi.Bean;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.faces.context.FacesContext;
 import javax.faces.model.DataModel;
+
+import org.jboss.seam.bridge.ManagerBridge;
 import org.jboss.seam.el.AbstractELResolver;
+import org.jboss.seam.faces.lifecycle.ImportNamespacesProcessor;
 
 /**
- * <p>An {@link ELResolver} implementation that adds magic properties
- * to a JSF DataModel object.</p>
- *
- * <p>The following is a list of the magic properties:</p>
- *
+ * <p>
+ * An {@link ELResolver} implementation that adds magic properties to a JSF
+ * DataModel object.
+ * </p>
+ * 
+ * <p>
+ * The following is a list of the magic properties:
+ * </p>
+ * 
  * <ul>
- *   <li>size - the size of the wrapped data</li>
- *   <li>empty - a boolean indicating whether the wrapped data is empty</li>
+ * <li>size - the size of the wrapped data</li>
+ * <li>empty - a boolean indicating whether the wrapped data is empty</li>
  * </ul>
- *
- * <p>Assuming the variable <code>results</code> held a reference to a JSF
- * DataModel, you could print out its size using the following expression:</p>
- *
- * <pre>#{results.size}</pre>
- *
+ * 
+ * <p>
+ * Assuming the variable <code>results</code> held a reference to a JSF
+ * DataModel, you could print out its size using the following expression:
+ * </p>
+ * 
+ * <pre>
+ * #{results.size}
+ * </pre>
+ * 
  * @author Gavin King
  * @author Dan Allen
  */
-public class SeamFacesELResolver extends AbstractELResolver {
-
+public class SeamFacesELResolver extends AbstractELResolver
+{
    @Override
    public Object getValue(ELContext context, Object base, Object property)
    {
-      if (base instanceof DataModel)
+      if (base == null)
       {
+         // we should be landing here after the JSR-299 resolver has a chance
+         return resolveBase(context, property);
+      }
+      else if (base instanceof DataModel)
+      {
          return resolveInDataModel(context, base, property);
       }
 
@@ -65,22 +89,52 @@
       return (base instanceof DataModel);
    }
 
+   private Object resolveBase(ELContext context, Object property)
+   {
+      // is this check necessary?
+      if (!(property instanceof String))
+      {
+         return null;
+      }
+      
+      // FIXME refactor me to somewhere clean
+      Map<String, Object> viewMap = FacesContext.getCurrentInstance().getViewRoot().getViewMap(false);
+      if (viewMap == null || !viewMap.containsKey(ImportNamespacesProcessor.NAMESPACES_CACHE_KEY))
+      {
+         return null;
+      }
+      
+      String name = (String) property;
+      BeanManager manager = ManagerBridge.getProvider().getCurrentManager();
+      for (String namespace : (Collection<String>) viewMap.get(ImportNamespacesProcessor.NAMESPACES_CACHE_KEY))
+      {
+         Set<Bean<?>> beans = manager.getBeans(namespace + "." + name);
+         // TODO complain if it is more than one
+         if (beans.size() == 1)
+         {
+            context.setPropertyResolved(true);
+            return manager.getInstance(beans.iterator().next());
+         }
+      }
+      
+      return null;
+   }
+   
    private Object resolveInDataModel(ELContext context, Object base, Object property)
    {
       if ("size".equals(property))
       {
          context.setPropertyResolved(true);
-         return ((DataModel) base).getRowCount();
+         return ((DataModel<?>) base).getRowCount();
       }
       else if ("empty".equals(property))
       {
          context.setPropertyResolved(true);
-         return ((DataModel) base).getRowCount() == 0;
+         return ((DataModel<?>) base).getRowCount() == 0;
       }
       else
       {
          return null;
       }
    }
-
 }

Added: modules/trunk/faces/src/main/java/org/jboss/seam/faces/lifecycle/AbstractViewMetadataProcesssor.java
===================================================================
--- modules/trunk/faces/src/main/java/org/jboss/seam/faces/lifecycle/AbstractViewMetadataProcesssor.java	                        (rev 0)
+++ modules/trunk/faces/src/main/java/org/jboss/seam/faces/lifecycle/AbstractViewMetadataProcesssor.java	2009-05-30 05:55:58 UTC (rev 11052)
@@ -0,0 +1,46 @@
+package org.jboss.seam.faces.lifecycle;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+
+import javax.faces.component.UIComponent;
+import javax.faces.component.UIViewRoot;
+
+/**
+ * An abstract base class for processors for UI components in the metadata facet
+ * of the UIViewRoot. This class provides infrastructure for extracting UI
+ * components from the metadata facet branch of the component tree.
+ * 
+ * @author Dan Allen
+ */
+public abstract class AbstractViewMetadataProcesssor implements FacesSystemEventProcessor
+{
+   public abstract boolean execute();
+   
+   protected <C extends UIComponent> Collection<C> collectMetadataComponents(UIViewRoot viewRoot, UIComponentFilter<C> componentFilter)
+   {
+      UIComponent metadataFacet = viewRoot.getFacet(UIViewRoot.METADATA_FACET_NAME);
+      
+      if (metadataFacet == null)
+      {
+         return Collections.<C>emptyList();
+      }
+      
+      Collection<C> matches = new ArrayList<C>();
+      for (UIComponent candidate : metadataFacet.getChildren())
+      {
+         if (componentFilter.accepts(candidate))
+         {
+            matches.add((C) candidate);
+         }
+      }
+      
+      return matches;
+   }
+   
+   protected abstract class UIComponentFilter<C extends UIComponent>
+   {
+      public abstract boolean accepts(UIComponent candidate);
+   }
+}

Deleted: modules/trunk/faces/src/main/java/org/jboss/seam/faces/lifecycle/ConvertStatusMessagesListener.java
===================================================================
--- modules/trunk/faces/src/main/java/org/jboss/seam/faces/lifecycle/ConvertStatusMessagesListener.java	2009-05-30 05:53:53 UTC (rev 11051)
+++ modules/trunk/faces/src/main/java/org/jboss/seam/faces/lifecycle/ConvertStatusMessagesListener.java	2009-05-30 05:55:58 UTC (rev 11052)
@@ -1,57 +0,0 @@
-package org.jboss.seam.faces.lifecycle;
-
-import javax.enterprise.inject.AnnotationLiteral;
-import javax.enterprise.inject.UnsatisfiedResolutionException;
-import javax.enterprise.inject.spi.BeanManager;
-import javax.faces.component.UIViewRoot;
-import javax.faces.event.SystemEvent;
-import javax.faces.event.SystemEventListener;
-
-import org.jboss.seam.bridge.ManagerBridge;
-import org.jboss.seam.faces.Faces;
-import org.jboss.seam.international.StatusMessages;
-import org.jboss.webbeans.log.LogProvider;
-import org.jboss.webbeans.log.Logging;
-
-/**
- * <p>A {@link SystemEventListener} that observes the PreRenderViewEvent or
- * a redirect navigation event (via SeamViewHandler) and transposes Seam
- * StatusMessage objects into FacesMessage objects and transfers them to the FacesContext.</p>
- *
- * <p>FIXME the messages are going to get dropped if a view action causes a navigation event followed by a redirect event</p>
- *
- * @author Dan Allen
- */
-//@ListenerFor(systemEventClass = PreRenderViewEvent.class, sourceClass = UIViewRoot.class)
-public class ConvertStatusMessagesListener implements SystemEventListener
-{
-   private static final LogProvider log = Logging.getLogProvider(ConvertStatusMessagesListener.class);
-   
-   public boolean isListenerForSource(Object source)
-   {
-      return source instanceof UIViewRoot;
-   }
-
-   public void processEvent(SystemEvent preRenderViewEvent)
-   {
-      execute();
-   }
-
-   public void execute()
-   {
-      try
-      {
-         BeanManager manager = ManagerBridge.getProvider().getCurrentManager();
-         // tests
-         if (manager != null)
-         {
-            manager.getInstanceByType(StatusMessages.class, new AnnotationLiteral<Faces>() {}).onBeforeRender();
-         }
-      }
-      catch (UnsatisfiedResolutionException e)
-      {
-         log.warn("Could not locate the StatusMessages bean. Status messages will not be transfered to the FacesContext.");
-      }
-
-   }
-}

Copied: modules/trunk/faces/src/main/java/org/jboss/seam/faces/lifecycle/ConvertStatusMessagesProcessor.java (from rev 11044, modules/trunk/faces/src/main/java/org/jboss/seam/faces/lifecycle/ConvertStatusMessagesListener.java)
===================================================================
--- modules/trunk/faces/src/main/java/org/jboss/seam/faces/lifecycle/ConvertStatusMessagesProcessor.java	                        (rev 0)
+++ modules/trunk/faces/src/main/java/org/jboss/seam/faces/lifecycle/ConvertStatusMessagesProcessor.java	2009-05-30 05:55:58 UTC (rev 11052)
@@ -0,0 +1,34 @@
+package org.jboss.seam.faces.lifecycle;
+
+import org.jboss.seam.faces.Faces;
+import org.jboss.seam.international.StatusMessages;
+import org.jboss.webbeans.log.Log;
+import org.jboss.webbeans.log.Logger;
+
+/**
+ * <p>
+ * A {@link FacesSystemEventProcessor} that is executed on a PreRenderViewEvent
+ * or a redirect navigation event (via SeamViewHandler) to transpose Seam
+ * StatusMessage objects into FacesMessage objects and transfer them to the
+ * FacesContext.
+ * </p>
+ * 
+ * <p>
+ * FIXME the messages are going to get dropped if a view action causes a
+ * navigation event followed by a redirect event
+ * </p>
+ * 
+ * @author Dan Allen
+ */
+public class ConvertStatusMessagesProcessor implements FacesSystemEventProcessor
+{
+   @Logger Log log;
+   
+   @Faces StatusMessages statusMessages;
+
+   public boolean execute()
+   {
+      statusMessages.onBeforeRender();
+      return true;
+   }
+}

Added: modules/trunk/faces/src/main/java/org/jboss/seam/faces/lifecycle/EnforceViewRestrictionsProcessor.java
===================================================================
--- modules/trunk/faces/src/main/java/org/jboss/seam/faces/lifecycle/EnforceViewRestrictionsProcessor.java	                        (rev 0)
+++ modules/trunk/faces/src/main/java/org/jboss/seam/faces/lifecycle/EnforceViewRestrictionsProcessor.java	2009-05-30 05:55:58 UTC (rev 11052)
@@ -0,0 +1,116 @@
+package org.jboss.seam.faces.lifecycle;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.Collection;
+
+import javax.enterprise.inject.Current;
+import javax.faces.component.UIComponent;
+import javax.faces.component.UIViewRoot;
+import javax.faces.context.FacesContext;
+import javax.faces.event.AbortProcessingException;
+import javax.faces.event.ExceptionQueuedEvent;
+import javax.faces.event.ExceptionQueuedEventContext;
+
+import org.jboss.seam.faces.component.UIRestrictView;
+import org.jboss.seam.security.Identity;
+import org.jboss.webbeans.log.Log;
+import org.jboss.webbeans.log.Logger;
+
+/**
+ * <p>
+ * A JSF metadata facet processor which enforces restrictions and permissions in
+ * the Render Response phase of the JSF life cycle, immediately prior to the
+ * view being rendered.
+ * </p>
+ * 
+ * <p>
+ * The prerequisites are provided in the form of an EL ValueExpression and are
+ * evaluated by the Identity component of the Seam security module. If the
+ * Identity component indicates the security is disabled, the restrictions are
+ * not enforced.
+ * </p>
+ * 
+ * <p>
+ * If the user is not logged in, a NotLoggedInException is passed to the JSF
+ * exception handler. If the user is logged in and the restriction or permission
+ * is not met, an AuthorizationException is passed to the JSF exception handler.
+ * </p>
+ * 
+ * @author Dan Allen
+ */
+public class EnforceViewRestrictionsProcessor extends AbstractViewMetadataProcesssor
+{
+   @Logger Log log;
+
+   @Current FacesContext facesContext;
+   @Current Identity identity;
+
+   @Override
+   public boolean execute()
+   {
+      UIViewRoot viewRoot = facesContext.getViewRoot();
+
+      // collect first so as not to introduce a hard dependency on Identity if
+      // tag is not in use
+      Collection<UIRestrictView> restrictions = collectionViewRestrictions(viewRoot);
+      if (restrictions.isEmpty() || !Identity.isSecurityEnabled())
+      {
+         return true;
+      }
+
+      if (log.isTraceEnabled())
+      {
+         log.trace("Processing view restrictions before render view");
+      }
+
+      try
+      {
+         for (UIRestrictView restriction : restrictions)
+         {
+            if (restriction.getRequire() != null)
+            {
+               identity.checkRestriction(restriction.getRequire());
+            }
+            else
+            {
+               identity.checkPermission(viewRoot.getViewId(), "render");
+            }
+         }
+      }
+      // FIXME damn this is ugly, but JCDI is wrapping exceptions
+      catch (Exception e)
+      {
+         Throwable cause = e;
+         if (e instanceof InvocationTargetException)
+         {
+            cause = e.getCause();
+         }
+
+         facesContext.getApplication().publishEvent(facesContext, ExceptionQueuedEvent.class, new ExceptionQueuedEventContext(facesContext, cause));
+         // FIXME this is lame; there should be some other way to stop view
+         // rendering
+         facesContext.getViewRoot().setRendered(false);
+         throw new AbortProcessingException("View restriction criteria was not met.");
+      }
+
+      return true;
+   }
+
+   /**
+    * Pick out the UIRestrictView components from the metadata facet's children.
+    * If no matches are found, an unmodifiable empty list is returned.
+    */
+   protected Collection<UIRestrictView> collectionViewRestrictions(UIViewRoot viewRoot)
+   {
+      return collectMetadataComponents(viewRoot, new UIComponentFilter<UIRestrictView>()
+      {
+
+         @Override
+         public boolean accepts(UIComponent candidate)
+         {
+            return candidate instanceof UIRestrictView;
+         }
+
+      });
+   }
+}

Modified: modules/trunk/faces/src/main/java/org/jboss/seam/faces/lifecycle/ExecuteViewActionsListener.java
===================================================================
--- modules/trunk/faces/src/main/java/org/jboss/seam/faces/lifecycle/ExecuteViewActionsListener.java	2009-05-30 05:53:53 UTC (rev 11051)
+++ modules/trunk/faces/src/main/java/org/jboss/seam/faces/lifecycle/ExecuteViewActionsListener.java	2009-05-30 05:55:58 UTC (rev 11052)
@@ -1,115 +1,80 @@
 package org.jboss.seam.faces.lifecycle;
 
-import java.lang.reflect.InvocationTargetException;
-import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 
 import javax.el.ELException;
 import javax.el.MethodExpression;
+import javax.enterprise.inject.Current;
 import javax.faces.FacesException;
 import javax.faces.application.NavigationHandler;
 import javax.faces.component.UIComponent;
 import javax.faces.component.UIViewRoot;
 import javax.faces.context.FacesContext;
-import javax.faces.event.AbortProcessingException;
-import javax.faces.event.ExceptionQueuedEvent;
-import javax.faces.event.ExceptionQueuedEventContext;
-import javax.faces.event.SystemEvent;
-import javax.faces.event.SystemEventListener;
 
-import org.jboss.seam.bridge.ManagerBridge;
-import org.jboss.seam.faces.component.UIRestrictView;
 import org.jboss.seam.faces.component.UIViewAction;
-import org.jboss.seam.security.Identity;
 import org.jboss.webbeans.log.Log;
-import org.jboss.webbeans.log.Logging;
+import org.jboss.webbeans.log.Logger;
 
-//@ListenerFor(systemEventClass = PreRenderViewEvent.class, sourceClass = UIViewRoot.class)
-public class ExecuteViewActionsListener implements SystemEventListener
+/**
+ * <p>
+ * A JSF metadata facet processor which executes action expressions in the
+ * Render Response phase of the JSF life cycle, immediate prior to the view
+ * being rendered.
+ * </p>
+ * 
+ * <p>
+ * Before the first action is processed, the processor first checks if this is
+ * an initial (non-faces) request and the FacesContext reports that validation
+ * has failed. This situation occurs when validation has failed on a view
+ * parameter. The processor will execute the navigation handler using a null
+ * "from action" and the built-in logical outcome
+ * "org.jboss.seam.ViewParameterValidationFailed".
+ * </p>
+ * 
+ * <p>
+ * View actions are executed in the order that they appear in the view template.
+ * After each action is executed, the navigation handler is fired. If a
+ * navigation case is pursued, it short-circuits the remaining actions. It also
+ * instructs the processor chain to abort. Otherwise, the remaining actions are
+ * processed in the same manner.
+ * </p>
+ * 
+ * @author Dan Allen
+ */
+public class ExecuteViewActionsListener extends AbstractViewMetadataProcesssor
 {
-   private static final Log log = Logging.getLog(ExecuteViewActionsListener.class);
-
-   public boolean isListenerForSource(Object source)
-   {
-      return source instanceof UIViewRoot;
-   }
+   public static final String VIEW_PARAMETER_VALIDATION_FAILED_OUTCOME = "org.jboss.seam.ViewParameterValidationFailed";
    
-   public void processEvent(SystemEvent event) throws AbortProcessingException
-   {
-      execute();
-   }
+   @Logger Log log;
+   
+   @Current FacesContext facesContext;
 
-   protected void execute()
+   @Override
+   public boolean execute()
    {
-      FacesContext context = FacesContext.getCurrentInstance();
-      UIViewRoot initialViewRoot = context.getViewRoot();
+      UIViewRoot initialViewRoot = facesContext.getViewRoot();
 
-      // TEMPORARY needs to be organized better
-      
-      // collect first so as not to introduce a hard dependency on Identity if tag is not in use
-      Collection<UIRestrictView> restrictions = collectionViewRestrictions(initialViewRoot);
-      if (!restrictions.isEmpty())
-      {
-         if (Identity.isSecurityEnabled())
-         {
-            if (log.isTraceEnabled())
-            {
-               log.trace("Processing view restrictions before render view");
-            }
-            
-            Identity identity = ManagerBridge.getProvider().getCurrentManager().getInstanceByType(Identity.class);
-            try
-            {
-               for (UIRestrictView restriction : restrictions)
-               {
-                  if (restriction.getRequire() != null)
-                  {
-                     identity.checkRestriction(restriction.getRequire());
-                  }
-                  else
-                  {
-                     identity.checkPermission(initialViewRoot.getViewId(), "render");
-                  }
-               }
-            }
-            // FIXME damn this is ugly, but JCDI is wrapping exceptions
-            catch (Exception e)
-            {
-               Throwable cause = e;
-               if (e instanceof InvocationTargetException)
-               {
-                  cause = e.getCause();
-               }
-               
-               context.getApplication().publishEvent(context, ExceptionQueuedEvent.class, new ExceptionQueuedEventContext(context, cause));
-               // FIXME this is lame; there should be some other way to stop view rendering
-               context.getViewRoot().setRendered(false);
-               throw new AbortProcessingException("View restriction criteria was not met.");
-               //return;
-            }
-         }
-      }
-      // END TEMPORARY
-      
       if (log.isTraceEnabled())
       {
          log.trace("Processing view actions before render view");
       }
       
-      NavigationHandler navHandler = context.getApplication().getNavigationHandler();
-      boolean postback = context.isPostback();
+      NavigationHandler navHandler = facesContext.getApplication().getNavigationHandler();
+      boolean postback = facesContext.isPostback();
 
-      if (!postback && context.isValidationFailed())
+      // check if any view parameters failed validation and if so, fire the navigation handler
+      if (!postback && facesContext.isValidationFailed())
       {
          if (log.isTraceEnabled())
          {
-            log.trace("Validation flagged as failed. Calling navigation handler without executing view actions.");
+            log.trace("Validation of view parameters failed. Calling navigation handler without executing view actions.");
          }
-         navHandler.handleNavigation(context, null, null);
-         return;
+         // QUESTION is this a good idea to use a built-in logical outcome?
+         navHandler.handleNavigation(facesContext, null, VIEW_PARAMETER_VALIDATION_FAILED_OUTCOME);
+         return !facesContext.getResponseComplete() && initialViewRoot.getViewId().equals(facesContext.getViewRoot().getViewId());
       }
 
+      boolean proceed = true;
       Collection<UIViewAction> actions = collectViewActions(initialViewRoot, postback);
       for (UIViewAction action : actions)
       {
@@ -126,7 +91,7 @@
             }
             try
             {
-               Object returnVal = execute.invoke(context.getELContext(), null);
+               Object returnVal = execute.invoke(facesContext.getELContext(), null);
                outcome = (returnVal != null ? returnVal.toString() : null);
                fromAction = execute.getExpressionString();
             }
@@ -140,46 +105,34 @@
             }
          }
 
-         navHandler.handleNavigation(context, fromAction, outcome);
+         navHandler.handleNavigation(facesContext, fromAction, outcome);
          
-         // QUESTION In either of these two cases, should an AbortProcessingEvent exception be thrown?
-         if (context.getResponseComplete())
+         // short-circuit actions if response has been marked complete
+         if (facesContext.getResponseComplete())
          {
             if (log.isDebugEnabled())
             {
                log.debug("Response marked as complete during view action processing. Short-circuiting remaining actions.");
             }
             // FIXME this is lame; there should be some other way to stop view rendering
-            context.getViewRoot().setRendered(false);
+            facesContext.getViewRoot().setRendered(false);
+            proceed = false;
             break;
          }
-         else if (!initialViewRoot.getViewId().equals(context.getViewRoot().getViewId()))
+         // short-circuit actions if a navigation case was pursued
+         else if (!initialViewRoot.getViewId().equals(facesContext.getViewRoot().getViewId()))
          {
             if (log.isDebugEnabled())
             {
                log.debug("Detected change in view ID during view action processing. Short-circuiting remaining actions.");
             }
+            proceed = false;
             break;
          }
       }
+      
+      return proceed;
    }
-
-   /**
-    * Pick out the UIRestrictView components from the metadata facet's children. If no
-    * matches are found, an unmodifiable empty list is returned.
-    */
-   protected Collection<UIRestrictView> collectionViewRestrictions(UIViewRoot viewRoot)
-   {
-      return collectMetadataComponents(viewRoot, new UIComponentFilter<UIRestrictView>() {
-
-         @Override
-         public boolean accepts(UIComponent candidate)
-         {
-            return candidate instanceof UIRestrictView;
-         }
-         
-      });
-   }
    
    /**
     * Pick out the UIViewAction components from the metadata facet's children. If this is a postback,
@@ -193,36 +146,10 @@
          @Override
          public boolean accepts(UIComponent candidate)
          {
-            return candidate instanceof UIViewAction && (!postback || ((UIViewAction) candidate).isOnPostback());
+            return candidate instanceof UIViewAction && ((UIViewAction) candidate).isIf() && (!postback || ((UIViewAction) candidate).isOnPostback());
          }
          
       });
    }
-   
-   protected <C extends UIComponent> Collection<C> collectMetadataComponents(UIViewRoot viewRoot, UIComponentFilter<C> componentFilter)
-   {
-      UIComponent metadataFacet = viewRoot.getFacet(UIViewRoot.METADATA_FACET_NAME);
-      
-      if (metadataFacet == null)
-      {
-         return Collections.<C>emptyList();
-      }
-      
-      Collection<C> matches = new ArrayList<C>();
-      for (UIComponent candidate : metadataFacet.getChildren())
-      {
-         if (componentFilter.accepts(candidate))
-         {
-            matches.add((C) candidate);
-         }
-      }
-      
-      return matches;
-   }
-   
-   protected abstract class UIComponentFilter<C extends UIComponent>
-   {
-      public abstract boolean accepts(UIComponent candidate);
-   }
 
 }

Added: modules/trunk/faces/src/main/java/org/jboss/seam/faces/lifecycle/FacesSystemEventProcessor.java
===================================================================
--- modules/trunk/faces/src/main/java/org/jboss/seam/faces/lifecycle/FacesSystemEventProcessor.java	                        (rev 0)
+++ modules/trunk/faces/src/main/java/org/jboss/seam/faces/lifecycle/FacesSystemEventProcessor.java	2009-05-30 05:55:58 UTC (rev 11052)
@@ -0,0 +1,19 @@
+package org.jboss.seam.faces.lifecycle;
+
+/**
+ * A <strong>FacesSystemEventProcessor</strong> is an object which performs
+ * processing when a JSF SystemEvent is fired. Each processor is typically part
+ * of a chain. The execute() method returns a boolean indicating whether to
+ * continue or abort processing.
+ * 
+ * @author Dan Allen
+ */
+public interface FacesSystemEventProcessor
+{
+   /**
+    * Perform processing.
+    * 
+    * @return Whether the processing should continue. true = continue; false = abort
+    */
+   public abstract boolean execute();
+}

Added: modules/trunk/faces/src/main/java/org/jboss/seam/faces/lifecycle/ImportNamespacesProcessor.java
===================================================================
--- modules/trunk/faces/src/main/java/org/jboss/seam/faces/lifecycle/ImportNamespacesProcessor.java	                        (rev 0)
+++ modules/trunk/faces/src/main/java/org/jboss/seam/faces/lifecycle/ImportNamespacesProcessor.java	2009-05-30 05:55:58 UTC (rev 11052)
@@ -0,0 +1,92 @@
+package org.jboss.seam.faces.lifecycle;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Map;
+
+import javax.faces.component.UIComponent;
+import javax.faces.component.UIViewRoot;
+import javax.faces.context.FacesContext;
+
+import org.jboss.seam.faces.component.UIImport;
+
+/**
+ * <p>
+ * A metadata facet processor that executes on a non-faces request immediately
+ * prior to the view being rendered. The processor collects the EL namespaces
+ * and stores them in the view-scope (the view map of the UIViewRoot) where they
+ * can be consulted by the EL resolver.
+ * </p>
+ * 
+ * <p>
+ * An EL namespace is identical to a Java namespace, except for EL names. EL
+ * names are fully-qualified to prevent naming conflicts, but it is convenient
+ * for the page author to shorten those names by importing the namespace. When
+ * the EL resolver is resolving the base object, the namespaces are prepended to
+ * the name to match a fully-qualified EL name.
+ * </p>
+ * 
+ * @author Dan Allen
+ */
+public class ImportNamespacesProcessor extends AbstractViewMetadataProcesssor
+{
+   public static final String NAMESPACES_CACHE_KEY = "org.jboss.seam.faces.NAMESPACES_CACHE";
+   
+   @Override
+   public boolean execute()
+   {
+      UIViewRoot viewRoot = FacesContext.getCurrentInstance().getViewRoot();
+      Map<String, Object> viewMap = viewRoot.getViewMap();
+      if (viewMap.containsKey(NAMESPACES_CACHE_KEY))
+      {
+         return true;
+      }
+      
+      Collection<String> aggregateNamespaces = new ArrayList<String>();
+      for (UIImport uiImport : collectImports(viewRoot))
+      {
+         Object namespaces = uiImport.getNamespaces();
+         
+         if (namespaces instanceof Collection)
+         {
+            for (Object candidate : (Collection<?>) namespaces)
+            {
+               if (candidate instanceof String && ((String) candidate).length() > 0)
+               {
+                  aggregateNamespaces.add((String) candidate);
+               }
+            }
+         }
+         else if (namespaces instanceof String)
+         {
+            for (String candidate : Arrays.asList(((String) namespaces).split("[\\s]*,[\\s]*")))
+            {
+               if (candidate.length() > 0)
+               {
+                  aggregateNamespaces.add(candidate);
+               }
+            }
+         }
+      }
+      
+      viewMap.put(NAMESPACES_CACHE_KEY, aggregateNamespaces);
+      return true;
+   }
+   
+   /**
+    * Pick out the UIImport components from the metadata facet's children.
+    */
+   protected Collection<UIImport> collectImports(UIViewRoot viewRoot)
+   {
+      return collectMetadataComponents(viewRoot, new UIComponentFilter<UIImport>() {
+
+         @Override
+         public boolean accepts(UIComponent candidate)
+         {
+            return candidate instanceof UIImport;
+         }
+         
+      });
+   }
+}

Added: modules/trunk/faces/src/main/java/org/jboss/seam/faces/lifecycle/SeamPreRenderViewListener.java
===================================================================
--- modules/trunk/faces/src/main/java/org/jboss/seam/faces/lifecycle/SeamPreRenderViewListener.java	                        (rev 0)
+++ modules/trunk/faces/src/main/java/org/jboss/seam/faces/lifecycle/SeamPreRenderViewListener.java	2009-05-30 05:55:58 UTC (rev 11052)
@@ -0,0 +1,58 @@
+package org.jboss.seam.faces.lifecycle;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.enterprise.inject.spi.BeanManager;
+import javax.faces.component.UIViewRoot;
+import javax.faces.event.AbortProcessingException;
+import javax.faces.event.SystemEvent;
+import javax.faces.event.SystemEventListener;
+
+import org.jboss.seam.bridge.ManagerBridge;
+
+/**
+ * <p>
+ * A JSF {@link SystemEventListener} that observes the PreRenderViewEvent, which
+ * is fired in the Render Response phase of the JSF life cycle immediately prior
+ * to rendering the view. This listener maintains a list of
+ * {@link FacesSystemEventProcessor} types that are resolved by the JSR-299
+ * BeanManager and executed in turn. The execute() method of each processor
+ * indicates whether to continue on to the next processor or not.
+ * </p>
+ * 
+ * @author Dan Allen
+ * @see FacesSystemEventProcessor
+ */
+//@ListenerFor(systemEventClass = PreRenderViewEvent.class, sourceClass = UIViewRoot.class)
+public class SeamPreRenderViewListener implements SystemEventListener
+{
+   private List<Class<? extends FacesSystemEventProcessor>> processorTypes = new ArrayList<Class<? extends FacesSystemEventProcessor>>();
+   
+   public SeamPreRenderViewListener()
+   {
+      processorTypes.add(EnforceViewRestrictionsProcessor.class);
+      processorTypes.add(ExecuteViewActionsListener.class);
+      processorTypes.add(ImportNamespacesProcessor.class);
+      processorTypes.add(ConvertStatusMessagesProcessor.class);
+   }
+   
+   public boolean isListenerForSource(Object source)
+   {
+      return source instanceof UIViewRoot;
+   }
+
+   public void processEvent(SystemEvent event) throws AbortProcessingException
+   {
+      BeanManager manager = ManagerBridge.getProvider().getCurrentManager();
+      for (Class<? extends FacesSystemEventProcessor> processorType : processorTypes)
+      {
+         boolean result = manager.getInstanceByType(processorType).execute();
+         if (!result)
+         {
+            break;
+         }
+      }
+   }
+
+}

Modified: modules/trunk/faces/src/main/resources/META-INF/faces-config.xml
===================================================================
--- modules/trunk/faces/src/main/resources/META-INF/faces-config.xml	2009-05-30 05:53:53 UTC (rev 11051)
+++ modules/trunk/faces/src/main/resources/META-INF/faces-config.xml	2009-05-30 05:55:58 UTC (rev 11052)
@@ -18,17 +18,14 @@
    </factory>
 
    <application>
+      <message-bundle>org.jboss.seam.international.SeamResourceBundleAdapter</message-bundle>
       <view-handler>org.jboss.seam.faces.application.SeamViewHandler</view-handler>
       <el-resolver>org.jboss.seam.el.SeamELResolver</el-resolver>
       <el-resolver>org.jboss.seam.faces.el.SeamFacesELResolver</el-resolver>
       <system-event-listener>
-         <system-event-listener-class>org.jboss.seam.faces.lifecycle.ExecuteViewActionsListener</system-event-listener-class>
+         <system-event-listener-class>org.jboss.seam.faces.lifecycle.SeamPreRenderViewListener</system-event-listener-class>
          <system-event-class>javax.faces.event.PreRenderViewEvent</system-event-class>
       </system-event-listener>
-      <system-event-listener>
-         <system-event-listener-class>org.jboss.seam.faces.lifecycle.ConvertStatusMessagesListener</system-event-listener-class>
-         <system-event-class>javax.faces.event.PreRenderViewEvent</system-event-class>
-      </system-event-listener>
    </application>
 
    <lifecycle>
@@ -36,6 +33,11 @@
       <phase-listener>org.jboss.seam.faces.lifecycle.SeamPhaseListener</phase-listener>
       -->
    </lifecycle>
+
+   <component>
+      <component-type>org.jboss.seam.faces.Import</component-type>
+      <component-class>org.jboss.seam.faces.component.UIImport</component-class>
+   </component>
    
    <component>
       <component-type>org.jboss.seam.faces.RestrictView</component-type>

Modified: modules/trunk/faces/src/main/resources/META-INF/seam-faces.taglib.xml
===================================================================
--- modules/trunk/faces/src/main/resources/META-INF/seam-faces.taglib.xml	2009-05-30 05:53:53 UTC (rev 11051)
+++ modules/trunk/faces/src/main/resources/META-INF/seam-faces.taglib.xml	2009-05-30 05:55:58 UTC (rev 11052)
@@ -3,6 +3,13 @@
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facelettaglibary_2_0.xsd"
    version="2.0">
    <namespace>http://jboss.com/products/seam/faces</namespace>
+
+   <tag>
+      <tag-name>import</tag-name>
+      <component>
+         <component-type>org.jboss.seam.faces.Import</component-type>
+      </component>
+   </tag>
    
    <tag>
       <tag-name>restrictView</tag-name>




More information about the seam-commits mailing list