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>