From ae67cf6faae6c759625a108cafda86f66caa88f4 Mon Sep 17 00:00:00 2001 From: gonzalad Date: Thu, 27 Oct 2011 17:48:49 +0200 Subject: [PATCH 1/9] Intermediate commit ViewAction are registered in ViewConfigStore. Limitation due to ViewConfigStore storing only Annotation. I need to store at least AnnotatedMethod otherwise I won't be able to know which method to call if 2 methods have been annotated with exactly the same annotation (same attribute values I think). --- .../seam/faces/view/action/Condition.java | 21 ++ .../seam/faces/view/action/Immediate.java | 22 ++ .../seam/faces/view/action/OnPostback.java | 22 ++ .../jboss/seam/faces/view/action/Phase.java | 23 ++ .../seam/faces/view/action/PhaseDefault.java | 22 ++ .../view/action/ViewActionBindingType.java | 27 ++ examples/viewconfig/pom.xml | 23 +- .../examples/viewconfig/MyViewAction.java | 21 ++ .../examples/viewconfig/PageController.java | 7 + .../view/action/ViewActionPhaseListener.java | 230 ++++++++++++++++++ .../view/config/ViewConfigExtension.java | 66 ++++- pom.xml | 8 +- 12 files changed, 482 insertions(+), 10 deletions(-) create mode 100644 api/src/main/java/org/jboss/seam/faces/view/action/Condition.java create mode 100644 api/src/main/java/org/jboss/seam/faces/view/action/Immediate.java create mode 100644 api/src/main/java/org/jboss/seam/faces/view/action/OnPostback.java create mode 100644 api/src/main/java/org/jboss/seam/faces/view/action/Phase.java create mode 100644 api/src/main/java/org/jboss/seam/faces/view/action/PhaseDefault.java create mode 100644 api/src/main/java/org/jboss/seam/faces/view/action/ViewActionBindingType.java create mode 100644 examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/MyViewAction.java create mode 100644 impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionPhaseListener.java diff --git a/api/src/main/java/org/jboss/seam/faces/view/action/Condition.java b/api/src/main/java/org/jboss/seam/faces/view/action/Condition.java new file mode 100644 index 0000000..a5448e1 --- /dev/null +++ b/api/src/main/java/org/jboss/seam/faces/view/action/Condition.java @@ -0,0 +1,21 @@ +package org.jboss.seam.faces.view.action; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Conditionnally executes a viewAction. + * + * TODO : refactor me, I'm not type-safe ! + * + * @author Adriàn Gonzalez + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Condition { + public String condition = null; +} diff --git a/api/src/main/java/org/jboss/seam/faces/view/action/Immediate.java b/api/src/main/java/org/jboss/seam/faces/view/action/Immediate.java new file mode 100644 index 0000000..6f1a98d --- /dev/null +++ b/api/src/main/java/org/jboss/seam/faces/view/action/Immediate.java @@ -0,0 +1,22 @@ +package org.jboss.seam.faces.view.action; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.jboss.seam.faces.component.UIViewAction; + +/** + * Can be used instead of Phase. + * + * @see UIViewAction#isImmediate() + * @author Adriàn Gonzalez + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Immediate { + public Boolean immediate = null; +} diff --git a/api/src/main/java/org/jboss/seam/faces/view/action/OnPostback.java b/api/src/main/java/org/jboss/seam/faces/view/action/OnPostback.java new file mode 100644 index 0000000..156a7d2 --- /dev/null +++ b/api/src/main/java/org/jboss/seam/faces/view/action/OnPostback.java @@ -0,0 +1,22 @@ +package org.jboss.seam.faces.view.action; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.jboss.seam.faces.component.UIViewAction; + +/** + * Determines if viewAction is executed on postback. + * + * @see UIViewAction#isOnPostback() + * @author Adriàn Gonzalez + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface OnPostback { + public Boolean onPostback = false; +} diff --git a/api/src/main/java/org/jboss/seam/faces/view/action/Phase.java b/api/src/main/java/org/jboss/seam/faces/view/action/Phase.java new file mode 100644 index 0000000..28d0487 --- /dev/null +++ b/api/src/main/java/org/jboss/seam/faces/view/action/Phase.java @@ -0,0 +1,23 @@ +package org.jboss.seam.faces.view.action; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.jboss.seam.faces.component.UIViewAction; +import org.jboss.seam.faces.event.PhaseIdType; + +/** + * Phase on which a viewAction is executed. + * + * @see UIViewAction#getPhase() + * @author Adriàn Gonzalez + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Phase { + public PhaseIdType value() default PhaseIdType.RENDER_RESPONSE; +} diff --git a/api/src/main/java/org/jboss/seam/faces/view/action/PhaseDefault.java b/api/src/main/java/org/jboss/seam/faces/view/action/PhaseDefault.java new file mode 100644 index 0000000..b21e929 --- /dev/null +++ b/api/src/main/java/org/jboss/seam/faces/view/action/PhaseDefault.java @@ -0,0 +1,22 @@ +package org.jboss.seam.faces.view.action; + +import org.jboss.seam.faces.event.PhaseIdType; + +/** + * The Default values for Phase annotation, extracted as constant. + * + * @author Adriàn Gonzalez + */ +public class PhaseDefault { + public static final PhaseIdType DEFAULT_PHASE; + + static { + try { + DEFAULT_PHASE = (PhaseIdType) Phase.class.getMethod("value").getDefaultValue(); + } catch (NoSuchMethodException ex) { + throw new IllegalStateException("Error initialising values", ex); + } catch (SecurityException ex) { + throw new IllegalStateException("Error initialising values", ex); + } + } +} diff --git a/api/src/main/java/org/jboss/seam/faces/view/action/ViewActionBindingType.java b/api/src/main/java/org/jboss/seam/faces/view/action/ViewActionBindingType.java new file mode 100644 index 0000000..93115e5 --- /dev/null +++ b/api/src/main/java/org/jboss/seam/faces/view/action/ViewActionBindingType.java @@ -0,0 +1,27 @@ +package org.jboss.seam.faces.view.action; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Applied to an annotation to indicate that it is a faces action binding type + * + * Additionally, you can customize your view annotation follogin one of those approaches : + * + * + * @author Adriàn Gonzalez + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface ViewActionBindingType { +} diff --git a/examples/viewconfig/pom.xml b/examples/viewconfig/pom.xml index 55e6f76..e40c71a 100644 --- a/examples/viewconfig/pom.xml +++ b/examples/viewconfig/pom.xml @@ -36,6 +36,26 @@ org.jboss.seam.faces seam-faces-api + + + org.jboss.seam.transaction + seam-transaction-api + + + + org.jboss.seam.transaction + seam-transaction + + + + org.jboss.seam.international + seam-international-api + + + + org.jboss.seam.international + seam-international + org.jboss.seam.security @@ -47,7 +67,6 @@ org.jboss.seam.security seam-security compile - 3.1.0.Beta2 @@ -57,6 +76,8 @@ com.ocpsoft prettyfaces-jsf2 + + 3.3.0 diff --git a/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/MyViewAction.java b/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/MyViewAction.java new file mode 100644 index 0000000..2e97b03 --- /dev/null +++ b/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/MyViewAction.java @@ -0,0 +1,21 @@ +package org.jboss.seam.faces.examples.viewconfig; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.jboss.seam.faces.view.action.ViewActionBindingType; + +@ViewActionBindingType +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE}) +public @interface MyViewAction { + MyAppViewConfig.Pages value(); + + //just testing value override +// public PhaseIdType phase() default PhaseIdType.RENDER_RESPONSE; +// public Boolean immediate = null; +// public Boolean onPostback = false; +// public String condition = null; +} diff --git a/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/PageController.java b/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/PageController.java index 122b3b7..34bdf36 100644 --- a/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/PageController.java +++ b/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/PageController.java @@ -25,6 +25,7 @@ import javax.inject.Inject; import javax.inject.Named; +import org.jboss.seam.faces.examples.viewconfig.MyAppViewConfig.Pages; import org.jboss.seam.faces.examples.viewconfig.model.Current; import org.jboss.seam.faces.examples.viewconfig.model.Item; import org.jboss.seam.faces.examples.viewconfig.model.ItemDao; @@ -53,6 +54,12 @@ public Item getItem() { return item; } + @MyViewAction(Pages.ITEM) + public void loadEntry() { + System.out.println("loadEntry called"); + } + + @MyViewAction(Pages.ITEM) public void setItem(Item item) { this.item = item; } diff --git a/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionPhaseListener.java b/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionPhaseListener.java new file mode 100644 index 0000000..80f3d57 --- /dev/null +++ b/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionPhaseListener.java @@ -0,0 +1,230 @@ +package org.jboss.seam.faces.view.action; + +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +import javax.enterprise.event.Observes; +import javax.enterprise.inject.spi.BeanManager; +import javax.faces.component.UIViewRoot; +import javax.faces.context.FacesContext; +import javax.faces.event.PhaseEvent; +import javax.inject.Inject; + +import org.jboss.seam.faces.event.PhaseIdType; +import org.jboss.seam.faces.event.qualifier.After; +import org.jboss.seam.faces.event.qualifier.ApplyRequestValues; +import org.jboss.seam.faces.event.qualifier.Before; +import org.jboss.seam.faces.event.qualifier.InvokeApplication; +import org.jboss.seam.faces.event.qualifier.ProcessValidations; +import org.jboss.seam.faces.event.qualifier.RenderResponse; +import org.jboss.seam.faces.event.qualifier.RestoreView; +import org.jboss.seam.faces.event.qualifier.UpdateModelValues; +import org.jboss.seam.faces.view.config.ViewConfigStore; +import org.jboss.solder.logging.Logger; +import org.jboss.solder.reflection.AnnotationInspector; + +/** + * Use the annotations stored in the ViewConfigStore to execute action (MethodExpression) calls. + * + * Class similar to SecurityPhaseListener. + * + * @author Adriàn Gonzalez + */ +public class ViewActionPhaseListener { + + private transient final Logger log = Logger.getLogger(ViewActionPhaseListener.class); + + @Inject + private ViewConfigStore viewConfigStore; + @Inject + private BeanManager beanManager; + + /** + * Execute any action annotations applicable to the RestoreView phase + * + * @param event + */ + public void observeRestoreView(@Observes @After @RestoreView PhaseEvent event) { + log.debug("After Restore View event"); + performObservation(event, PhaseIdType.RESTORE_VIEW); + } + + /** + * Execute any action annotations applicable to the ApplyRequestValues phase + * + * @param event + */ + public void observeApplyRequestValues(@Observes @Before @ApplyRequestValues PhaseEvent event) { + log.debug("After Apply Request Values event"); + performObservation(event, PhaseIdType.APPLY_REQUEST_VALUES); + } + + /** + * Execute any action annotations applicable to the ProcessValidations phase + * + * @param event + */ + public void observeProcessValidations(@Observes @Before @ProcessValidations PhaseEvent event) { + log.debug("After Process Validations event"); + performObservation(event, PhaseIdType.PROCESS_VALIDATIONS); + } + + /** + * Execute any action annotations applicable to the UpdateModelValues phase + * + * @param event + */ + public void observeUpdateModelValues(@Observes @Before @UpdateModelValues PhaseEvent event) { + log.debug("After Update Model Values event"); + performObservation(event, PhaseIdType.UPDATE_MODEL_VALUES); + } + + /** + * Execute any action annotations applicable to the InvokeApplication phase + * + * @param event + */ + public void observeInvokeApplication(@Observes @Before @InvokeApplication PhaseEvent event) { + log.debug("Before Render Response event"); + performObservation(event, PhaseIdType.INVOKE_APPLICATION); + } + + /** + * Execute any action annotations applicable to the RenderResponse phase + * + * @param event + */ + public void observeRenderResponse(@Observes @Before @RenderResponse PhaseEvent event) { + log.debug("Before Render Response event"); + performObservation(event, PhaseIdType.RENDER_RESPONSE); + } + + /** + * Inspect the annotations in the ViewConfigStore, executing any actions applicable to this phase + * + * @param event + * @param phaseIdType + */ + private void performObservation(PhaseEvent event, PhaseIdType phaseIdType) { + UIViewRoot viewRoot = (UIViewRoot) event.getFacesContext().getViewRoot(); + List actionsForPhase = getViewActionsForPhase(phaseIdType, viewRoot.getViewId()); + if (actionsForPhase != null) { + log.debugf("Enforcing on phase %s", phaseIdType); + execute(event.getFacesContext(), viewRoot, actionsForPhase); + } + } + + /** + * Retrieve all annotations from the ViewConfigStore for a given a JSF phase, and a view id, + * and where the annotation is qualified by @SecurityBindingType + * + * @param currentPhase + * @param viewId + * @return list of restrictions applicable to this viewId and PhaseTypeId + */ + public List getViewActionsForPhase(PhaseIdType currentPhase, String viewId) { + List allViewActionAnnotations = viewConfigStore.getAllQualifierData(viewId, ViewActionBindingType.class); + List applicableViewActionAnnotations = null; + for (Annotation annotation : allViewActionAnnotations) { + PhaseIdType defaultPhase = getDefaultPhase(viewId); + if (isAnnotationApplicableToPhase(annotation, currentPhase, defaultPhase)) { + if (applicableViewActionAnnotations == null) { // avoid spawning arrays at all phases of the lifecycle + applicableViewActionAnnotations = new ArrayList(); + } + applicableViewActionAnnotations.add(annotation); + } + } + return applicableViewActionAnnotations; + } + + /** + * Inspect an annotation to see if it specifies a view in which it should be. Fall back on default view otherwise. + * + * @param annotation + * @param currentPhase + * @param defaultPhases + * @return true if the annotation is applicable to this view and phase, false otherwise + */ + public boolean isAnnotationApplicableToPhase(Annotation annotation, PhaseIdType currentPhase, PhaseIdType defaultPhase) { + Method phaseAtViewActionMethod = getPhaseAtViewActionMethod(annotation); + PhaseIdType phasedId = null; + if (phaseAtViewActionMethod != null) { + log.debug("Annotation %s is using the phase method."); + phasedId = getViewActionPhaseId(phaseAtViewActionMethod, annotation); + } + Phase phaseQualifier = AnnotationInspector.getAnnotation(annotation.annotationType(), Phase.class, beanManager); + if (phaseQualifier != null) { + log.debug("Using Phase found in @Phase qualifier on the annotation."); + phasedId = phaseQualifier.value(); + } + if (phasedId == null) { + log.debug("Falling back on default phase id"); + phasedId = defaultPhase; + } + return phasedId == currentPhase; + } + + /** + * Get the default phases at which restrictions should be applied, by looking for a @Phase default value + * + * @param viewId + * @return default phase + */ + public PhaseIdType getDefaultPhase(String viewId) { + return PhaseDefault.DEFAULT_PHASE; + } + + /** + * Utility method to extract the "phase" method from an annotation + * + * @param annotation + * @return phaseAtViewActionMethod if found, null otherwise + */ + public Method getPhaseAtViewActionMethod(Annotation annotation) { + Method phaseAtViewActionMethod; + try { + phaseAtViewActionMethod = annotation.annotationType().getDeclaredMethod("phase"); + } catch (NoSuchMethodException ex) { + phaseAtViewActionMethod = null; + } catch (SecurityException ex) { + throw new IllegalArgumentException("phase method must be accessible", ex); + } + return phaseAtViewActionMethod; + } + + /** + * Retrieve the default PhaseIdType defined by the phaseAtViewActionMethod in the annotation + * + * @param phaseAtViewActionMethod + * @param annotation + * @return PhaseIdType from the phaseAtViewActionMethod, null if empty + */ + public PhaseIdType getViewActionPhaseId(Method phaseAtViewActionMethod, Annotation annotation) { + PhaseIdType phaseId; + try { + phaseId = (PhaseIdType) phaseAtViewActionMethod.invoke(annotation); + } catch (IllegalAccessException ex) { + throw new IllegalArgumentException("phase method must be accessible", ex); + } catch (InvocationTargetException ex) { + throw new RuntimeException(ex); + } + return phaseId; + } + + /** + * Execute the list of applicable view action annotations, TODO... + * + * @param context + * @param viewRoot + * @param annotations + */ + private void execute(FacesContext context, UIViewRoot viewRoot, List annotations) { + if (annotations == null || annotations.isEmpty()) { + log.debug("Annotations is null/empty"); + return; + } + } +} diff --git a/impl/src/main/java/org/jboss/seam/faces/view/config/ViewConfigExtension.java b/impl/src/main/java/org/jboss/seam/faces/view/config/ViewConfigExtension.java index 8d9cc59..2cdc7a7 100644 --- a/impl/src/main/java/org/jboss/seam/faces/view/config/ViewConfigExtension.java +++ b/impl/src/main/java/org/jboss/seam/faces/view/config/ViewConfigExtension.java @@ -18,6 +18,8 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -25,10 +27,12 @@ import java.util.Set; import javax.enterprise.event.Observes; +import javax.enterprise.inject.spi.AnnotatedMethod; import javax.enterprise.inject.spi.AnnotatedType; import javax.enterprise.inject.spi.Extension; import javax.enterprise.inject.spi.ProcessAnnotatedType; +import org.jboss.seam.faces.view.action.ViewActionBindingType; import org.jboss.solder.logging.Logger; /** @@ -43,6 +47,8 @@ public class ViewConfigExtension implements Extension { private final Map> data = new HashMap>(); + private final Map viewFieldValueToViewId = new HashMap(); + public void processAnnotatedType(@Observes ProcessAnnotatedType event) { AnnotatedType tp = event.getAnnotatedType(); if (log.isTraceEnabled()) { @@ -64,20 +70,76 @@ public void processAnnotatedType(@Observes ProcessAnnotatedType event) { if (enumm.isAnnotationPresent(ViewPattern.class)) { ViewPattern viewConfig = enumm.getAnnotation(ViewPattern.class); Set viewPattern = new HashSet(); - data.put(viewConfig.value(), viewPattern); for (Annotation a : enumm.getAnnotations()) { if (a.annotationType() != ViewPattern.class) { viewPattern.add(a); } } + data.put(viewConfig.value(), viewPattern); + viewFieldValueToViewId.put(getViewFieldValue(enumm), viewConfig.value()); } } } } + // viewAction processing + for (final AnnotatedMethod m : tp.getMethods()) { + for (final Annotation annotation : m.getAnnotations()) { + if (annotation.annotationType().isAnnotationPresent(ViewActionBindingType.class)) { + Object viewField = getValue (annotation); + String viewId = viewFieldValueToViewId.get(viewField); + if (viewId == null) { + throw new IllegalArgumentException("Annotation "+annotation+" invalid : the view specified" + + "("+viewField+") doesn't correspond to a registered view in an annotated @ViewConfig class/interface."); + } + data.get(viewId).add(annotation); + } + } + } } - public Map> getData() { + /** + * Returns the value of a view field. + * @throws IllegalArgumentException if an error happens + */ + private Object getViewFieldValue(Field enumm) { + try { + return enumm.get(null); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Invalid view field "+enumm+" - error getting value "+e.toString(), e); + } catch (IllegalAccessException e) { + throw new IllegalArgumentException("Invalid view field "+enumm+" - error getting value "+e.toString(), e); + } + } + + public Map> getData() { return Collections.unmodifiableMap(data); } + + // Utility methods for viewAction, TODO move this block out of this class + + /** + * Retrieve the view defined by the value() method in the annotation + * + * @param annotation + * @return the result of value() call + * @throws IllegalArgumentException if no value() method was found + */ + private Object getValue(Annotation annotation) { + Method valueMethod; + try { + valueMethod = annotation.annotationType().getDeclaredMethod("value"); + } catch (NoSuchMethodException ex) { + throw new IllegalArgumentException("value method must be declared and must resolve to a valid view", ex); + } catch (SecurityException ex) { + throw new IllegalArgumentException("value method must be accessible", ex); + } + try { + return valueMethod.invoke(annotation); + } catch (IllegalAccessException ex) { + throw new IllegalArgumentException("value method must be accessible", ex); + } catch (InvocationTargetException ex) { + throw new RuntimeException(ex); + } + } } diff --git a/pom.xml b/pom.xml index 42abf6f..0bb2997 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.jboss.seam seam-parent - 16 + 17-SNAPSHOT seam-faces-parent @@ -31,12 +31,6 @@ - - com.ocpsoft - prettyfaces-jsf2 - 3.2.0 - - org.jboss.seam seam-bom From 8ba51cd905d0b718a768e893d73d14924b91143e Mon Sep 17 00:00:00 2001 From: gonzalad Date: Wed, 2 Nov 2011 20:36:12 +0100 Subject: [PATCH 2/9] @ViewAction, @ViewActionBindingType and @ViewController handling @ViewAction and @ViewActionBindingType usage are as per https://issues.jboss.org/browse/SEAMFACES-147 @ViewController usage is like MyFaces CODI's @PageBean (https://cwiki.apache.org/confluence/display/EXTCDI/JSF+Usage#JSFUsage-PageBeans) Sample usage in seam-faces-example-viewconfig application --- .../seam/faces/event/qualifier/After.java | 12 +- .../event/qualifier/ApplyRequestValues.java | 4 +- .../seam/faces/event/qualifier/Before.java | 12 +- .../event/qualifier/InvokeApplication.java | 4 +- .../event/qualifier/ProcessValidations.java | 4 +- .../faces/event/qualifier/RenderResponse.java | 4 +- .../faces/event/qualifier/RestoreView.java | 4 +- .../event/qualifier/UpdateModelValues.java | 4 +- .../view/action/AfterRenderResponse.java | 22 ++ .../view/action/BeforeRenderReponse.java | 22 ++ .../seam/faces/view/action/ViewAction.java | 24 ++ .../view/action/ViewActionBindingType.java | 13 +- .../faces/view/action/ViewController.java | 39 +++ .../view/config/ViewConfigDescriptor.java | 106 ++++++ .../faces/view/config/ViewConfigStore.java | 4 + .../examples/viewconfig/MyAppViewConfig.java | 18 + .../examples/viewconfig/MyViewAction.java | 6 - .../examples/viewconfig/PageController.java | 15 +- .../ViewActionBindingTypeController.java | 18 + .../viewconfig/ViewActionController.java | 15 + .../examples/viewconfig/ViewController.java | 71 ++++ .../viewconfig/src/main/webapp/index.xhtml | 14 + .../src/main/webapp/viewaction.xhtml | 19 ++ .../main/webapp/viewactionbindingtype.xhtml | 19 ++ .../src/main/webapp/viewcontroller.xhtml | 20 ++ .../seam/faces/view/action/PhaseInstant.java | 54 +++ .../ViewActionBindingTypeDescriptor.java | 52 +++ .../view/action/ViewActionPhaseListener.java | 228 ++----------- .../faces/view/action/ViewActionStrategy.java | 18 + .../faces/view/action/ViewActionUtils.java | 114 +++++++ .../view/action/ViewControllerDescriptor.java | 315 ++++++++++++++++++ .../view/action/ViewControllerExtension.java | 80 +++++ .../view/action/ViewControllerStore.java | 172 ++++++++++ .../view/config/ViewConfigExtension.java | 88 ++--- .../view/config/ViewConfigStoreImpl.java | 14 +- .../javax.enterprise.inject.spi.Extension | 1 + 36 files changed, 1327 insertions(+), 302 deletions(-) create mode 100644 api/src/main/java/org/jboss/seam/faces/view/action/AfterRenderResponse.java create mode 100644 api/src/main/java/org/jboss/seam/faces/view/action/BeforeRenderReponse.java create mode 100644 api/src/main/java/org/jboss/seam/faces/view/action/ViewAction.java create mode 100644 api/src/main/java/org/jboss/seam/faces/view/action/ViewController.java create mode 100644 api/src/main/java/org/jboss/seam/faces/view/config/ViewConfigDescriptor.java create mode 100644 examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/ViewActionBindingTypeController.java create mode 100644 examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/ViewActionController.java create mode 100644 examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/ViewController.java create mode 100644 examples/viewconfig/src/main/webapp/viewaction.xhtml create mode 100644 examples/viewconfig/src/main/webapp/viewactionbindingtype.xhtml create mode 100644 examples/viewconfig/src/main/webapp/viewcontroller.xhtml create mode 100644 impl/src/main/java/org/jboss/seam/faces/view/action/PhaseInstant.java create mode 100644 impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionBindingTypeDescriptor.java create mode 100644 impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionStrategy.java create mode 100644 impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionUtils.java create mode 100644 impl/src/main/java/org/jboss/seam/faces/view/action/ViewControllerDescriptor.java create mode 100644 impl/src/main/java/org/jboss/seam/faces/view/action/ViewControllerExtension.java create mode 100644 impl/src/main/java/org/jboss/seam/faces/view/action/ViewControllerStore.java diff --git a/api/src/main/java/org/jboss/seam/faces/event/qualifier/After.java b/api/src/main/java/org/jboss/seam/faces/event/qualifier/After.java index 8965d1d..d281a3d 100644 --- a/api/src/main/java/org/jboss/seam/faces/event/qualifier/After.java +++ b/api/src/main/java/org/jboss/seam/faces/event/qualifier/After.java @@ -16,22 +16,24 @@ */ package org.jboss.seam.faces.event.qualifier; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + import java.lang.annotation.Retention; import java.lang.annotation.Target; import javax.inject.Qualifier; -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.ElementType.PARAMETER; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - /** * Qualifies observer method parameters to select events that occur in a "after" phase in the JSF lifecycle * * @author Nicklas Karlsson */ @Qualifier -@Target({FIELD, PARAMETER}) +@Target({TYPE, FIELD, METHOD, PARAMETER}) @Retention(RUNTIME) public @interface After { } diff --git a/api/src/main/java/org/jboss/seam/faces/event/qualifier/ApplyRequestValues.java b/api/src/main/java/org/jboss/seam/faces/event/qualifier/ApplyRequestValues.java index 6202718..4773bb4 100644 --- a/api/src/main/java/org/jboss/seam/faces/event/qualifier/ApplyRequestValues.java +++ b/api/src/main/java/org/jboss/seam/faces/event/qualifier/ApplyRequestValues.java @@ -23,7 +23,9 @@ import javax.inject.Qualifier; import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** @@ -36,7 +38,7 @@ * @see javax.faces.event.PhaseEvent */ @Qualifier -@Target({FIELD, PARAMETER}) +@Target({TYPE, FIELD, METHOD, PARAMETER}) @Retention(RUNTIME) public @interface ApplyRequestValues { } diff --git a/api/src/main/java/org/jboss/seam/faces/event/qualifier/Before.java b/api/src/main/java/org/jboss/seam/faces/event/qualifier/Before.java index b82c509..a27adca 100644 --- a/api/src/main/java/org/jboss/seam/faces/event/qualifier/Before.java +++ b/api/src/main/java/org/jboss/seam/faces/event/qualifier/Before.java @@ -16,22 +16,24 @@ */ package org.jboss.seam.faces.event.qualifier; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + import java.lang.annotation.Retention; import java.lang.annotation.Target; import javax.inject.Qualifier; -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.ElementType.PARAMETER; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - /** * Qualifies observer method parameters to select events that occur in a "before" phase in the JSF lifecycle * * @author Nicklas Karlsson */ @Qualifier -@Target({FIELD, PARAMETER}) +@Target({TYPE, FIELD, METHOD, PARAMETER}) @Retention(RUNTIME) public @interface Before { } diff --git a/api/src/main/java/org/jboss/seam/faces/event/qualifier/InvokeApplication.java b/api/src/main/java/org/jboss/seam/faces/event/qualifier/InvokeApplication.java index a911a38..e4fbf19 100644 --- a/api/src/main/java/org/jboss/seam/faces/event/qualifier/InvokeApplication.java +++ b/api/src/main/java/org/jboss/seam/faces/event/qualifier/InvokeApplication.java @@ -23,7 +23,9 @@ import javax.inject.Qualifier; import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** @@ -34,7 +36,7 @@ * @author Nicklas Karlsson */ @Qualifier -@Target({FIELD, PARAMETER}) +@Target({TYPE, FIELD, METHOD, PARAMETER}) @Retention(RUNTIME) public @interface InvokeApplication { } diff --git a/api/src/main/java/org/jboss/seam/faces/event/qualifier/ProcessValidations.java b/api/src/main/java/org/jboss/seam/faces/event/qualifier/ProcessValidations.java index 94bd20c..fe8e1c6 100644 --- a/api/src/main/java/org/jboss/seam/faces/event/qualifier/ProcessValidations.java +++ b/api/src/main/java/org/jboss/seam/faces/event/qualifier/ProcessValidations.java @@ -23,7 +23,9 @@ import javax.inject.Qualifier; import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** @@ -35,7 +37,7 @@ * @After}. The event parameter is a {@link PhaseEvent}. */ @Qualifier -@Target({FIELD, PARAMETER}) +@Target({TYPE, FIELD, METHOD, PARAMETER}) @Retention(RUNTIME) public @interface ProcessValidations { } diff --git a/api/src/main/java/org/jboss/seam/faces/event/qualifier/RenderResponse.java b/api/src/main/java/org/jboss/seam/faces/event/qualifier/RenderResponse.java index d259613..f27b3a5 100644 --- a/api/src/main/java/org/jboss/seam/faces/event/qualifier/RenderResponse.java +++ b/api/src/main/java/org/jboss/seam/faces/event/qualifier/RenderResponse.java @@ -23,7 +23,9 @@ import javax.inject.Qualifier; import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** @@ -34,7 +36,7 @@ * @author Nicklas Karlsson */ @Qualifier -@Target({FIELD, PARAMETER}) +@Target({TYPE, FIELD, METHOD, PARAMETER}) @Retention(RUNTIME) public @interface RenderResponse { } diff --git a/api/src/main/java/org/jboss/seam/faces/event/qualifier/RestoreView.java b/api/src/main/java/org/jboss/seam/faces/event/qualifier/RestoreView.java index 5943387..38ec850 100644 --- a/api/src/main/java/org/jboss/seam/faces/event/qualifier/RestoreView.java +++ b/api/src/main/java/org/jboss/seam/faces/event/qualifier/RestoreView.java @@ -23,7 +23,9 @@ import javax.inject.Qualifier; import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** @@ -34,7 +36,7 @@ * @author Nicklas Karlsson */ @Qualifier -@Target({FIELD, PARAMETER}) +@Target({TYPE, FIELD, METHOD, PARAMETER}) @Retention(RUNTIME) public @interface RestoreView { } diff --git a/api/src/main/java/org/jboss/seam/faces/event/qualifier/UpdateModelValues.java b/api/src/main/java/org/jboss/seam/faces/event/qualifier/UpdateModelValues.java index 91b58a5..00b8fea 100644 --- a/api/src/main/java/org/jboss/seam/faces/event/qualifier/UpdateModelValues.java +++ b/api/src/main/java/org/jboss/seam/faces/event/qualifier/UpdateModelValues.java @@ -23,7 +23,9 @@ import javax.inject.Qualifier; import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** @@ -35,7 +37,7 @@ * @After}. The event parameter is a {@link PhaseEvent}. */ @Qualifier -@Target({FIELD, PARAMETER}) +@Target({TYPE, FIELD, METHOD, PARAMETER}) @Retention(RUNTIME) public @interface UpdateModelValues { } diff --git a/api/src/main/java/org/jboss/seam/faces/view/action/AfterRenderResponse.java b/api/src/main/java/org/jboss/seam/faces/view/action/AfterRenderResponse.java new file mode 100644 index 0000000..5316d80 --- /dev/null +++ b/api/src/main/java/org/jboss/seam/faces/view/action/AfterRenderResponse.java @@ -0,0 +1,22 @@ +package org.jboss.seam.faces.view.action; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This lifecycle annotation can be used on viewController methods. + * + * These methods will be called after JSF RENDER_VIEW phase. + * Typically used for cleanup purposes + * Shortcut for @After @RenderResponse. + * + * @author Adriàn Gonzalez + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +@Documented +public @interface AfterRenderResponse { +} diff --git a/api/src/main/java/org/jboss/seam/faces/view/action/BeforeRenderReponse.java b/api/src/main/java/org/jboss/seam/faces/view/action/BeforeRenderReponse.java new file mode 100644 index 0000000..bd6556c --- /dev/null +++ b/api/src/main/java/org/jboss/seam/faces/view/action/BeforeRenderReponse.java @@ -0,0 +1,22 @@ +package org.jboss.seam.faces.view.action; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This lifecycle annotation can be used on viewController methods. + * + * These methods will be called before JSF RENDER_VIEW phase. + * Typically used for view initialization purposes. + * Shortcut for @Before @RenderResponse. + * + * @author Adriàn Gonzalez + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +@Documented +public @interface BeforeRenderReponse { +} diff --git a/api/src/main/java/org/jboss/seam/faces/view/action/ViewAction.java b/api/src/main/java/org/jboss/seam/faces/view/action/ViewAction.java new file mode 100644 index 0000000..96c9131 --- /dev/null +++ b/api/src/main/java/org/jboss/seam/faces/view/action/ViewAction.java @@ -0,0 +1,24 @@ +package org.jboss.seam.faces.view.action; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * The EL MethodExpression is executed when this annotation is applied to a ViewConfig. + * + * The MethodExpression is called before RENDER_RESPONSE phase. + * + * @author Adriàn Gonzalez + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface ViewAction { + /** + * El MethodExpression + */ + String value(); +} diff --git a/api/src/main/java/org/jboss/seam/faces/view/action/ViewActionBindingType.java b/api/src/main/java/org/jboss/seam/faces/view/action/ViewActionBindingType.java index 93115e5..6f54760 100644 --- a/api/src/main/java/org/jboss/seam/faces/view/action/ViewActionBindingType.java +++ b/api/src/main/java/org/jboss/seam/faces/view/action/ViewActionBindingType.java @@ -7,16 +7,11 @@ import java.lang.annotation.Target; /** - * Applied to an annotation to indicate that it is a faces action binding type + * Applied to an annotation to indicate that it is a faces action binding type. * - * Additionally, you can customize your view annotation follogin one of those approaches : - *
    - *
  • any other annotations in this package can also be applied to this annotation - * to customize view action behaviour (phase, postback, ...). This enables to customize behaviour - * per annotation definition
  • - *
  • those annotations can be replaced by methods of the same name in the view action annotation. - * This enables to customize behaviour per annotation usage.
  • - *
+ * By default, this method will be called before RENDER_RESPONSE phase. + * You can change the jsf phase by using the annotations from org.jboss.seam.faces.event.qualifier package + * on your custom annotation. * * @author Adriàn Gonzalez */ diff --git a/api/src/main/java/org/jboss/seam/faces/view/action/ViewController.java b/api/src/main/java/org/jboss/seam/faces/view/action/ViewController.java new file mode 100644 index 0000000..f040d1a --- /dev/null +++ b/api/src/main/java/org/jboss/seam/faces/view/action/ViewController.java @@ -0,0 +1,39 @@ +package org.jboss.seam.faces.view.action; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.jboss.seam.faces.event.qualifier.After; +import org.jboss.seam.faces.event.qualifier.ApplyRequestValues; +import org.jboss.seam.faces.event.qualifier.Before; +import org.jboss.seam.faces.event.qualifier.InvokeApplication; +import org.jboss.seam.faces.event.qualifier.ProcessValidations; +import org.jboss.seam.faces.event.qualifier.RenderResponse; +import org.jboss.seam.faces.event.qualifier.UpdateModelValues; + +/** + * This annotation must be used on a ViewConfig to specify its viewControllers. + * + *

A viewController is a managed bean handling a specific view. + * Some methods of the bean can be called during the lifecycle of the view. + * Those methods must be annotated with {@link BeforeRenderResponse}, {@link AfterRenderResponse}, or a mixture of + * {@link Before}, {@link After}, {@link ApplyRequestValues}, {@link ProcessValidations}, {@link UpdateModelValues}, + * {@link InvokeApplication} or {@link RenderResponse}.

+ * + *

Classic use case are : + *

    + *
  • {@link BeforeRenderResponse} for handling view initialization data (i.e. fetching data from database).
  • + *
  • {@link AfterRenderResponse} for view cleanup.
  • + *

+ * + * @author Adriàn Gonzalez + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +@Documented +public @interface ViewController { + Class[] value(); +} diff --git a/api/src/main/java/org/jboss/seam/faces/view/config/ViewConfigDescriptor.java b/api/src/main/java/org/jboss/seam/faces/view/config/ViewConfigDescriptor.java new file mode 100644 index 0000000..4a70c79 --- /dev/null +++ b/api/src/main/java/org/jboss/seam/faces/view/config/ViewConfigDescriptor.java @@ -0,0 +1,106 @@ +package org.jboss.seam.faces.view.config; + +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +import org.jboss.solder.logging.Logger; + +/** + * Information about {@link ViewConfig} enum. + * + * @author Adriàn Gonzalez + */ +public class ViewConfigDescriptor { + private transient final Logger log = Logger.getLogger(ViewConfigDescriptor.class); + + private String viewId; + private List values = new ArrayList(); + private List metaData = new ArrayList(); + private final ConcurrentHashMap, Annotation> metaDataByAnnotation = new ConcurrentHashMap, Annotation>(); + private ConcurrentHashMap, List> metaDataByQualifier = new ConcurrentHashMap, List>(); + + /** + * ViewConfigDescriptor for view viewId + */ + public ViewConfigDescriptor(String viewId, Object value) { + this.viewId = viewId; + this.values = new ArrayList(); + values.add(value); + } + + public String getViewId() { + return viewId; + } + + public void setViewId(String viewId) { + this.viewId = viewId; + } + + public void addValue(Object value) { + if (!values.contains(value)) { + values.add(value); + } + } + + public List getValues() { + return values; + } + + public void setValues(List values) { + this.values = values; + } + + public void addMetaData(Annotation metaData) { + this.metaData.add(metaData); + //add to metaDataByAnnotation + metaDataByAnnotation.put(metaData.annotationType(), metaData); + log.debugf("Putting new annotation (type: %s) for viewId: %s", metaData.annotationType().getName(), getViewId()); + //add to metaDataByQualifier + Annotation[] annotations = metaData.annotationType().getAnnotations(); + for (Annotation qualifier : annotations) { + if (qualifier.annotationType().getName().startsWith("java.")) { + log.debugf("Disregarding java.* package %s", qualifier.annotationType().getName()); + continue; + } + List qualifiedAnnotations = new ArrayList(); + List exisitngQualifiedAnnotations = metaDataByQualifier.get(qualifier + .annotationType()); + if (exisitngQualifiedAnnotations != null && !exisitngQualifiedAnnotations.isEmpty()) { + qualifiedAnnotations.addAll(exisitngQualifiedAnnotations); + } + qualifiedAnnotations.add(metaData); + log.debugf("Adding new annotation (type: %s) for Qualifier %s", metaData.annotationType().getName(), qualifier.annotationType().getName()); + metaDataByQualifier.put(qualifier.annotationType(), qualifiedAnnotations); + } + } + + /** + * Returns read-only list. + * + * Use {@link #addMetaData(Annotation)} to modify metaDatas. + */ + public List getMetaData() { + return Collections.unmodifiableList(metaData); + } + + /** + * returns all metaData of the corresponding type. + */ + public T getMetaData(Class type) { + return (T) metaDataByAnnotation.get(type); + } + + /** + * returns all qualified data from metadata annotations. + * + * returns empty list if there's no metaData for the qualifier. + */ + @SuppressWarnings("unchecked") + public List getAllQualifierData(Class qualifier) { + List metaData = metaDataByQualifier.get(qualifier); + return metaData!=null ? Collections.unmodifiableList(metaData) : Collections.EMPTY_LIST; + } +} diff --git a/api/src/main/java/org/jboss/seam/faces/view/config/ViewConfigStore.java b/api/src/main/java/org/jboss/seam/faces/view/config/ViewConfigStore.java index da7b462..f8b85de 100644 --- a/api/src/main/java/org/jboss/seam/faces/view/config/ViewConfigStore.java +++ b/api/src/main/java/org/jboss/seam/faces/view/config/ViewConfigStore.java @@ -56,4 +56,8 @@ public interface ViewConfigStore { */ public Map getAllAnnotationViewMap(Class type); + /** + * return the registered viewConfigs + */ + public List getAllViewConfigDescriptors(); } diff --git a/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/MyAppViewConfig.java b/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/MyAppViewConfig.java index f74ada0..dcc26d0 100644 --- a/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/MyAppViewConfig.java +++ b/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/MyAppViewConfig.java @@ -16,12 +16,16 @@ */ package org.jboss.seam.faces.examples.viewconfig; +import org.jboss.seam.faces.event.qualifier.ApplyRequestValues; +import org.jboss.seam.faces.event.qualifier.Before; import org.jboss.seam.faces.examples.viewconfig.security.Admin; import org.jboss.seam.faces.examples.viewconfig.security.Owner; import org.jboss.seam.faces.rewrite.FacesRedirect; import org.jboss.seam.faces.rewrite.UrlMapping; import org.jboss.seam.faces.security.AccessDeniedView; import org.jboss.seam.faces.security.LoginView; +import org.jboss.seam.faces.view.action.ViewAction; +import org.jboss.seam.faces.view.action.ViewController; import org.jboss.seam.faces.view.config.ViewConfig; import org.jboss.seam.faces.view.config.ViewPattern; @@ -39,9 +43,23 @@ static enum Pages { @UrlMapping(pattern = "/item/#{id}/") @ViewPattern("/item.xhtml") + @ViewController(PageController.class) @Owner + @ViewAction("#{pageController.viewAction(pageController.item)}") + @Before @ApplyRequestValues ITEM, + @ViewPattern("/viewcontroller.xhtml") + @ViewController(org.jboss.seam.faces.examples.viewconfig.ViewController.class) + VIEW_CONTROLLER, + + @ViewPattern("/viewactionbindingtype.xhtml") + VIEW_ACTION_BINDING_TYPE, + + @ViewPattern("/viewaction.xhtml") + @ViewAction("#{viewActionController.preRenderAction}") + VIEW_ACTION, + @FacesRedirect @ViewPattern("/*") @AccessDeniedView("/denied.xhtml") diff --git a/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/MyViewAction.java b/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/MyViewAction.java index 2e97b03..8725143 100644 --- a/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/MyViewAction.java +++ b/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/MyViewAction.java @@ -12,10 +12,4 @@ @Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE}) public @interface MyViewAction { MyAppViewConfig.Pages value(); - - //just testing value override -// public PhaseIdType phase() default PhaseIdType.RENDER_RESPONSE; -// public Boolean immediate = null; -// public Boolean onPostback = false; -// public String condition = null; } diff --git a/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/PageController.java b/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/PageController.java index 34bdf36..c540cfa 100644 --- a/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/PageController.java +++ b/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/PageController.java @@ -29,6 +29,7 @@ import org.jboss.seam.faces.examples.viewconfig.model.Current; import org.jboss.seam.faces.examples.viewconfig.model.Item; import org.jboss.seam.faces.examples.viewconfig.model.ItemDao; +import org.jboss.seam.faces.view.action.BeforeRenderReponse; /** * @author Brian Leathem @@ -54,12 +55,20 @@ public Item getItem() { return item; } + @BeforeRenderReponse + public void beforeRenderView(@Current Item item) { + System.out.println("beforeRenderView called "+item); + } + + public void viewAction(@Current Item item) { + System.out.println("viewAction "+item); + } + @MyViewAction(Pages.ITEM) - public void loadEntry() { - System.out.println("loadEntry called"); + public void viewActionBindingType(@Current Item item) { + System.out.println("viewActionBindingType "+item); } - @MyViewAction(Pages.ITEM) public void setItem(Item item) { this.item = item; } diff --git a/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/ViewActionBindingTypeController.java b/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/ViewActionBindingTypeController.java new file mode 100644 index 0000000..0285282 --- /dev/null +++ b/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/ViewActionBindingTypeController.java @@ -0,0 +1,18 @@ +package org.jboss.seam.faces.examples.viewconfig; + +import javax.enterprise.context.RequestScoped; +import javax.faces.application.FacesMessage; +import javax.faces.context.FacesContext; +import javax.inject.Named; + +import org.jboss.seam.faces.examples.viewconfig.MyAppViewConfig.Pages; + +@Named +@RequestScoped +public class ViewActionBindingTypeController { + @MyViewAction(Pages.VIEW_ACTION_BINDING_TYPE) + public void beforeRenderAction() { + FacesMessage facesMessages = new FacesMessage("ViewActionBindingTypeController.beforeRenderAction was called"); + FacesContext.getCurrentInstance().addMessage(null, facesMessages); + } +} diff --git a/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/ViewActionController.java b/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/ViewActionController.java new file mode 100644 index 0000000..0a299a1 --- /dev/null +++ b/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/ViewActionController.java @@ -0,0 +1,15 @@ +package org.jboss.seam.faces.examples.viewconfig; + +import javax.enterprise.context.RequestScoped; +import javax.faces.application.FacesMessage; +import javax.faces.context.FacesContext; +import javax.inject.Named; + +@Named +@RequestScoped +public class ViewActionController { + public void preRenderAction() { + FacesMessage facesMessages = new FacesMessage("ViewActionController.preRenderAction was called"); + FacesContext.getCurrentInstance().addMessage(null, facesMessages); + } +} diff --git a/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/ViewController.java b/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/ViewController.java new file mode 100644 index 0000000..951c922 --- /dev/null +++ b/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/ViewController.java @@ -0,0 +1,71 @@ +package org.jboss.seam.faces.examples.viewconfig; + +import javax.faces.application.FacesMessage; +import javax.faces.context.FacesContext; + +import org.jboss.seam.faces.event.qualifier.After; +import org.jboss.seam.faces.event.qualifier.ApplyRequestValues; +import org.jboss.seam.faces.event.qualifier.Before; +import org.jboss.seam.faces.event.qualifier.InvokeApplication; +import org.jboss.seam.faces.event.qualifier.ProcessValidations; +import org.jboss.seam.faces.event.qualifier.RenderResponse; +import org.jboss.seam.faces.event.qualifier.UpdateModelValues; +import org.jboss.seam.faces.view.action.BeforeRenderReponse; + +public class ViewController { + + @Before @ApplyRequestValues + public void beforeApplyRequestValues() { + addFacesMessage(this.getClass().getSimpleName()+".beforeApplyRequestValues was called"); + } + + @After @ApplyRequestValues + public void afterApplyRequestValues() { + addFacesMessage(this.getClass().getSimpleName()+".afterApplyRequestValues was called"); + } + + @Before @ProcessValidations + public void beforeProcessValidations() { + addFacesMessage(this.getClass().getSimpleName()+".beforeProcessValidations was called"); + } + + @After @ProcessValidations + public void afterProcessValidations() { + addFacesMessage(this.getClass().getSimpleName()+".afterProcessValidations was called"); + } + + @Before @UpdateModelValues + public void beforeUpdateModelValues() { + addFacesMessage(this.getClass().getSimpleName()+".beforeUpdateModelValues was called"); + } + + @After @UpdateModelValues + public void afterUpdateModelValues() { + addFacesMessage(this.getClass().getSimpleName()+".afterUpdateModelValues was called"); + } + + @Before @InvokeApplication + public void beforeInvokeApplication() { + addFacesMessage(this.getClass().getSimpleName()+".beforeInvokeApplication was called"); + } + + @After @InvokeApplication + public void afterInvokeApplication() { + addFacesMessage(this.getClass().getSimpleName()+".afterInvokeApplication was called"); + } + + @BeforeRenderReponse + public void beforeRenderResponse() { + addFacesMessage(this.getClass().getSimpleName()+".beforeRenderResponse was called"); + } + + @After @RenderResponse + public void afterRenderResponse() { + addFacesMessage(this.getClass().getSimpleName()+".RenderResponse was called"); + } + + private void addFacesMessage(String message) { + FacesMessage facesMessages = new FacesMessage(message); + FacesContext.getCurrentInstance().addMessage(null, facesMessages); + } +} diff --git a/examples/viewconfig/src/main/webapp/index.xhtml b/examples/viewconfig/src/main/webapp/index.xhtml index 7407433..8bc57ac 100644 --- a/examples/viewconfig/src/main/webapp/index.xhtml +++ b/examples/viewconfig/src/main/webapp/index.xhtml @@ -22,6 +22,20 @@ +

This example demonstrates shows View Actions in action

+ +
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+
diff --git a/examples/viewconfig/src/main/webapp/viewaction.xhtml b/examples/viewconfig/src/main/webapp/viewaction.xhtml new file mode 100644 index 0000000..ac71eb7 --- /dev/null +++ b/examples/viewconfig/src/main/webapp/viewaction.xhtml @@ -0,0 +1,19 @@ + + + + + + + @ViewAction usage + + + + + + + + diff --git a/examples/viewconfig/src/main/webapp/viewactionbindingtype.xhtml b/examples/viewconfig/src/main/webapp/viewactionbindingtype.xhtml new file mode 100644 index 0000000..2793fd6 --- /dev/null +++ b/examples/viewconfig/src/main/webapp/viewactionbindingtype.xhtml @@ -0,0 +1,19 @@ + + + + + + + @ViewActionBindingType usage + + + + + + + + diff --git a/examples/viewconfig/src/main/webapp/viewcontroller.xhtml b/examples/viewconfig/src/main/webapp/viewcontroller.xhtml new file mode 100644 index 0000000..535fccd --- /dev/null +++ b/examples/viewconfig/src/main/webapp/viewcontroller.xhtml @@ -0,0 +1,20 @@ + + + + + + + @ViewController usage + + + + + + + + + diff --git a/impl/src/main/java/org/jboss/seam/faces/view/action/PhaseInstant.java b/impl/src/main/java/org/jboss/seam/faces/view/action/PhaseInstant.java new file mode 100644 index 0000000..98b4419 --- /dev/null +++ b/impl/src/main/java/org/jboss/seam/faces/view/action/PhaseInstant.java @@ -0,0 +1,54 @@ +package org.jboss.seam.faces.view.action; + +import javax.faces.event.PhaseId; + +/** + * Identifies when the viewAction must take place in the JSF lifecycle. + */ +public class PhaseInstant { + private PhaseId phaseId; + private boolean before; + + public static final PhaseInstant BEFORE_RENDER_RESPONSE = new PhaseInstant(PhaseId.RENDER_RESPONSE, true); + + public PhaseInstant(PhaseId phaseId, boolean before) { + this.phaseId = phaseId; + this.before = before; + } + + public PhaseId getPhaseId() { + return phaseId; + } + + public void setPhaseId(PhaseId phaseId) { + this.phaseId = phaseId; + } + + public boolean isBefore() { + return before; + } + + public void setBefore(boolean before) { + this.before = before; + } + + @Override + public int hashCode() { + return phaseId.hashCode(); + } + + @Override + public boolean equals(Object object) { + if (!(object instanceof PhaseInstant)) { + return false; + } + PhaseInstant instant = (PhaseInstant) object; + if (getPhaseId() != instant.getPhaseId()) { + return false; + } + if (isBefore() != instant.isBefore()) { + return false; + } + return true; + } +} diff --git a/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionBindingTypeDescriptor.java b/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionBindingTypeDescriptor.java new file mode 100644 index 0000000..4e4e4e6 --- /dev/null +++ b/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionBindingTypeDescriptor.java @@ -0,0 +1,52 @@ +package org.jboss.seam.faces.view.action; + +import java.lang.annotation.Annotation; +import java.util.Arrays; + +import javax.enterprise.inject.spi.AnnotatedMethod; + +import static org.jboss.seam.faces.view.action.PhaseInstant.BEFORE_RENDER_RESPONSE; + +public class ViewActionBindingTypeDescriptor { + private Annotation annotation; + private Object viewControllerValue; + private AnnotatedMethod annotatedMethod; + private PhaseInstant phaseInstant; + + public ViewActionBindingTypeDescriptor(AnnotatedMethod annotatedMethod, Annotation annotation, + Object viewControllerValue) { + this.annotatedMethod = annotatedMethod; + this.annotation = annotation; + this.viewControllerValue = viewControllerValue; + this.phaseInstant = ViewActionUtils.getPhaseInstantOrDefault(Arrays.asList(annotation.annotationType().getAnnotations()), + annotation.annotationType(), BEFORE_RENDER_RESPONSE); + } + + public Annotation getAnnotation() { + return annotation; + } + + public void setAnnotation(Annotation annotation) { + this.annotation = annotation; + } + + public Object getViewControllerValue() { + return viewControllerValue; + } + + public void setViewControllerValue(Object viewControllerValue) { + this.viewControllerValue = viewControllerValue; + } + + public AnnotatedMethod getAnnotatedMethod() { + return annotatedMethod; + } + + public void setAnnotatedMethod(AnnotatedMethod annotatedMethod) { + this.annotatedMethod = annotatedMethod; + } + + public PhaseInstant getPhaseInstant() { + return phaseInstant; + } +} diff --git a/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionPhaseListener.java b/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionPhaseListener.java index 80f3d57..c2d05b9 100644 --- a/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionPhaseListener.java +++ b/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionPhaseListener.java @@ -1,30 +1,15 @@ package org.jboss.seam.faces.view.action; -import java.lang.annotation.Annotation; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.ArrayList; import java.util.List; import javax.enterprise.event.Observes; -import javax.enterprise.inject.spi.BeanManager; -import javax.faces.component.UIViewRoot; -import javax.faces.context.FacesContext; import javax.faces.event.PhaseEvent; +import javax.faces.event.PhaseId; import javax.inject.Inject; -import org.jboss.seam.faces.event.PhaseIdType; import org.jboss.seam.faces.event.qualifier.After; -import org.jboss.seam.faces.event.qualifier.ApplyRequestValues; import org.jboss.seam.faces.event.qualifier.Before; -import org.jboss.seam.faces.event.qualifier.InvokeApplication; -import org.jboss.seam.faces.event.qualifier.ProcessValidations; -import org.jboss.seam.faces.event.qualifier.RenderResponse; -import org.jboss.seam.faces.event.qualifier.RestoreView; -import org.jboss.seam.faces.event.qualifier.UpdateModelValues; -import org.jboss.seam.faces.view.config.ViewConfigStore; import org.jboss.solder.logging.Logger; -import org.jboss.solder.reflection.AnnotationInspector; /** * Use the annotations stored in the ViewConfigStore to execute action (MethodExpression) calls. @@ -38,193 +23,42 @@ public class ViewActionPhaseListener { private transient final Logger log = Logger.getLogger(ViewActionPhaseListener.class); @Inject - private ViewConfigStore viewConfigStore; - @Inject - private BeanManager beanManager; - - /** - * Execute any action annotations applicable to the RestoreView phase - * - * @param event - */ - public void observeRestoreView(@Observes @After @RestoreView PhaseEvent event) { - log.debug("After Restore View event"); - performObservation(event, PhaseIdType.RESTORE_VIEW); - } - - /** - * Execute any action annotations applicable to the ApplyRequestValues phase - * - * @param event - */ - public void observeApplyRequestValues(@Observes @Before @ApplyRequestValues PhaseEvent event) { - log.debug("After Apply Request Values event"); - performObservation(event, PhaseIdType.APPLY_REQUEST_VALUES); - } - - /** - * Execute any action annotations applicable to the ProcessValidations phase - * - * @param event - */ - public void observeProcessValidations(@Observes @Before @ProcessValidations PhaseEvent event) { - log.debug("After Process Validations event"); - performObservation(event, PhaseIdType.PROCESS_VALIDATIONS); - } - - /** - * Execute any action annotations applicable to the UpdateModelValues phase - * - * @param event - */ - public void observeUpdateModelValues(@Observes @Before @UpdateModelValues PhaseEvent event) { - log.debug("After Update Model Values event"); - performObservation(event, PhaseIdType.UPDATE_MODEL_VALUES); - } - - /** - * Execute any action annotations applicable to the InvokeApplication phase - * - * @param event - */ - public void observeInvokeApplication(@Observes @Before @InvokeApplication PhaseEvent event) { - log.debug("Before Render Response event"); - performObservation(event, PhaseIdType.INVOKE_APPLICATION); - } - - /** - * Execute any action annotations applicable to the RenderResponse phase - * - * @param event - */ - public void observeRenderResponse(@Observes @Before @RenderResponse PhaseEvent event) { - log.debug("Before Render Response event"); - performObservation(event, PhaseIdType.RENDER_RESPONSE); - } - - /** - * Inspect the annotations in the ViewConfigStore, executing any actions applicable to this phase - * - * @param event - * @param phaseIdType - */ - private void performObservation(PhaseEvent event, PhaseIdType phaseIdType) { - UIViewRoot viewRoot = (UIViewRoot) event.getFacesContext().getViewRoot(); - List actionsForPhase = getViewActionsForPhase(phaseIdType, viewRoot.getViewId()); - if (actionsForPhase != null) { - log.debugf("Enforcing on phase %s", phaseIdType); - execute(event.getFacesContext(), viewRoot, actionsForPhase); - } - } - - /** - * Retrieve all annotations from the ViewConfigStore for a given a JSF phase, and a view id, - * and where the annotation is qualified by @SecurityBindingType - * - * @param currentPhase - * @param viewId - * @return list of restrictions applicable to this viewId and PhaseTypeId - */ - public List getViewActionsForPhase(PhaseIdType currentPhase, String viewId) { - List allViewActionAnnotations = viewConfigStore.getAllQualifierData(viewId, ViewActionBindingType.class); - List applicableViewActionAnnotations = null; - for (Annotation annotation : allViewActionAnnotations) { - PhaseIdType defaultPhase = getDefaultPhase(viewId); - if (isAnnotationApplicableToPhase(annotation, currentPhase, defaultPhase)) { - if (applicableViewActionAnnotations == null) { // avoid spawning arrays at all phases of the lifecycle - applicableViewActionAnnotations = new ArrayList(); - } - applicableViewActionAnnotations.add(annotation); - } - } - return applicableViewActionAnnotations; - } - - /** - * Inspect an annotation to see if it specifies a view in which it should be. Fall back on default view otherwise. - * - * @param annotation - * @param currentPhase - * @param defaultPhases - * @return true if the annotation is applicable to this view and phase, false otherwise - */ - public boolean isAnnotationApplicableToPhase(Annotation annotation, PhaseIdType currentPhase, PhaseIdType defaultPhase) { - Method phaseAtViewActionMethod = getPhaseAtViewActionMethod(annotation); - PhaseIdType phasedId = null; - if (phaseAtViewActionMethod != null) { - log.debug("Annotation %s is using the phase method."); - phasedId = getViewActionPhaseId(phaseAtViewActionMethod, annotation); - } - Phase phaseQualifier = AnnotationInspector.getAnnotation(annotation.annotationType(), Phase.class, beanManager); - if (phaseQualifier != null) { - log.debug("Using Phase found in @Phase qualifier on the annotation."); - phasedId = phaseQualifier.value(); + private ViewControllerStore viewControllerStore; + + // TODO : should be executed after SecurityPhaseListener + public void observerBeforePhase(@Observes @Before PhaseEvent event) { + PhaseId phaseId = event.getPhaseId(); + log.debugf("Before {1} event", phaseId); + if (event.getFacesContext().getViewRoot() == null) { + log.debug("viewRoot null, skipping view actions"); + return; } - if (phasedId == null) { - log.debug("Falling back on default phase id"); - phasedId = defaultPhase; + List viewControllers = viewControllerStore.getControllerDescriptors(event.getFacesContext() + .getViewRoot().getViewId()); + for (int i = viewControllers.size(); --i >= 0;) { + ViewControllerDescriptor viewControllerDescriptor = viewControllers.get(i); + viewControllerDescriptor.executeBeforePhase(event.getPhaseId()); } - return phasedId == currentPhase; - } - - /** - * Get the default phases at which restrictions should be applied, by looking for a @Phase default value - * - * @param viewId - * @return default phase - */ - public PhaseIdType getDefaultPhase(String viewId) { - return PhaseDefault.DEFAULT_PHASE; - } - - /** - * Utility method to extract the "phase" method from an annotation - * - * @param annotation - * @return phaseAtViewActionMethod if found, null otherwise - */ - public Method getPhaseAtViewActionMethod(Annotation annotation) { - Method phaseAtViewActionMethod; - try { - phaseAtViewActionMethod = annotation.annotationType().getDeclaredMethod("phase"); - } catch (NoSuchMethodException ex) { - phaseAtViewActionMethod = null; - } catch (SecurityException ex) { - throw new IllegalArgumentException("phase method must be accessible", ex); + if (phaseId == PhaseId.RENDER_RESPONSE) { + for (int i = viewControllers.size(); --i >= 0;) { + ViewControllerDescriptor viewControllerDescriptor = viewControllers.get(i); + viewControllerDescriptor.executeBeforeRenderView(); + } } - return phaseAtViewActionMethod; } - /** - * Retrieve the default PhaseIdType defined by the phaseAtViewActionMethod in the annotation - * - * @param phaseAtViewActionMethod - * @param annotation - * @return PhaseIdType from the phaseAtViewActionMethod, null if empty - */ - public PhaseIdType getViewActionPhaseId(Method phaseAtViewActionMethod, Annotation annotation) { - PhaseIdType phaseId; - try { - phaseId = (PhaseIdType) phaseAtViewActionMethod.invoke(annotation); - } catch (IllegalAccessException ex) { - throw new IllegalArgumentException("phase method must be accessible", ex); - } catch (InvocationTargetException ex) { - throw new RuntimeException(ex); + public void observerAfterPhase(@Observes @After PhaseEvent event) { + PhaseId phaseId = event.getPhaseId(); + log.debugf("After {1} event", phaseId); + List viewControllers = viewControllerStore.getControllerDescriptors(event.getFacesContext() + .getViewRoot().getViewId()); + for (ViewControllerDescriptor viewControllerDescriptor : viewControllers) { + viewControllerDescriptor.executeAfterPhase(event.getPhaseId()); } - return phaseId; - } - - /** - * Execute the list of applicable view action annotations, TODO... - * - * @param context - * @param viewRoot - * @param annotations - */ - private void execute(FacesContext context, UIViewRoot viewRoot, List annotations) { - if (annotations == null || annotations.isEmpty()) { - log.debug("Annotations is null/empty"); - return; + if (phaseId == PhaseId.RENDER_RESPONSE) { + for (ViewControllerDescriptor viewControllerDescriptor : viewControllers) { + viewControllerDescriptor.executeAfterRenderView(); + } } } } diff --git a/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionStrategy.java b/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionStrategy.java new file mode 100644 index 0000000..676a956 --- /dev/null +++ b/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionStrategy.java @@ -0,0 +1,18 @@ +package org.jboss.seam.faces.view.action; + +/** + * Interface encapsulating view action implementation. + * + * The implementation can be : + *
    + *
  • a viewController method call.
  • + *
  • an annotated ViewActionBindingType method call.
  • + *
  • an El contained in a ViewAction annotation.
  • + *
  • ... or any other logic...
  • + *
+ * + * @author Adriàn Gonzalez + */ +public interface ViewActionStrategy { + public Object execute(); +} diff --git a/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionUtils.java b/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionUtils.java new file mode 100644 index 0000000..33f44d7 --- /dev/null +++ b/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionUtils.java @@ -0,0 +1,114 @@ +package org.jboss.seam.faces.view.action; + +import java.lang.annotation.Annotation; +import java.util.List; + +import javax.faces.event.PhaseId; + +import org.jboss.seam.faces.event.qualifier.After; +import org.jboss.seam.faces.event.qualifier.ApplyRequestValues; +import org.jboss.seam.faces.event.qualifier.Before; +import org.jboss.seam.faces.event.qualifier.InvokeApplication; +import org.jboss.seam.faces.event.qualifier.ProcessValidations; +import org.jboss.seam.faces.event.qualifier.RenderResponse; +import org.jboss.seam.faces.event.qualifier.UpdateModelValues; + +public class ViewActionUtils { + + // Utility class - no instanciation + private ViewActionUtils() { + } + + public static PhaseInstant getPhaseInstantOrDefault(List annotations, Object parentElement, PhaseInstant defaultInstant) { + PhaseInstant phaseInstant = getPhaseInstant(annotations, parentElement); + return phaseInstant!=null ? phaseInstant : defaultInstant; + } + + public static PhaseInstant getPhaseInstant(List annotations, Object parentElement) { + Boolean before = null; + PhaseId phaseId = null; + for (Annotation annotation : annotations) { + Class annotationType = annotation.annotationType(); + if (annotationType == Before.class) { + if (before != null) { + throw new IllegalStateException("invalid " + parentElement + + ". Cannot be annotated simultaneously with multiples @Before and @After"); + } + before = true; + } else if (annotationType == After.class) { + if (before != null) { + throw new IllegalStateException("invalid " + parentElement + + ". Cannot be annotated simultaneously with multiples @Before and @After"); + } + before = false; + } else if (annotationType == ApplyRequestValues.class) { + if (phaseId != null) { + throw new IllegalStateException("invalid " + parentElement + + ". Cannot be annotated simultaneously with multiples JSF lifecycle annotations (" + annotationType + + " and " + phaseId + ")"); + } + phaseId = PhaseId.APPLY_REQUEST_VALUES; + } else if (annotationType == ProcessValidations.class) { + if (phaseId != null) { + throw new IllegalStateException("invalid " + parentElement + + ". Cannot be annotated simultaneously with multiples JSF lifecycle annotations (" + annotationType + + " and " + phaseId + ")"); + } + phaseId = PhaseId.PROCESS_VALIDATIONS; + } else if (annotationType == UpdateModelValues.class) { + if (phaseId != null) { + throw new IllegalStateException("invalid " + parentElement + + ". Cannot be annotated simultaneously with multiples JSF lifecycle annotations (" + annotationType + + " and " + phaseId + ")"); + } + phaseId = PhaseId.UPDATE_MODEL_VALUES; + } else if (annotationType == InvokeApplication.class) { + if (phaseId != null) { + throw new IllegalStateException("invalid " + parentElement + + ". Cannot be annotated simultaneously with multiples JSF lifecycle annotations (" + annotationType + + " and " + phaseId + ")"); + } + phaseId = PhaseId.INVOKE_APPLICATION; + } else if (annotationType == RenderResponse.class) { + if (phaseId != null) { + throw new IllegalStateException("invalid " + parentElement + + ". Cannot be annotated simultaneously with multiples JSF lifecycle annotations (" + annotationType + + " and " + phaseId + ")"); + } + phaseId = PhaseId.RENDER_RESPONSE; + } else if (annotationType == BeforeRenderReponse.class) { + if (phaseId != null) { + throw new IllegalStateException("invalid " + parentElement + + ". Cannot be annotated simultaneously with multiples JSF lifecycle annotations (" + annotationType + + " and " + phaseId + ")"); + } + if (before != null) { + throw new IllegalStateException("invalid " + parentElement + + ". Cannot be annotated simultaneously with multiples @Before/@After and " + annotationType); + } + phaseId = PhaseId.RENDER_RESPONSE; + before = true; + } else if (annotationType == AfterRenderView.class) { + if (phaseId != null) { + throw new IllegalStateException("invalid " + parentElement + + ". Cannot be annotated simultaneously with multiples JSF lifecycle annotations (" + annotationType + + " and " + phaseId + ")"); + } + if (before != null) { + throw new IllegalStateException("invalid " + parentElement + + ". Cannot be annotated simultaneously with multiples @Before/@After and " + annotationType); + } + phaseId = PhaseId.RENDER_RESPONSE; + before = false; + } + } + if (before==null && phaseId==null) { + return null; + } else if (before!=null && phaseId!=null) { + return new PhaseInstant(phaseId, before); + } else { + throw new IllegalStateException("invalid " + parentElement + + ". both phaseId and @Before/@After must be specified {phaseId: " + phaseId+", before: "+before+"}"); + } + } +} diff --git a/impl/src/main/java/org/jboss/seam/faces/view/action/ViewControllerDescriptor.java b/impl/src/main/java/org/jboss/seam/faces/view/action/ViewControllerDescriptor.java new file mode 100644 index 0000000..68b813b --- /dev/null +++ b/impl/src/main/java/org/jboss/seam/faces/view/action/ViewControllerDescriptor.java @@ -0,0 +1,315 @@ +package org.jboss.seam.faces.view.action; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.el.MethodExpression; +import javax.enterprise.context.spi.CreationalContext; +import javax.enterprise.inject.spi.AnnotatedMethod; +import javax.enterprise.inject.spi.AnnotatedType; +import javax.enterprise.inject.spi.Bean; +import javax.enterprise.inject.spi.BeanManager; +import javax.faces.context.FacesContext; +import javax.faces.event.PhaseId; + +import org.jboss.seam.faces.view.config.ViewConfig; +import org.jboss.solder.logging.Logger; +import org.jboss.solder.reflection.annotated.InjectableMethod; + +/** + * Information about a particular controller. + * + * A Controller is managed bean which is associated to a given view (a given {@link ViewConfig}). + * + * @author Adriàn Gonzalez + */ +public class ViewControllerDescriptor { + + private transient final Logger log = Logger.getLogger(ViewControllerDescriptor.class); + + private String viewId; + private Class viewControllerClass; + private BeanManager beanManager; + + // call this one in an additional actionListener (faces-config.xml) + private List beforePageActionMethods = new ArrayList(); + private List beforeRenderViewMethods = new ArrayList(); + private List afterRenderViewMethods = new ArrayList(); + private Map> phaseMethods = new HashMap>(); + + /** + * Creates descriptor. + * + * Lifecycle callback registration is up to the caller. + * + * Note : beanManager parameter is horrible, should be a way to send beanManager more elegantly + */ + public ViewControllerDescriptor(String viewId, BeanManager beanManager) { + this.viewId = viewId; + this.beanManager = beanManager; + log.debugf("Created viewController #0", this); + } + + /** + * Creates descriptor by reading controllerViewClass methods. + * + * Register controllerViewClass lifecycle callback methods. + * + * Note : beanManager parameter is horrible, should be a way to send beanManager more elegantly + */ + public ViewControllerDescriptor(String viewId, Class viewControllerClass, BeanManager beanManager) { + this.viewId = viewId; + this.viewControllerClass = viewControllerClass; + this.beanManager = beanManager; + registerCallbacks(); + log.debugf("Created viewController #0", this); + } + + /** + * Register any lifecycle methods declared in this viewController class (or inherited). + */ + private void registerCallbacks() { + Class current = viewControllerClass; + while (current != Object.class) { + for (Method method : current.getDeclaredMethods()) { +// if (method.isAnnotationPresent(BeforeRenderView.class)) { +// beforeRenderViewMethods.add(new MethodInvoker(method, beanManager)); +// } +// if (method.isAnnotationPresent(AfterRenderView.class)) { +// afterRenderViewMethods.add(new MethodInvoker(method, beanManager)); +// } + PhaseInstant phaseInstant = ViewActionUtils.getPhaseInstant(Arrays.asList(method.getAnnotations()), method); + if (phaseInstant != null) { + addMethod(phaseInstant, new MethodInvoker(method, beanManager)); + } + } + current = current.getSuperclass(); + } + } + + public void executeBeforePhase(PhaseId phaseId) { + List actions = phaseMethods.get(new PhaseInstant(phaseId, true)); + if (actions != null) { + for (ViewActionStrategy action : actions) { + action.execute(); + } + } + } + + public void executeAfterPhase(PhaseId phaseId) { + List actions = phaseMethods.get(new PhaseInstant(phaseId, false)); + if (actions != null) { + for (ViewActionStrategy action : actions) { + action.execute(); + } + } + } + + public void executeBeforePageAction() { + throw new NoSuchMethodError("unimplemented"); + } + + public void executeBeforeRenderView() { + for (ViewActionStrategy invoker : getBeforeRenderViewMethods()) { + invoker.execute(); + } + } + + public void executeAfterRenderView() { + for (ViewActionStrategy invoker : getAfterRenderViewMethods()) { + invoker.execute(); + } + } + + public List getBeforePageActionMethods() { + return beforePageActionMethods; + } + + public void setBeforePageActionMethods(List beforePageActionMethods) { + this.beforePageActionMethods = beforePageActionMethods; + } + + public void addBeforeRenderViewMethod(ViewActionStrategy beforeRenderViewMethod) { + beforeRenderViewMethods.add(beforeRenderViewMethod); + } + + public List getBeforeRenderViewMethods() { + return beforeRenderViewMethods; + } + + public void setBeforeRenderViewMethods(List beforeRenderViewMethods) { + this.beforeRenderViewMethods = beforeRenderViewMethods; + } + + public List getAfterRenderViewMethods() { + return afterRenderViewMethods; + } + + public void setAfterRenderViewMethods(List afterRenderViewMethods) { + this.afterRenderViewMethods = afterRenderViewMethods; + } + + public void addMethod(PhaseInstant phaseInstant, ViewActionStrategy method) { + List methods = phaseMethods.get(phaseInstant); + if (methods == null) { + methods = new ArrayList(); + phaseMethods.put(phaseInstant, methods); + } + methods.add(method); + } + + public Map> getPhaseMethods() { + return phaseMethods; + } + + public void setPhaseMethods(Map> phaseMethods) { + this.phaseMethods = phaseMethods; + } + + public String getViewId() { + return viewId; + } + + public void setViewId(String viewId) { + this.viewId = viewId; + } + + public Class getViewControllerClass() { + return viewControllerClass; + } + + public void setViewControllerClass(Class viewControllerClass) { + this.viewControllerClass = viewControllerClass; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(super.toString()); + builder.append("{viewId: ").append(getViewId()).append(", viewControllerClass: ").append(getViewControllerClass()) + .append(", beforePageActionMethods: {").append(getBeforePageActionMethods()) + .append("}, beforeRenderViewMethods: ").append(getAfterRenderViewMethods()) + .append("}, afterRenderViewMethods: {").append(getAfterRenderViewMethods()).append("}, phaseMethods: {") + .append(getPhaseMethods()).append("}}"); + return builder.toString(); + } + + /** + * Invokes method on a CDI bean. + * + * Note : copy from Seam Security's SecurityExtension class. Should be extracted into common utility. + */ + public static class AnnotatedMethodInvoker implements ViewActionStrategy { + private Bean targetBean; + private BeanManager beanManager; + private InjectableMethod injectableMethod; + private AnnotatedMethod annotatedMethod; + + public AnnotatedMethodInvoker(AnnotatedMethod annotatedMethod, BeanManager beanManager) { + this.beanManager = beanManager; + this.annotatedMethod = annotatedMethod; + } + + public AnnotatedMethod getAnnotatedMethod() { + return annotatedMethod; + } + + public void setAnnotatedMethod(AnnotatedMethod annotatedMethod) { + this.annotatedMethod = annotatedMethod; + } + + public BeanManager getBeanManager() { + return beanManager; + } + + public Object execute() { + if (targetBean == null) { + lookupTargetBean(); + } + CreationalContext cc = beanManager.createCreationalContext(targetBean); + Object reference = beanManager.getReference(targetBean, getAnnotatedMethod().getJavaMember().getDeclaringClass(), + cc); + return injectableMethod.invoke(reference, cc, null); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private synchronized void lookupTargetBean() { + if (targetBean == null) { + AnnotatedMethod annotatedMethod = getAnnotatedMethod(); + Method method = annotatedMethod.getJavaMember(); + Set> beans = beanManager.getBeans(method.getDeclaringClass()); + if (beans.size() == 1) { + targetBean = beans.iterator().next(); + } else if (beans.isEmpty()) { + throw new IllegalStateException("Exception looking up method bean - " + "no beans found for method [" + + method.getDeclaringClass() + "." + method.getName() + "]"); + } else if (beans.size() > 1) { + throw new IllegalStateException("Exception looking up method bean - " + "multiple beans found for method [" + + method.getDeclaringClass().getName() + "." + method.getName() + "]"); + } + injectableMethod = new InjectableMethod(annotatedMethod, targetBean, beanManager); + } + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(this.getClass().getSimpleName()); + builder.append("{method: ").append(getAnnotatedMethod()).append("}"); + return builder.toString(); + } + } + + /** + * Invokes method on a CDI bean. + * + * Note : copy from Seam Security's SecurityExtension class. Should be extracted into common utility. + */ + public static class MethodInvoker extends AnnotatedMethodInvoker { + + public MethodInvoker(Method method, BeanManager beanManager) { + super(null, beanManager); + setAnnotatedMethod(convert(method)); + + } + + private AnnotatedMethod convert(Method method) { + AnnotatedType annotatedType = getBeanManager().createAnnotatedType(method.getDeclaringClass()); + AnnotatedMethod annotatedMethod = null; + for (AnnotatedMethod current : annotatedType.getMethods()) { + if (current.getJavaMember().equals(method)) { + annotatedMethod = current; + } + } + if (annotatedMethod == null) { + throw new IllegalStateException("No matching annotated method found for method : " + method); + } + return annotatedMethod; + } + } + + /** + * Invokes a method expression. + */ + public static class MethodExpressionInvoker implements ViewActionStrategy { + private MethodExpression methodExpression; + private String methodExpressionAsString; + + public MethodExpressionInvoker(String methodExpressionAsString) { + this.methodExpressionAsString = methodExpressionAsString; + } + + @Override + public Object execute() { + FacesContext facesContext = FacesContext.getCurrentInstance(); + if (methodExpression == null) { + methodExpression = facesContext.getApplication().getExpressionFactory() + .createMethodExpression(facesContext.getELContext(), methodExpressionAsString, null, new Class[] {}); + } + return methodExpression.invoke(FacesContext.getCurrentInstance().getELContext(), null); + } + } +} diff --git a/impl/src/main/java/org/jboss/seam/faces/view/action/ViewControllerExtension.java b/impl/src/main/java/org/jboss/seam/faces/view/action/ViewControllerExtension.java new file mode 100644 index 0000000..6eedc5e --- /dev/null +++ b/impl/src/main/java/org/jboss/seam/faces/view/action/ViewControllerExtension.java @@ -0,0 +1,80 @@ +package org.jboss.seam.faces.view.action; + +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.enterprise.event.Observes; +import javax.enterprise.inject.spi.AnnotatedMethod; +import javax.enterprise.inject.spi.AnnotatedType; +import javax.enterprise.inject.spi.Extension; +import javax.enterprise.inject.spi.ProcessAnnotatedType; + +/** + * Scans for viewController classes and view actions. + * + * @author Adriàn Gonzalez + */ +public class ViewControllerExtension implements Extension { + + private final Map> descriptors = new HashMap>(); + + public void processAnnotatedType(@Observes ProcessAnnotatedType event) { + AnnotatedType tp = event.getAnnotatedType(); + for (final AnnotatedMethod m : tp.getMethods()) { + for (final Annotation annotation : m.getAnnotations()) { + if (annotation.annotationType().isAnnotationPresent(ViewActionBindingType.class)) { + Object viewConfigValue = getValue(annotation); + if (viewConfigValue == null) { + throw new IllegalArgumentException("Annotation " + annotation + + " invalid : no view specified"); + } + List actions = descriptors.get(viewConfigValue); + if (actions == null) { + actions = new ArrayList(); + descriptors.put(viewConfigValue, actions); + } + ViewActionBindingTypeDescriptor descriptor = new ViewActionBindingTypeDescriptor(m, annotation, viewConfigValue); + actions.add(descriptor); + } + } + } + } + + public Map> getViewActionBindingTypeDescriptors() { + return descriptors; + } + + + // Utility methods for viewAction, TODO move this block out of this class + + /** + * Retrieve the view defined by the value() method in the annotation + * + * @param annotation + * @return the result of value() call + * @throws IllegalArgumentException if no value() method was found + */ + private Object getValue(Annotation annotation) { + Method valueMethod; + try { + valueMethod = annotation.annotationType().getDeclaredMethod("value"); + } catch (NoSuchMethodException ex) { + throw new IllegalArgumentException("value method must be declared and must resolve to a valid view", ex); + } catch (SecurityException ex) { + throw new IllegalArgumentException("value method must be accessible", ex); + } + try { + return valueMethod.invoke(annotation); + } catch (IllegalAccessException ex) { + throw new IllegalArgumentException("value method must be accessible", ex); + } catch (InvocationTargetException ex) { + throw new RuntimeException(ex); + } + } + +} diff --git a/impl/src/main/java/org/jboss/seam/faces/view/action/ViewControllerStore.java b/impl/src/main/java/org/jboss/seam/faces/view/action/ViewControllerStore.java new file mode 100644 index 0000000..362026b --- /dev/null +++ b/impl/src/main/java/org/jboss/seam/faces/view/action/ViewControllerStore.java @@ -0,0 +1,172 @@ +package org.jboss.seam.faces.view.action; + +import static org.jboss.seam.faces.view.action.PhaseInstant.BEFORE_RENDER_RESPONSE; + +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import javax.enterprise.inject.spi.BeanManager; +import javax.inject.Inject; + +import org.jboss.seam.faces.view.config.ViewConfigDescriptor; +import org.jboss.seam.faces.view.config.ViewConfigStore; + +/** + * Data store for view controllers. + * + * @author Adriàn gonzalez + */ +public class ViewControllerStore { + /** map containing view pattern / controller */ + private Map> viewPatternControllerDescriptors = new HashMap>(); + private ConcurrentHashMap> viewControllerDescriptorsCache = new ConcurrentHashMap>(); + + /** + * Initialization : Retrieves any ViewControllers associated to ViewConfig objects + */ + @Inject + public void setup(ViewControllerExtension viewControllerExtension, ViewConfigStore viewConfigStore, BeanManager beanManager) { + registerViewControllers(viewConfigStore, beanManager); + registerViewActions(viewConfigStore, beanManager); + registerViewActionBindingTypes(viewControllerExtension, viewConfigStore, beanManager); + } + + private void registerViewControllers(ViewConfigStore viewConfigStore, BeanManager beanManager) { + Map views = viewConfigStore.getAllAnnotationViewMap(ViewController.class); + for (Map.Entry entry : views.entrySet()) { + ViewController annotation = (ViewController) entry.getValue(); + if (annotation.value() == null) { + throw new IllegalArgumentException("Invalid ViewConfig for view '" + entry.getKey() + + "' : @ViewController must have a non null value."); + } + for (Class viewControllerClass : annotation.value()) { + ViewControllerDescriptor viewControllerDescriptor = createViewControllerDescriptor(entry.getKey(), + viewControllerClass, beanManager); + addControllerDescriptor(viewControllerDescriptor); + } + } + } + + private void registerViewActionBindingTypes(ViewControllerExtension viewControllerExtension, + ViewConfigStore viewConfigStore, BeanManager beanManager) { + List viewConfigDescriptors = viewConfigStore.getAllViewConfigDescriptors(); + for (ViewConfigDescriptor viewConfigDescriptor : viewConfigDescriptors) { + List viewActionBindingTypes = new ArrayList(); + for (Object value : viewConfigDescriptor.getValues()) { + List current = viewControllerExtension.getViewActionBindingTypeDescriptors() + .get(value); + if (current != null) { + viewActionBindingTypes.addAll(current); + } + } + if (viewActionBindingTypes.size() > 0) { + ViewControllerDescriptor viewControllerDescriptor = new ViewControllerDescriptor( + viewConfigDescriptor.getViewId(), beanManager); + for (ViewActionBindingTypeDescriptor viewActionBindingTypeDescriptor : viewActionBindingTypes) { + viewControllerDescriptor.addMethod( + viewActionBindingTypeDescriptor.getPhaseInstant(), + new ViewControllerDescriptor.AnnotatedMethodInvoker(viewActionBindingTypeDescriptor + .getAnnotatedMethod(), beanManager)); + } + addControllerDescriptor(viewControllerDescriptor); + } + } + } + + private void registerViewActions(ViewConfigStore viewConfigStore, BeanManager beanManager) { + List viewConfigDescriptors = viewConfigStore.getAllViewConfigDescriptors(); + for (ViewConfigDescriptor viewConfigDescriptor : viewConfigDescriptors) { + ViewAction viewAction = viewConfigDescriptor.getMetaData(ViewAction.class); + // TODO : don't know using annotations @Before / @RenderResponse / ... with @ViewAction + // is a good idea (possible collision with future annotations ?) + // would it be better to add attributes to @ViewAction ? + if (viewAction != null) { + ViewControllerDescriptor viewControllerDescriptor = new ViewControllerDescriptor( + viewConfigDescriptor.getViewId(), beanManager); + PhaseInstant phaseInstant = ViewActionUtils.getPhaseInstantOrDefault(viewConfigDescriptor.getMetaData(), + viewConfigDescriptor, BEFORE_RENDER_RESPONSE); + viewControllerDescriptor.addMethod(phaseInstant, new ViewControllerDescriptor.MethodExpressionInvoker( + viewAction.value())); + addControllerDescriptor(viewControllerDescriptor); + } + } + } + + private ViewControllerDescriptor createViewControllerDescriptor(String viewId, Class controllerViewClass, + BeanManager beanManager) { + return new ViewControllerDescriptor(viewId, controllerViewClass, beanManager); + } + + public void addControllerDescriptor(ViewControllerDescriptor controllerDescriptor) { + List descriptors = viewPatternControllerDescriptors.get(controllerDescriptor.getViewId()); + if (descriptors == null) { + descriptors = new ArrayList(); + } + descriptors.add(controllerDescriptor); + viewPatternControllerDescriptors.put(controllerDescriptor.getViewId(), descriptors); + } + + /** + * Returns contollers matching a viewId. + * + * Controllers are ordered from best matching viewId (longest) to least matching one. + */ + public List getControllerDescriptors(String viewId) { + List controllers = viewControllerDescriptorsCache.get(viewId); + if (controllers == null) { + controllers = new ArrayList(); + List viewPatterns = findViewsWithPatternsThatMatch(viewId, viewPatternControllerDescriptors.keySet()); + for (String viewPattern : viewPatterns) { + List viewPatternControllers = viewPatternControllerDescriptors.get(viewPattern); + controllers.addAll(viewPatternControllers); + } + viewControllerDescriptorsCache.putIfAbsent(viewId, controllers); + } + return controllers; + } + + // Copied from ViewConfigStoreImpl : extract into utility method // + + private List findViewsWithPatternsThatMatch(String viewId, Set viewPatterns) { + List resultingViews = new ArrayList(); + for (String viewPattern : viewPatterns) { + if (viewPattern.endsWith("*")) { + String cutView = viewPattern.substring(0, viewPattern.length() - 1); + if (viewId.startsWith(cutView)) { + resultingViews.add(viewPattern); + } + } else { + if (viewPattern.equals(viewId)) { + resultingViews.add(viewPattern); + } + } + } + // sort the keys by length, longest is the most specific and so should go first + Collections.sort(resultingViews, StringLengthComparator.INSTANCE); + return resultingViews; + } + + private static class StringLengthComparator implements Comparator { + + @Override + public int compare(String o1, String o2) { + if (o1.length() > o2.length()) { + return -1; + } + if (o1.length() < o2.length()) { + return 1; + } + return 0; + } + + public static final StringLengthComparator INSTANCE = new StringLengthComparator(); + + } +} diff --git a/impl/src/main/java/org/jboss/seam/faces/view/config/ViewConfigExtension.java b/impl/src/main/java/org/jboss/seam/faces/view/config/ViewConfigExtension.java index 2cdc7a7..5356c13 100644 --- a/impl/src/main/java/org/jboss/seam/faces/view/config/ViewConfigExtension.java +++ b/impl/src/main/java/org/jboss/seam/faces/view/config/ViewConfigExtension.java @@ -18,26 +18,20 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; -import java.util.Set; import javax.enterprise.event.Observes; -import javax.enterprise.inject.spi.AnnotatedMethod; import javax.enterprise.inject.spi.AnnotatedType; import javax.enterprise.inject.spi.Extension; import javax.enterprise.inject.spi.ProcessAnnotatedType; -import org.jboss.seam.faces.view.action.ViewActionBindingType; import org.jboss.solder.logging.Logger; /** * Extension that scans enums for view specific configuration - * + * * @author stuart * @author Brian Leathem */ @@ -45,9 +39,7 @@ public class ViewConfigExtension implements Extension { private transient final Logger log = Logger.getLogger(ViewConfigExtension.class); - private final Map> data = new HashMap>(); - - private final Map viewFieldValueToViewId = new HashMap(); + private final Map data = new HashMap(); public void processAnnotatedType(@Observes ProcessAnnotatedType event) { AnnotatedType tp = event.getAnnotatedType(); @@ -65,81 +57,43 @@ public void processAnnotatedType(@Observes ProcessAnnotatedType event) { log.warn("ViewConfig annotation should only be applied to interfaces, and [" + tp.getJavaClass() + "] is not an interface."); } else { - for (Class clazz : tp.getJavaClass().getClasses()) { + for (Class clazz : tp.getJavaClass().getClasses()) { for (Field enumm : clazz.getFields()) if (enumm.isAnnotationPresent(ViewPattern.class)) { ViewPattern viewConfig = enumm.getAnnotation(ViewPattern.class); - Set viewPattern = new HashSet(); + String viewId = viewConfig.value(); + ViewConfigDescriptor viewConfigDescriptor = data.get(viewId); + if (viewConfigDescriptor == null) { + viewConfigDescriptor = new ViewConfigDescriptor(viewId, getViewFieldValue(enumm)); + data.put(viewId, viewConfigDescriptor); + } for (Annotation a : enumm.getAnnotations()) { if (a.annotationType() != ViewPattern.class) { - viewPattern.add(a); + viewConfigDescriptor.addMetaData(a); } } - data.put(viewConfig.value(), viewPattern); - viewFieldValueToViewId.put(getViewFieldValue(enumm), viewConfig.value()); } } } } - // viewAction processing - for (final AnnotatedMethod m : tp.getMethods()) { - for (final Annotation annotation : m.getAnnotations()) { - if (annotation.annotationType().isAnnotationPresent(ViewActionBindingType.class)) { - Object viewField = getValue (annotation); - String viewId = viewFieldValueToViewId.get(viewField); - if (viewId == null) { - throw new IllegalArgumentException("Annotation "+annotation+" invalid : the view specified" - + "("+viewField+") doesn't correspond to a registered view in an annotated @ViewConfig class/interface."); - } - data.get(viewId).add(annotation); - } - } - } } /** - * Returns the value of a view field. - * @throws IllegalArgumentException if an error happens + * Returns the value of a view field. + * + * @throws IllegalArgumentException if an error happens */ private Object getViewFieldValue(Field enumm) { try { - return enumm.get(null); - } catch (IllegalArgumentException e) { - throw new IllegalArgumentException("Invalid view field "+enumm+" - error getting value "+e.toString(), e); - } catch (IllegalAccessException e) { - throw new IllegalArgumentException("Invalid view field "+enumm+" - error getting value "+e.toString(), e); - } - } - - public Map> getData() { - return Collections.unmodifiableMap(data); + return enumm.get(null); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Invalid view field " + enumm + " - error getting value " + e.toString(), e); + } catch (IllegalAccessException e) { + throw new IllegalArgumentException("Invalid view field " + enumm + " - error getting value " + e.toString(), e); + } } - - // Utility methods for viewAction, TODO move this block out of this class - - /** - * Retrieve the view defined by the value() method in the annotation - * - * @param annotation - * @return the result of value() call - * @throws IllegalArgumentException if no value() method was found - */ - private Object getValue(Annotation annotation) { - Method valueMethod; - try { - valueMethod = annotation.annotationType().getDeclaredMethod("value"); - } catch (NoSuchMethodException ex) { - throw new IllegalArgumentException("value method must be declared and must resolve to a valid view", ex); - } catch (SecurityException ex) { - throw new IllegalArgumentException("value method must be accessible", ex); - } - try { - return valueMethod.invoke(annotation); - } catch (IllegalAccessException ex) { - throw new IllegalArgumentException("value method must be accessible", ex); - } catch (InvocationTargetException ex) { - throw new RuntimeException(ex); - } + public Map getData() { + return Collections.unmodifiableMap(data); } } diff --git a/impl/src/main/java/org/jboss/seam/faces/view/config/ViewConfigStoreImpl.java b/impl/src/main/java/org/jboss/seam/faces/view/config/ViewConfigStoreImpl.java index 44de799..84f89af 100644 --- a/impl/src/main/java/org/jboss/seam/faces/view/config/ViewConfigStoreImpl.java +++ b/impl/src/main/java/org/jboss/seam/faces/view/config/ViewConfigStoreImpl.java @@ -22,7 +22,6 @@ import java.util.Comparator; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -47,6 +46,7 @@ public class ViewConfigStoreImpl implements ViewConfigStore { private final ConcurrentHashMap, ConcurrentHashMap>> annotationCache = new ConcurrentHashMap, ConcurrentHashMap>>(); private final ConcurrentHashMap, ConcurrentHashMap>> qualifierCache = new ConcurrentHashMap, ConcurrentHashMap>>(); + private Map viewConfigDescriptors = new ConcurrentHashMap(); private final ConcurrentHashMap, ConcurrentHashMap> viewPatternDataByAnnotation = new ConcurrentHashMap, ConcurrentHashMap>(); private final ConcurrentHashMap, ConcurrentHashMap>> viewPatternDataByQualifier = new ConcurrentHashMap, ConcurrentHashMap>>(); @@ -58,9 +58,10 @@ public class ViewConfigStoreImpl implements ViewConfigStore { */ @Inject public void setup(ViewConfigExtension extension) { - for (Entry> e : extension.getData().entrySet()) { - for (Annotation i : e.getValue()) { - addAnnotationData(e.getKey(), i); + viewConfigDescriptors = extension.getData(); + for (ViewConfigDescriptor viewConfigDescriptor : viewConfigDescriptors.values()) { + for (Annotation metaData : viewConfigDescriptor.getMetaData()) { + addAnnotationData(viewConfigDescriptor.getViewId(), metaData); } } } @@ -132,6 +133,11 @@ public List getAllQualifierData(String viewId, Class getAllViewConfigDescriptors() { + return Collections.unmodifiableList(new ArrayList(viewConfigDescriptors.values())); + } + private List prepareAnnotationCache(String viewId, Class annotationType, ConcurrentHashMap, ConcurrentHashMap>> cache, ConcurrentHashMap, ConcurrentHashMap> viewPatternData) { diff --git a/impl/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension b/impl/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension index f37ca0c..b6c1bcb 100644 --- a/impl/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension +++ b/impl/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension @@ -4,5 +4,6 @@ org.jboss.seam.faces.context.ViewScopedExtension org.jboss.seam.faces.context.RenderScopedExtension org.jboss.seam.faces.context.FacesAnnotationsAdapterExtension org.jboss.seam.faces.view.config.ViewConfigExtension +org.jboss.seam.faces.view.action.ViewControllerExtension org.jboss.seam.faces.projectstage.ProjectStageExtension From aacce800241f556cec22580bf884aa9df82b72bf Mon Sep 17 00:00:00 2001 From: gonzalad Date: Wed, 2 Nov 2011 20:48:48 +0100 Subject: [PATCH 3/9] Removed unneeded files and corrected ViewActionUtils --- .../seam/faces/view/action/Condition.java | 21 --------- .../seam/faces/view/action/Immediate.java | 22 ---------- .../seam/faces/view/action/OnPostback.java | 22 ---------- .../jboss/seam/faces/view/action/Phase.java | 23 ---------- .../seam/faces/view/action/PhaseDefault.java | 22 ---------- .../faces/view/action/ViewActionUtils.java | 43 ++++++++++--------- 6 files changed, 22 insertions(+), 131 deletions(-) delete mode 100644 api/src/main/java/org/jboss/seam/faces/view/action/Condition.java delete mode 100644 api/src/main/java/org/jboss/seam/faces/view/action/Immediate.java delete mode 100644 api/src/main/java/org/jboss/seam/faces/view/action/OnPostback.java delete mode 100644 api/src/main/java/org/jboss/seam/faces/view/action/Phase.java delete mode 100644 api/src/main/java/org/jboss/seam/faces/view/action/PhaseDefault.java diff --git a/api/src/main/java/org/jboss/seam/faces/view/action/Condition.java b/api/src/main/java/org/jboss/seam/faces/view/action/Condition.java deleted file mode 100644 index a5448e1..0000000 --- a/api/src/main/java/org/jboss/seam/faces/view/action/Condition.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.jboss.seam.faces.view.action; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Conditionnally executes a viewAction. - * - * TODO : refactor me, I'm not type-safe ! - * - * @author Adriàn Gonzalez - */ -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface Condition { - public String condition = null; -} diff --git a/api/src/main/java/org/jboss/seam/faces/view/action/Immediate.java b/api/src/main/java/org/jboss/seam/faces/view/action/Immediate.java deleted file mode 100644 index 6f1a98d..0000000 --- a/api/src/main/java/org/jboss/seam/faces/view/action/Immediate.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.jboss.seam.faces.view.action; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.jboss.seam.faces.component.UIViewAction; - -/** - * Can be used instead of Phase. - * - * @see UIViewAction#isImmediate() - * @author Adriàn Gonzalez - */ -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface Immediate { - public Boolean immediate = null; -} diff --git a/api/src/main/java/org/jboss/seam/faces/view/action/OnPostback.java b/api/src/main/java/org/jboss/seam/faces/view/action/OnPostback.java deleted file mode 100644 index 156a7d2..0000000 --- a/api/src/main/java/org/jboss/seam/faces/view/action/OnPostback.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.jboss.seam.faces.view.action; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.jboss.seam.faces.component.UIViewAction; - -/** - * Determines if viewAction is executed on postback. - * - * @see UIViewAction#isOnPostback() - * @author Adriàn Gonzalez - */ -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface OnPostback { - public Boolean onPostback = false; -} diff --git a/api/src/main/java/org/jboss/seam/faces/view/action/Phase.java b/api/src/main/java/org/jboss/seam/faces/view/action/Phase.java deleted file mode 100644 index 28d0487..0000000 --- a/api/src/main/java/org/jboss/seam/faces/view/action/Phase.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.jboss.seam.faces.view.action; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.jboss.seam.faces.component.UIViewAction; -import org.jboss.seam.faces.event.PhaseIdType; - -/** - * Phase on which a viewAction is executed. - * - * @see UIViewAction#getPhase() - * @author Adriàn Gonzalez - */ -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface Phase { - public PhaseIdType value() default PhaseIdType.RENDER_RESPONSE; -} diff --git a/api/src/main/java/org/jboss/seam/faces/view/action/PhaseDefault.java b/api/src/main/java/org/jboss/seam/faces/view/action/PhaseDefault.java deleted file mode 100644 index b21e929..0000000 --- a/api/src/main/java/org/jboss/seam/faces/view/action/PhaseDefault.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.jboss.seam.faces.view.action; - -import org.jboss.seam.faces.event.PhaseIdType; - -/** - * The Default values for Phase annotation, extracted as constant. - * - * @author Adriàn Gonzalez - */ -public class PhaseDefault { - public static final PhaseIdType DEFAULT_PHASE; - - static { - try { - DEFAULT_PHASE = (PhaseIdType) Phase.class.getMethod("value").getDefaultValue(); - } catch (NoSuchMethodException ex) { - throw new IllegalStateException("Error initialising values", ex); - } catch (SecurityException ex) { - throw new IllegalStateException("Error initialising values", ex); - } - } -} diff --git a/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionUtils.java b/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionUtils.java index 33f44d7..6a2fea6 100644 --- a/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionUtils.java +++ b/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionUtils.java @@ -19,11 +19,12 @@ public class ViewActionUtils { private ViewActionUtils() { } - public static PhaseInstant getPhaseInstantOrDefault(List annotations, Object parentElement, PhaseInstant defaultInstant) { + public static PhaseInstant getPhaseInstantOrDefault(List annotations, Object parentElement, + PhaseInstant defaultInstant) { PhaseInstant phaseInstant = getPhaseInstant(annotations, parentElement); - return phaseInstant!=null ? phaseInstant : defaultInstant; + return phaseInstant != null ? phaseInstant : defaultInstant; } - + public static PhaseInstant getPhaseInstant(List annotations, Object parentElement) { Boolean before = null; PhaseId phaseId = null; @@ -44,43 +45,43 @@ public static PhaseInstant getPhaseInstant(List annotations, Object } else if (annotationType == ApplyRequestValues.class) { if (phaseId != null) { throw new IllegalStateException("invalid " + parentElement - + ". Cannot be annotated simultaneously with multiples JSF lifecycle annotations (" + annotationType - + " and " + phaseId + ")"); + + ". Cannot be annotated simultaneously with multiples JSF lifecycle annotations (" + + annotationType + " and " + phaseId + ")"); } phaseId = PhaseId.APPLY_REQUEST_VALUES; } else if (annotationType == ProcessValidations.class) { if (phaseId != null) { throw new IllegalStateException("invalid " + parentElement - + ". Cannot be annotated simultaneously with multiples JSF lifecycle annotations (" + annotationType - + " and " + phaseId + ")"); + + ". Cannot be annotated simultaneously with multiples JSF lifecycle annotations (" + + annotationType + " and " + phaseId + ")"); } phaseId = PhaseId.PROCESS_VALIDATIONS; } else if (annotationType == UpdateModelValues.class) { if (phaseId != null) { throw new IllegalStateException("invalid " + parentElement - + ". Cannot be annotated simultaneously with multiples JSF lifecycle annotations (" + annotationType - + " and " + phaseId + ")"); + + ". Cannot be annotated simultaneously with multiples JSF lifecycle annotations (" + + annotationType + " and " + phaseId + ")"); } phaseId = PhaseId.UPDATE_MODEL_VALUES; } else if (annotationType == InvokeApplication.class) { if (phaseId != null) { throw new IllegalStateException("invalid " + parentElement - + ". Cannot be annotated simultaneously with multiples JSF lifecycle annotations (" + annotationType - + " and " + phaseId + ")"); + + ". Cannot be annotated simultaneously with multiples JSF lifecycle annotations (" + + annotationType + " and " + phaseId + ")"); } phaseId = PhaseId.INVOKE_APPLICATION; } else if (annotationType == RenderResponse.class) { if (phaseId != null) { throw new IllegalStateException("invalid " + parentElement - + ". Cannot be annotated simultaneously with multiples JSF lifecycle annotations (" + annotationType - + " and " + phaseId + ")"); + + ". Cannot be annotated simultaneously with multiples JSF lifecycle annotations (" + + annotationType + " and " + phaseId + ")"); } phaseId = PhaseId.RENDER_RESPONSE; } else if (annotationType == BeforeRenderReponse.class) { if (phaseId != null) { throw new IllegalStateException("invalid " + parentElement - + ". Cannot be annotated simultaneously with multiples JSF lifecycle annotations (" + annotationType - + " and " + phaseId + ")"); + + ". Cannot be annotated simultaneously with multiples JSF lifecycle annotations (" + + annotationType + " and " + phaseId + ")"); } if (before != null) { throw new IllegalStateException("invalid " + parentElement @@ -88,11 +89,11 @@ public static PhaseInstant getPhaseInstant(List annotations, Object } phaseId = PhaseId.RENDER_RESPONSE; before = true; - } else if (annotationType == AfterRenderView.class) { + } else if (annotationType == AfterRenderResponse.class) { if (phaseId != null) { throw new IllegalStateException("invalid " + parentElement - + ". Cannot be annotated simultaneously with multiples JSF lifecycle annotations (" + annotationType - + " and " + phaseId + ")"); + + ". Cannot be annotated simultaneously with multiples JSF lifecycle annotations (" + + annotationType + " and " + phaseId + ")"); } if (before != null) { throw new IllegalStateException("invalid " + parentElement @@ -102,13 +103,13 @@ public static PhaseInstant getPhaseInstant(List annotations, Object before = false; } } - if (before==null && phaseId==null) { + if (before == null && phaseId == null) { return null; - } else if (before!=null && phaseId!=null) { + } else if (before != null && phaseId != null) { return new PhaseInstant(phaseId, before); } else { throw new IllegalStateException("invalid " + parentElement - + ". both phaseId and @Before/@After must be specified {phaseId: " + phaseId+", before: "+before+"}"); + + ". both phaseId and @Before/@After must be specified {phaseId: " + phaseId + ", before: " + before + "}"); } } } From 6247f6b94053eb4a1a11d58db144ed10ce6e6983 Mon Sep 17 00:00:00 2001 From: gonzalad Date: Wed, 2 Nov 2011 23:46:33 +0100 Subject: [PATCH 4/9] Removed @BeforeRenderResponse and @AfterRenderResponse Those where shortcut annotations duplicating @Before/@After @RenderResponse --- .../view/action/AfterRenderResponse.java | 22 -------- .../view/action/BeforeRenderReponse.java | 22 -------- .../examples/viewconfig/PageController.java | 14 ++--- .../examples/viewconfig/ViewController.java | 51 +++++++++++-------- .../faces/view/action/ViewActionUtils.java | 24 --------- 5 files changed, 38 insertions(+), 95 deletions(-) delete mode 100644 api/src/main/java/org/jboss/seam/faces/view/action/AfterRenderResponse.java delete mode 100644 api/src/main/java/org/jboss/seam/faces/view/action/BeforeRenderReponse.java diff --git a/api/src/main/java/org/jboss/seam/faces/view/action/AfterRenderResponse.java b/api/src/main/java/org/jboss/seam/faces/view/action/AfterRenderResponse.java deleted file mode 100644 index 5316d80..0000000 --- a/api/src/main/java/org/jboss/seam/faces/view/action/AfterRenderResponse.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.jboss.seam.faces.view.action; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * This lifecycle annotation can be used on viewController methods. - * - * These methods will be called after JSF RENDER_VIEW phase. - * Typically used for cleanup purposes - * Shortcut for @After @RenderResponse. - * - * @author Adriàn Gonzalez - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) -@Documented -public @interface AfterRenderResponse { -} diff --git a/api/src/main/java/org/jboss/seam/faces/view/action/BeforeRenderReponse.java b/api/src/main/java/org/jboss/seam/faces/view/action/BeforeRenderReponse.java deleted file mode 100644 index bd6556c..0000000 --- a/api/src/main/java/org/jboss/seam/faces/view/action/BeforeRenderReponse.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.jboss.seam.faces.view.action; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * This lifecycle annotation can be used on viewController methods. - * - * These methods will be called before JSF RENDER_VIEW phase. - * Typically used for view initialization purposes. - * Shortcut for @Before @RenderResponse. - * - * @author Adriàn Gonzalez - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) -@Documented -public @interface BeforeRenderReponse { -} diff --git a/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/PageController.java b/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/PageController.java index c540cfa..9d6e704 100644 --- a/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/PageController.java +++ b/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/PageController.java @@ -25,11 +25,12 @@ import javax.inject.Inject; import javax.inject.Named; +import org.jboss.seam.faces.event.qualifier.Before; +import org.jboss.seam.faces.event.qualifier.RenderResponse; import org.jboss.seam.faces.examples.viewconfig.MyAppViewConfig.Pages; import org.jboss.seam.faces.examples.viewconfig.model.Current; import org.jboss.seam.faces.examples.viewconfig.model.Item; import org.jboss.seam.faces.examples.viewconfig.model.ItemDao; -import org.jboss.seam.faces.view.action.BeforeRenderReponse; /** * @author Brian Leathem @@ -55,20 +56,21 @@ public Item getItem() { return item; } - @BeforeRenderReponse + @Before + @RenderResponse public void beforeRenderView(@Current Item item) { - System.out.println("beforeRenderView called "+item); + System.out.println("beforeRenderView called " + item); } public void viewAction(@Current Item item) { - System.out.println("viewAction "+item); + System.out.println("viewAction " + item); } @MyViewAction(Pages.ITEM) public void viewActionBindingType(@Current Item item) { - System.out.println("viewActionBindingType "+item); + System.out.println("viewActionBindingType " + item); } - + public void setItem(Item item) { this.item = item; } diff --git a/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/ViewController.java b/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/ViewController.java index 951c922..bb7bc22 100644 --- a/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/ViewController.java +++ b/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/ViewController.java @@ -10,58 +10,67 @@ import org.jboss.seam.faces.event.qualifier.ProcessValidations; import org.jboss.seam.faces.event.qualifier.RenderResponse; import org.jboss.seam.faces.event.qualifier.UpdateModelValues; -import org.jboss.seam.faces.view.action.BeforeRenderReponse; public class ViewController { - @Before @ApplyRequestValues + @Before + @ApplyRequestValues public void beforeApplyRequestValues() { - addFacesMessage(this.getClass().getSimpleName()+".beforeApplyRequestValues was called"); + addFacesMessage(this.getClass().getSimpleName() + ".beforeApplyRequestValues was called"); } - @After @ApplyRequestValues + @After + @ApplyRequestValues public void afterApplyRequestValues() { - addFacesMessage(this.getClass().getSimpleName()+".afterApplyRequestValues was called"); + addFacesMessage(this.getClass().getSimpleName() + ".afterApplyRequestValues was called"); } - @Before @ProcessValidations + @Before + @ProcessValidations public void beforeProcessValidations() { - addFacesMessage(this.getClass().getSimpleName()+".beforeProcessValidations was called"); + addFacesMessage(this.getClass().getSimpleName() + ".beforeProcessValidations was called"); } - @After @ProcessValidations + @After + @ProcessValidations public void afterProcessValidations() { - addFacesMessage(this.getClass().getSimpleName()+".afterProcessValidations was called"); + addFacesMessage(this.getClass().getSimpleName() + ".afterProcessValidations was called"); } - @Before @UpdateModelValues + @Before + @UpdateModelValues public void beforeUpdateModelValues() { - addFacesMessage(this.getClass().getSimpleName()+".beforeUpdateModelValues was called"); + addFacesMessage(this.getClass().getSimpleName() + ".beforeUpdateModelValues was called"); } - @After @UpdateModelValues + @After + @UpdateModelValues public void afterUpdateModelValues() { - addFacesMessage(this.getClass().getSimpleName()+".afterUpdateModelValues was called"); + addFacesMessage(this.getClass().getSimpleName() + ".afterUpdateModelValues was called"); } - @Before @InvokeApplication + @Before + @InvokeApplication public void beforeInvokeApplication() { - addFacesMessage(this.getClass().getSimpleName()+".beforeInvokeApplication was called"); + addFacesMessage(this.getClass().getSimpleName() + ".beforeInvokeApplication was called"); } - @After @InvokeApplication + @After + @InvokeApplication public void afterInvokeApplication() { - addFacesMessage(this.getClass().getSimpleName()+".afterInvokeApplication was called"); + addFacesMessage(this.getClass().getSimpleName() + ".afterInvokeApplication was called"); } - @BeforeRenderReponse + @Before + @RenderResponse public void beforeRenderResponse() { - addFacesMessage(this.getClass().getSimpleName()+".beforeRenderResponse was called"); + addFacesMessage(this.getClass().getSimpleName() + ".beforeRenderResponse was called"); } - @After @RenderResponse + @After + @RenderResponse public void afterRenderResponse() { - addFacesMessage(this.getClass().getSimpleName()+".RenderResponse was called"); + addFacesMessage(this.getClass().getSimpleName() + ".RenderResponse was called"); } private void addFacesMessage(String message) { diff --git a/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionUtils.java b/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionUtils.java index 6a2fea6..734ce40 100644 --- a/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionUtils.java +++ b/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionUtils.java @@ -77,30 +77,6 @@ public static PhaseInstant getPhaseInstant(List annotations, Object + annotationType + " and " + phaseId + ")"); } phaseId = PhaseId.RENDER_RESPONSE; - } else if (annotationType == BeforeRenderReponse.class) { - if (phaseId != null) { - throw new IllegalStateException("invalid " + parentElement - + ". Cannot be annotated simultaneously with multiples JSF lifecycle annotations (" - + annotationType + " and " + phaseId + ")"); - } - if (before != null) { - throw new IllegalStateException("invalid " + parentElement - + ". Cannot be annotated simultaneously with multiples @Before/@After and " + annotationType); - } - phaseId = PhaseId.RENDER_RESPONSE; - before = true; - } else if (annotationType == AfterRenderResponse.class) { - if (phaseId != null) { - throw new IllegalStateException("invalid " + parentElement - + ". Cannot be annotated simultaneously with multiples JSF lifecycle annotations (" - + annotationType + " and " + phaseId + ")"); - } - if (before != null) { - throw new IllegalStateException("invalid " + parentElement - + ". Cannot be annotated simultaneously with multiples @Before/@After and " + annotationType); - } - phaseId = PhaseId.RENDER_RESPONSE; - before = false; } } if (before == null && phaseId == null) { From 24cc75d898b86c6eea92805e54ea74e5e1addce0 Mon Sep 17 00:00:00 2001 From: gonzalad Date: Thu, 3 Nov 2011 09:00:02 +0100 Subject: [PATCH 5/9] Add attributes phase, before to @ViewAction And removed usage of @Before/@After and phase annotation with @ViewAction. Attribute usage is more explicit here. --- .../seam/faces/view/action/ViewAction.java | 15 +++- .../examples/viewconfig/MyAppViewConfig.java | 4 -- .../faces/view/action/ViewActionUtils.java | 68 +++++++++++-------- .../view/action/ViewControllerStore.java | 6 +- 4 files changed, 53 insertions(+), 40 deletions(-) diff --git a/api/src/main/java/org/jboss/seam/faces/view/action/ViewAction.java b/api/src/main/java/org/jboss/seam/faces/view/action/ViewAction.java index 96c9131..aeb8ea3 100644 --- a/api/src/main/java/org/jboss/seam/faces/view/action/ViewAction.java +++ b/api/src/main/java/org/jboss/seam/faces/view/action/ViewAction.java @@ -6,10 +6,13 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.jboss.seam.faces.event.qualifier.RenderResponse; + /** * The EL MethodExpression is executed when this annotation is applied to a ViewConfig. * - * The MethodExpression is called before RENDER_RESPONSE phase. + * The MethodExpression is called by default before RENDER_RESPONSE phase. You can change this + * behaviour by using phase and before fields. * * @author Adriàn Gonzalez */ @@ -21,4 +24,14 @@ * El MethodExpression */ String value(); + + /** + * On which JSF phase must this viewAction be executed ? + */ + Class phase() default RenderResponse.class; + + /** + * Is this viewAction executed before phase ? + */ + boolean before() default true; } diff --git a/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/MyAppViewConfig.java b/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/MyAppViewConfig.java index dcc26d0..c2700e5 100644 --- a/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/MyAppViewConfig.java +++ b/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/MyAppViewConfig.java @@ -16,8 +16,6 @@ */ package org.jboss.seam.faces.examples.viewconfig; -import org.jboss.seam.faces.event.qualifier.ApplyRequestValues; -import org.jboss.seam.faces.event.qualifier.Before; import org.jboss.seam.faces.examples.viewconfig.security.Admin; import org.jboss.seam.faces.examples.viewconfig.security.Owner; import org.jboss.seam.faces.rewrite.FacesRedirect; @@ -45,8 +43,6 @@ static enum Pages { @ViewPattern("/item.xhtml") @ViewController(PageController.class) @Owner - @ViewAction("#{pageController.viewAction(pageController.item)}") - @Before @ApplyRequestValues ITEM, @ViewPattern("/viewcontroller.xhtml") diff --git a/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionUtils.java b/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionUtils.java index 734ce40..938ceb0 100644 --- a/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionUtils.java +++ b/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionUtils.java @@ -42,41 +42,13 @@ public static PhaseInstant getPhaseInstant(List annotations, Object + ". Cannot be annotated simultaneously with multiples @Before and @After"); } before = false; - } else if (annotationType == ApplyRequestValues.class) { + } else if (isPhaseQualifier(annotationType)) { if (phaseId != null) { throw new IllegalStateException("invalid " + parentElement + ". Cannot be annotated simultaneously with multiples JSF lifecycle annotations (" + annotationType + " and " + phaseId + ")"); } - phaseId = PhaseId.APPLY_REQUEST_VALUES; - } else if (annotationType == ProcessValidations.class) { - if (phaseId != null) { - throw new IllegalStateException("invalid " + parentElement - + ". Cannot be annotated simultaneously with multiples JSF lifecycle annotations (" - + annotationType + " and " + phaseId + ")"); - } - phaseId = PhaseId.PROCESS_VALIDATIONS; - } else if (annotationType == UpdateModelValues.class) { - if (phaseId != null) { - throw new IllegalStateException("invalid " + parentElement - + ". Cannot be annotated simultaneously with multiples JSF lifecycle annotations (" - + annotationType + " and " + phaseId + ")"); - } - phaseId = PhaseId.UPDATE_MODEL_VALUES; - } else if (annotationType == InvokeApplication.class) { - if (phaseId != null) { - throw new IllegalStateException("invalid " + parentElement - + ". Cannot be annotated simultaneously with multiples JSF lifecycle annotations (" - + annotationType + " and " + phaseId + ")"); - } - phaseId = PhaseId.INVOKE_APPLICATION; - } else if (annotationType == RenderResponse.class) { - if (phaseId != null) { - throw new IllegalStateException("invalid " + parentElement - + ". Cannot be annotated simultaneously with multiples JSF lifecycle annotations (" - + annotationType + " and " + phaseId + ")"); - } - phaseId = PhaseId.RENDER_RESPONSE; + phaseId = convert(annotationType); } } if (before == null && phaseId == null) { @@ -88,4 +60,40 @@ public static PhaseInstant getPhaseInstant(List annotations, Object + ". both phaseId and @Before/@After must be specified {phaseId: " + phaseId + ", before: " + before + "}"); } } + + /** + * Converts the annotations from package org.jboss.seam.faces.event.qualifier to their corresponding JSF PhaseId. + * + * @throws IllegalArgumentException if annotationType isn't a valid Jsf annotation. + */ + public static PhaseId convert(Class annotationType) { + PhaseId phaseId; + if (annotationType == ApplyRequestValues.class) { + phaseId = PhaseId.APPLY_REQUEST_VALUES; + } else if (annotationType == ProcessValidations.class) { + phaseId = PhaseId.PROCESS_VALIDATIONS; + } else if (annotationType == UpdateModelValues.class) { + phaseId = PhaseId.UPDATE_MODEL_VALUES; + } else if (annotationType == InvokeApplication.class) { + phaseId = PhaseId.INVOKE_APPLICATION; + } else if (annotationType == RenderResponse.class) { + phaseId = PhaseId.RENDER_RESPONSE; + } else { + throw new IllegalArgumentException("Annotation " + annotationType + " doesn't correspond to valid a Jsf phase."); + } + return phaseId; + } + + /** + * Returns true if annotationType is a valid JSF annotation + */ + public static boolean isPhaseQualifier(Class annotationType) { + if (annotationType == ApplyRequestValues.class || annotationType == ProcessValidations.class + || annotationType == UpdateModelValues.class || annotationType == InvokeApplication.class + || annotationType == RenderResponse.class) { + return true; + } else { + return false; + } + } } diff --git a/impl/src/main/java/org/jboss/seam/faces/view/action/ViewControllerStore.java b/impl/src/main/java/org/jboss/seam/faces/view/action/ViewControllerStore.java index 362026b..2de5354 100644 --- a/impl/src/main/java/org/jboss/seam/faces/view/action/ViewControllerStore.java +++ b/impl/src/main/java/org/jboss/seam/faces/view/action/ViewControllerStore.java @@ -84,14 +84,10 @@ private void registerViewActions(ViewConfigStore viewConfigStore, BeanManager be List viewConfigDescriptors = viewConfigStore.getAllViewConfigDescriptors(); for (ViewConfigDescriptor viewConfigDescriptor : viewConfigDescriptors) { ViewAction viewAction = viewConfigDescriptor.getMetaData(ViewAction.class); - // TODO : don't know using annotations @Before / @RenderResponse / ... with @ViewAction - // is a good idea (possible collision with future annotations ?) - // would it be better to add attributes to @ViewAction ? if (viewAction != null) { ViewControllerDescriptor viewControllerDescriptor = new ViewControllerDescriptor( viewConfigDescriptor.getViewId(), beanManager); - PhaseInstant phaseInstant = ViewActionUtils.getPhaseInstantOrDefault(viewConfigDescriptor.getMetaData(), - viewConfigDescriptor, BEFORE_RENDER_RESPONSE); + PhaseInstant phaseInstant = new PhaseInstant(ViewActionUtils.convert(viewAction.phase()), viewAction.before()); viewControllerDescriptor.addMethod(phaseInstant, new ViewControllerDescriptor.MethodExpressionInvoker( viewAction.value())); addControllerDescriptor(viewControllerDescriptor); From e5c770d82e0da82d281ed7856fd2840b4802eec0 Mon Sep 17 00:00:00 2001 From: gonzalad Date: Thu, 3 Nov 2011 23:27:33 +0100 Subject: [PATCH 6/9] Added unit tests --- .../view/config/ViewConfigDescriptor.java | 4 + .../view/action/ViewControllerDescriptor.java | 4 + .../view/action/ViewControllerStore.java | 2 - .../test/weld/config/ViewConfigStoreTest.java | 1 + .../test/weld/config/ViewConfigTest.java | 60 +++++++++++++++ .../action/ViewControllerDescriptorTest.java | 77 +++++++++++++++++++ .../AfterInvokeApplicationViewAction.java | 19 +++++ .../BeforeRenderResponseViewAction.java | 15 ++++ .../action/annotation/ClientController.java | 17 ++++ .../action/annotation/CountryController.java | 15 ++++ .../action/annotation/ViewConfigEnum.java | 51 ++++++++++++ 11 files changed, 263 insertions(+), 2 deletions(-) create mode 100644 testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/ViewControllerDescriptorTest.java create mode 100644 testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/annotation/AfterInvokeApplicationViewAction.java create mode 100644 testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/annotation/BeforeRenderResponseViewAction.java create mode 100644 testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/annotation/ClientController.java create mode 100644 testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/annotation/CountryController.java create mode 100644 testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/annotation/ViewConfigEnum.java diff --git a/api/src/main/java/org/jboss/seam/faces/view/config/ViewConfigDescriptor.java b/api/src/main/java/org/jboss/seam/faces/view/config/ViewConfigDescriptor.java index 4a70c79..bba2d1e 100644 --- a/api/src/main/java/org/jboss/seam/faces/view/config/ViewConfigDescriptor.java +++ b/api/src/main/java/org/jboss/seam/faces/view/config/ViewConfigDescriptor.java @@ -103,4 +103,8 @@ public List getAllQualifierData(Class metaData = metaDataByQualifier.get(qualifier); return metaData!=null ? Collections.unmodifiableList(metaData) : Collections.EMPTY_LIST; } + + public String toString() { + return super.toString()+"{viewId="+getViewId()+"}"; + } } diff --git a/impl/src/main/java/org/jboss/seam/faces/view/action/ViewControllerDescriptor.java b/impl/src/main/java/org/jboss/seam/faces/view/action/ViewControllerDescriptor.java index 68b813b..74645d7 100644 --- a/impl/src/main/java/org/jboss/seam/faces/view/action/ViewControllerDescriptor.java +++ b/impl/src/main/java/org/jboss/seam/faces/view/action/ViewControllerDescriptor.java @@ -301,6 +301,10 @@ public static class MethodExpressionInvoker implements ViewActionStrategy { public MethodExpressionInvoker(String methodExpressionAsString) { this.methodExpressionAsString = methodExpressionAsString; } + + public String getMethodExpressionString() { + return methodExpressionAsString; + } @Override public Object execute() { diff --git a/impl/src/main/java/org/jboss/seam/faces/view/action/ViewControllerStore.java b/impl/src/main/java/org/jboss/seam/faces/view/action/ViewControllerStore.java index 2de5354..becc122 100644 --- a/impl/src/main/java/org/jboss/seam/faces/view/action/ViewControllerStore.java +++ b/impl/src/main/java/org/jboss/seam/faces/view/action/ViewControllerStore.java @@ -1,7 +1,5 @@ package org.jboss.seam.faces.view.action; -import static org.jboss.seam.faces.view.action.PhaseInstant.BEFORE_RENDER_RESPONSE; - import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Collections; diff --git a/testsuite/src/test/java/org/jboss/seam/faces/test/weld/config/ViewConfigStoreTest.java b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/config/ViewConfigStoreTest.java index 2e78773..9a5c3e0 100644 --- a/testsuite/src/test/java/org/jboss/seam/faces/test/weld/config/ViewConfigStoreTest.java +++ b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/config/ViewConfigStoreTest.java @@ -20,6 +20,7 @@ import java.util.List; import junit.framework.Assert; + import org.jboss.seam.faces.test.weld.config.annotation.Icon; import org.jboss.seam.faces.test.weld.config.annotation.IconLiteral; import org.jboss.seam.faces.test.weld.config.annotation.QualifiedIcon; diff --git a/testsuite/src/test/java/org/jboss/seam/faces/test/weld/config/ViewConfigTest.java b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/config/ViewConfigTest.java index ecb9848..6eee302 100644 --- a/testsuite/src/test/java/org/jboss/seam/faces/test/weld/config/ViewConfigTest.java +++ b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/config/ViewConfigTest.java @@ -16,16 +16,24 @@ */ package org.jboss.seam.faces.test.weld.config; +import java.lang.annotation.Annotation; +import java.util.HashMap; import java.util.List; +import java.util.Map; import javax.inject.Inject; import junit.framework.Assert; + import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.junit.Arquillian; +import org.jboss.seam.faces.security.RestrictAtPhase; import org.jboss.seam.faces.test.weld.config.annotation.Icon; import org.jboss.seam.faces.test.weld.config.annotation.IconLiteral; +import org.jboss.seam.faces.test.weld.config.annotation.QualifiedIcon; +import org.jboss.seam.faces.test.weld.config.annotation.RestrictedAtRestoreView; import org.jboss.seam.faces.test.weld.config.annotation.ViewConfigEnum; +import org.jboss.seam.faces.view.config.ViewConfigDescriptor; import org.jboss.seam.faces.view.config.ViewConfigStore; import org.jboss.seam.faces.view.config.ViewConfigStoreImpl; import org.jboss.shrinkwrap.api.Archive; @@ -84,4 +92,56 @@ public void testViewConfigStore() { Assert.assertEquals("default.gif", dlist.get(0).value()); } + + @Test + public void testViewConfigDescriptor() { + + Map descriptorMap = new HashMap(); + for (ViewConfigDescriptor descriptor : store.getAllViewConfigDescriptors()) { + Assert.assertFalse("duplicated viewId "+descriptor.getViewId(), descriptorMap.containsKey(descriptor.getViewId())); + descriptorMap.put(descriptor.getViewId(), descriptor); + } + + String viewId = "/happy/done.xhtml"; + ViewConfigDescriptor descriptor = descriptorMap.get(viewId); + Assert.assertEquals(viewId, descriptor.getViewId()); + Assert.assertEquals(ViewConfigEnum.Pages.HAPPY_DONE, descriptor.getValues().get(0)); + Icon data = descriptor.getMetaData(Icon.class); + Assert.assertEquals("finished.gif", data.value()); + + viewId = "/happy/*"; + descriptor = descriptorMap.get(viewId); + Assert.assertEquals(viewId, descriptor.getViewId()); + Assert.assertEquals(ViewConfigEnum.Pages.HAPPY, descriptor.getValues().get(0)); + data = descriptor.getMetaData(Icon.class); + Assert.assertEquals("happy.gif", data.value()); + + viewId = "/sad/*"; + descriptor = descriptorMap.get(viewId); + Assert.assertEquals(viewId, descriptor.getViewId()); + Assert.assertEquals(ViewConfigEnum.Pages.SAD, descriptor.getValues().get(0)); + data = descriptor.getMetaData(Icon.class); + Assert.assertEquals("sad.gif", data.value()); + + viewId = "/*"; + descriptor = descriptorMap.get(viewId); + Assert.assertEquals(viewId, descriptor.getViewId()); + Assert.assertEquals(ViewConfigEnum.Pages.DEFAULT, descriptor.getValues().get(0)); + data = descriptor.getMetaData(Icon.class); + Assert.assertEquals("default.gif", data.value()); + + QualifiedIcon qualifiedData; + descriptor = descriptorMap.get("/qualified/*"); + qualifiedData = descriptor.getMetaData(QualifiedIcon.class); + Assert.assertEquals(ViewConfigEnum.Pages.QUALIFIED, descriptor.getValues().get(0)); + Assert.assertEquals("qualified.gif", qualifiedData.value()); + + descriptor = descriptorMap.get("/qualified/yes.xhtml"); + Assert.assertEquals(ViewConfigEnum.Pages.QUALIFIED_YES, descriptor.getValues().get(0)); + List annotations = descriptor.getAllQualifierData(RestrictAtPhase.class); + Assert.assertEquals(1, annotations.size()); + Assert.assertTrue(RestrictedAtRestoreView.class.isAssignableFrom(annotations.get(0).getClass())); + + Assert.assertEquals(6, descriptorMap.size()); + } } diff --git a/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/ViewControllerDescriptorTest.java b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/ViewControllerDescriptorTest.java new file mode 100644 index 0000000..1046724 --- /dev/null +++ b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/ViewControllerDescriptorTest.java @@ -0,0 +1,77 @@ +package org.jboss.seam.faces.test.weld.view.action; + +import java.util.List; +import java.util.Map; + +import javax.faces.event.PhaseId; +import javax.inject.Inject; + +import junit.framework.Assert; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.junit.Arquillian; +import org.jboss.seam.faces.test.weld.view.action.annotation.AfterInvokeApplicationViewAction; +import org.jboss.seam.faces.test.weld.view.action.annotation.BeforeRenderResponseViewAction; +import org.jboss.seam.faces.test.weld.view.action.annotation.ClientController; +import org.jboss.seam.faces.test.weld.view.action.annotation.CountryController; +import org.jboss.seam.faces.test.weld.view.action.annotation.ViewConfigEnum; +import org.jboss.seam.faces.view.action.PhaseInstant; +import org.jboss.seam.faces.view.action.ViewActionStrategy; +import org.jboss.seam.faces.view.action.ViewControllerDescriptor; +import org.jboss.seam.faces.view.action.ViewControllerExtension; +import org.jboss.seam.faces.view.action.ViewControllerStore; +import org.jboss.seam.faces.view.config.ViewConfigStoreImpl; +import org.jboss.shrinkwrap.api.Archive; +import org.jboss.shrinkwrap.api.ArchivePaths; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.ByteArrayAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(Arquillian.class) +public class ViewControllerDescriptorTest { + @Deployment + public static Archive createTestArchive() { + JavaArchive archive = ShrinkWrap.create(JavaArchive.class).addClass(ViewConfigStoreImpl.class) + .addClass(ViewControllerStore.class).addClass(ViewControllerExtension.class) + .addClass(AfterInvokeApplicationViewAction.class).addClass(BeforeRenderResponseViewAction.class) + .addClass(ClientController.class).addClass(CountryController.class).addClass(ViewConfigEnum.class) + .addPackage(ViewConfigEnum.class.getPackage()) + .addAsManifestResource(new ByteArrayAsset(new byte[0]), ArchivePaths.create("beans.xml")); + return archive; + } + + @Inject + private ViewControllerStore store; + + @Test + public void testGetControllerDescriptors() { + + List descriptors = store.getControllerDescriptors("/client/done.xhtml"); + Assert.assertEquals(2, descriptors.size()); + ViewControllerDescriptor descriptor = descriptors.get(0); + Assert.assertEquals("/client/*", descriptor.getViewId()); + Map> phaseMethods = descriptor.getPhaseMethods(); + List viewActions = phaseMethods.get(PhaseInstant.BEFORE_RENDER_RESPONSE); + Assert.assertEquals(1, viewActions.size()); + ViewActionStrategy actionStrategy = viewActions.get(0); + Assert.assertEquals("#{clientController.viewAction}", + ((ViewControllerDescriptor.MethodExpressionInvoker) actionStrategy).getMethodExpressionString()); + descriptor = descriptors.get(1); + Assert.assertEquals("/client/*", descriptor.getViewId()); + phaseMethods = descriptor.getPhaseMethods(); + Assert.assertEquals(2, phaseMethods.size()); + Assert.assertEquals(1, phaseMethods.get(PhaseInstant.BEFORE_RENDER_RESPONSE).size()); + Assert.assertEquals(1, phaseMethods.get(new PhaseInstant(PhaseId.INVOKE_APPLICATION, false)).size()); + + descriptors = store.getControllerDescriptors("/country/done.xhtml"); + Assert.assertEquals(1, descriptors.size()); + descriptor = descriptors.get(0); + Assert.assertEquals("/country/*", descriptor.getViewId()); + Assert.assertEquals(CountryController.class, descriptor.getViewControllerClass()); + phaseMethods = descriptor.getPhaseMethods(); + Assert.assertEquals(1, phaseMethods.size()); + Assert.assertEquals(1, phaseMethods.get(PhaseInstant.BEFORE_RENDER_RESPONSE).size()); + } +} diff --git a/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/annotation/AfterInvokeApplicationViewAction.java b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/annotation/AfterInvokeApplicationViewAction.java new file mode 100644 index 0000000..60a1ef2 --- /dev/null +++ b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/annotation/AfterInvokeApplicationViewAction.java @@ -0,0 +1,19 @@ +package org.jboss.seam.faces.test.weld.view.action.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.jboss.seam.faces.event.qualifier.After; +import org.jboss.seam.faces.event.qualifier.InvokeApplication; +import org.jboss.seam.faces.view.action.ViewActionBindingType; + +@ViewActionBindingType +@After +@InvokeApplication +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE}) +public @interface AfterInvokeApplicationViewAction { + ViewConfigEnum.Pages value(); +} diff --git a/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/annotation/BeforeRenderResponseViewAction.java b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/annotation/BeforeRenderResponseViewAction.java new file mode 100644 index 0000000..168b393 --- /dev/null +++ b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/annotation/BeforeRenderResponseViewAction.java @@ -0,0 +1,15 @@ +package org.jboss.seam.faces.test.weld.view.action.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.jboss.seam.faces.view.action.ViewActionBindingType; + +@ViewActionBindingType +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE}) +public @interface BeforeRenderResponseViewAction { + ViewConfigEnum.Pages value(); +} diff --git a/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/annotation/ClientController.java b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/annotation/ClientController.java new file mode 100644 index 0000000..b6cd8dd --- /dev/null +++ b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/annotation/ClientController.java @@ -0,0 +1,17 @@ +package org.jboss.seam.faces.test.weld.view.action.annotation; + +import javax.inject.Named; + +@Named +public class ClientController { + public void viewAction() { + } + + @BeforeRenderResponseViewAction(ViewConfigEnum.Pages.CLIENTS) + public void beforeRenderResponse() { + } + + @AfterInvokeApplicationViewAction(ViewConfigEnum.Pages.CLIENTS) + public void afterInvokeApplication() { + } +} diff --git a/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/annotation/CountryController.java b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/annotation/CountryController.java new file mode 100644 index 0000000..af6b5a4 --- /dev/null +++ b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/annotation/CountryController.java @@ -0,0 +1,15 @@ +package org.jboss.seam.faces.test.weld.view.action.annotation; + +import javax.inject.Named; + +import org.jboss.seam.faces.event.qualifier.Before; +import org.jboss.seam.faces.event.qualifier.RenderResponse; + +@Named +public class CountryController { + + @Before + @RenderResponse + public void beforeRenderResponse() { + } +} diff --git a/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/annotation/ViewConfigEnum.java b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/annotation/ViewConfigEnum.java new file mode 100644 index 0000000..39a0573 --- /dev/null +++ b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/annotation/ViewConfigEnum.java @@ -0,0 +1,51 @@ +/* + * JBoss, Home of Professional Open Source + * Copyright 2011, Red Hat, Inc., and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jboss.seam.faces.test.weld.view.action.annotation; + +import org.jboss.seam.faces.event.PhaseIdType; +import org.jboss.seam.faces.security.RestrictAtPhase; +import org.jboss.seam.faces.view.action.ViewAction; +import org.jboss.seam.faces.view.action.ViewController; +import org.jboss.seam.faces.view.config.ViewConfig; +import org.jboss.seam.faces.view.config.ViewPattern; + +@ViewConfig +public interface ViewConfigEnum { + static enum Pages { + @ViewPattern("/*") + DEFAULT, + + @ViewPattern("/client/*") + @ViewAction("#{clientController.viewAction}") + CLIENTS, + + @ViewPattern("/country/*") + @ViewController(CountryController.class) + COUNTRIES(), + + @ViewPattern("/client/done.xhtml") + CLIENT_CONFIRMED(), + + @ViewPattern("/qualified/*") + @RestrictAtPhase(PhaseIdType.INVOKE_APPLICATION) + QUALIFIED, + + @ViewPattern("/qualified/yes.xhtml") + QUALIFIED_YES; + + } +} From 4e06e40655bd9820dc5319ab2483e37eaf397986 Mon Sep 17 00:00:00 2001 From: gonzalad Date: Thu, 3 Nov 2011 23:38:27 +0100 Subject: [PATCH 7/9] Removed commented / unused code --- .../view/action/ViewActionPhaseListener.java | 11 --- .../view/action/ViewControllerDescriptor.java | 68 +++---------------- 2 files changed, 8 insertions(+), 71 deletions(-) diff --git a/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionPhaseListener.java b/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionPhaseListener.java index c2d05b9..1b0766d 100644 --- a/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionPhaseListener.java +++ b/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionPhaseListener.java @@ -39,12 +39,6 @@ public void observerBeforePhase(@Observes @Before PhaseEvent event) { ViewControllerDescriptor viewControllerDescriptor = viewControllers.get(i); viewControllerDescriptor.executeBeforePhase(event.getPhaseId()); } - if (phaseId == PhaseId.RENDER_RESPONSE) { - for (int i = viewControllers.size(); --i >= 0;) { - ViewControllerDescriptor viewControllerDescriptor = viewControllers.get(i); - viewControllerDescriptor.executeBeforeRenderView(); - } - } } public void observerAfterPhase(@Observes @After PhaseEvent event) { @@ -55,10 +49,5 @@ public void observerAfterPhase(@Observes @After PhaseEvent event) { for (ViewControllerDescriptor viewControllerDescriptor : viewControllers) { viewControllerDescriptor.executeAfterPhase(event.getPhaseId()); } - if (phaseId == PhaseId.RENDER_RESPONSE) { - for (ViewControllerDescriptor viewControllerDescriptor : viewControllers) { - viewControllerDescriptor.executeAfterRenderView(); - } - } } } diff --git a/impl/src/main/java/org/jboss/seam/faces/view/action/ViewControllerDescriptor.java b/impl/src/main/java/org/jboss/seam/faces/view/action/ViewControllerDescriptor.java index 74645d7..d63c83f 100644 --- a/impl/src/main/java/org/jboss/seam/faces/view/action/ViewControllerDescriptor.java +++ b/impl/src/main/java/org/jboss/seam/faces/view/action/ViewControllerDescriptor.java @@ -35,11 +35,6 @@ public class ViewControllerDescriptor { private String viewId; private Class viewControllerClass; private BeanManager beanManager; - - // call this one in an additional actionListener (faces-config.xml) - private List beforePageActionMethods = new ArrayList(); - private List beforeRenderViewMethods = new ArrayList(); - private List afterRenderViewMethods = new ArrayList(); private Map> phaseMethods = new HashMap>(); /** @@ -77,12 +72,12 @@ private void registerCallbacks() { Class current = viewControllerClass; while (current != Object.class) { for (Method method : current.getDeclaredMethods()) { -// if (method.isAnnotationPresent(BeforeRenderView.class)) { -// beforeRenderViewMethods.add(new MethodInvoker(method, beanManager)); -// } -// if (method.isAnnotationPresent(AfterRenderView.class)) { -// afterRenderViewMethods.add(new MethodInvoker(method, beanManager)); -// } + // if (method.isAnnotationPresent(BeforeRenderView.class)) { + // beforeRenderViewMethods.add(new MethodInvoker(method, beanManager)); + // } + // if (method.isAnnotationPresent(AfterRenderView.class)) { + // afterRenderViewMethods.add(new MethodInvoker(method, beanManager)); + // } PhaseInstant phaseInstant = ViewActionUtils.getPhaseInstant(Arrays.asList(method.getAnnotations()), method); if (phaseInstant != null) { addMethod(phaseInstant, new MethodInvoker(method, beanManager)); @@ -110,50 +105,6 @@ public void executeAfterPhase(PhaseId phaseId) { } } - public void executeBeforePageAction() { - throw new NoSuchMethodError("unimplemented"); - } - - public void executeBeforeRenderView() { - for (ViewActionStrategy invoker : getBeforeRenderViewMethods()) { - invoker.execute(); - } - } - - public void executeAfterRenderView() { - for (ViewActionStrategy invoker : getAfterRenderViewMethods()) { - invoker.execute(); - } - } - - public List getBeforePageActionMethods() { - return beforePageActionMethods; - } - - public void setBeforePageActionMethods(List beforePageActionMethods) { - this.beforePageActionMethods = beforePageActionMethods; - } - - public void addBeforeRenderViewMethod(ViewActionStrategy beforeRenderViewMethod) { - beforeRenderViewMethods.add(beforeRenderViewMethod); - } - - public List getBeforeRenderViewMethods() { - return beforeRenderViewMethods; - } - - public void setBeforeRenderViewMethods(List beforeRenderViewMethods) { - this.beforeRenderViewMethods = beforeRenderViewMethods; - } - - public List getAfterRenderViewMethods() { - return afterRenderViewMethods; - } - - public void setAfterRenderViewMethods(List afterRenderViewMethods) { - this.afterRenderViewMethods = afterRenderViewMethods; - } - public void addMethod(PhaseInstant phaseInstant, ViewActionStrategy method) { List methods = phaseMethods.get(phaseInstant); if (methods == null) { @@ -191,10 +142,7 @@ public void setViewControllerClass(Class viewControllerClass) { public String toString() { StringBuilder builder = new StringBuilder(super.toString()); builder.append("{viewId: ").append(getViewId()).append(", viewControllerClass: ").append(getViewControllerClass()) - .append(", beforePageActionMethods: {").append(getBeforePageActionMethods()) - .append("}, beforeRenderViewMethods: ").append(getAfterRenderViewMethods()) - .append("}, afterRenderViewMethods: {").append(getAfterRenderViewMethods()).append("}, phaseMethods: {") - .append(getPhaseMethods()).append("}}"); + .append("}, phaseMethods: {").append(getPhaseMethods()).append("}}"); return builder.toString(); } @@ -301,7 +249,7 @@ public static class MethodExpressionInvoker implements ViewActionStrategy { public MethodExpressionInvoker(String methodExpressionAsString) { this.methodExpressionAsString = methodExpressionAsString; } - + public String getMethodExpressionString() { return methodExpressionAsString; } From cac32998cce9020e4af29dbb2e3a49b4564e028d Mon Sep 17 00:00:00 2001 From: gonzalad Date: Wed, 23 Nov 2011 19:38:50 +0100 Subject: [PATCH 8/9] Seamfaces 147.b - viewActions second approach This approach : * enables the end-user to easily create it's own viewAction annotations. Mechanism similar to Hibernate Validator custom annotations. * enforces order execution between Restrict annotation and view actions * (pb is CDI events aren't ordered). * Extension now depend on Store and not the other way around. * removed completely ViewControllerStore (and the duplicated logic) --- api/pom.xml | 4 + .../jboss/seam/faces/event/PhaseIdType.java | 48 ++ .../AnnotatedMethodViewActionHandler.java | 76 ++ .../jboss/seam/faces/view/action/Order.java | 22 + .../seam/faces/view/action/OrderDefault.java | 24 + .../seam/faces/view/action/PhaseInstant.java | 5 + .../SimpleViewActionHandlerProvider.java | 36 + .../seam/faces/view/action/ViewAction.java | 109 ++- .../faces/view/action/ViewActionHandler.java | 30 + .../action/ViewActionHandlerProvider.java | 22 + .../ViewActionHandlerProviderSupport.java | 38 + .../view/action/ViewActionHandlerUtils.java | 201 ++++++ .../{ => binding}/ViewActionBindingType.java | 2 +- .../{ => controller}/ViewController.java | 4 +- .../ViewControllerHandlerProvider.java | 88 +++ .../view/action/el/BeginConversation.java | 24 + .../el/BeginConversationHandlerProvider.java | 27 + .../faces/view/action/el/ElViewAction.java | 39 + .../el/ElViewActionHandlerProvider.java | 26 + .../view/config/ViewConfigDescriptor.java | 103 ++- .../faces/view/config/ViewConfigStore.java | 16 +- .../examples/viewconfig/MyAppViewConfig.java | 6 +- .../examples/viewconfig/MyViewAction.java | 2 +- .../security/RestrictViewActionExtension.java | 92 +++ .../RestrictViewActionHandlerProvider.java | 178 +++++ .../security/RestrictViewActionUtils.java | 114 +++ .../faces/security/SecurityPhaseListener.java | 362 ---------- .../seam/faces/view/action/NonContextual.java | 142 ++++ .../ViewActionBindingTypeDescriptor.java | 52 -- .../view/action/ViewActionExtension.java | 89 +++ .../view/action/ViewActionPhaseListener.java | 20 +- .../faces/view/action/ViewActionStrategy.java | 18 - .../faces/view/action/ViewActionUtils.java | 99 --- .../view/action/ViewControllerDescriptor.java | 267 ------- .../view/action/ViewControllerExtension.java | 80 --- .../view/action/ViewControllerStore.java | 166 ----- .../ViewActionBindingTypeExtension.java | 116 +++ .../binding/ViewActionBindingTypeHandler.java | 36 + .../view/config/ViewConfigStoreImpl.java | 56 +- .../javax.enterprise.inject.spi.Extension | 4 +- testsuite/pom.xml | 671 ++++++++++-------- .../test/weld/config/ViewConfigTest.java | 2 +- .../weld/security/RestrictViewActionTest.java | 145 ++++ .../security/SecurityPhaseListenerTest.java | 115 --- .../action/ViewControllerDescriptorTest.java | 77 -- .../action/annotation/ClientController.java | 17 - .../action/annotation/CountryController.java | 15 - .../weld/view/action/binding/AController.java | 30 + .../AfterInvokeApplicationViewAction.java | 4 +- .../BeforeRenderResponseViewAction.java | 4 +- .../action/binding/HighOrderViewAction.java | 21 + .../action/binding/LowOrderViewAction.java | 21 + .../action/binding/MiddleOrderViewAction.java | 21 + .../action/binding/OrderedController.java | 45 ++ .../binding/ParameterizedOrderViewAction.java | 22 + .../action/binding/ViewActionBindingTest.java | 96 +++ .../view/action/binding/ViewConfigEnum.java | 40 ++ .../action/controller/ClientController.java | 49 ++ .../action/controller/CountryController.java | 35 + .../view/action/controller/DependentBean.java | 8 + .../ViewConfigEnum.java | 18 +- .../action/controller/ViewControllerTest.java | 124 ++++ .../weld/view/action/el/ElViewActionBean.java | 26 + .../action/el/ElViewActionConfigEnum.java | 40 ++ .../weld/view/action/el/ElViewActionTest.java | 117 +++ 65 files changed, 2961 insertions(+), 1645 deletions(-) create mode 100644 api/src/main/java/org/jboss/seam/faces/view/action/AnnotatedMethodViewActionHandler.java create mode 100644 api/src/main/java/org/jboss/seam/faces/view/action/Order.java create mode 100644 api/src/main/java/org/jboss/seam/faces/view/action/OrderDefault.java rename {impl => api}/src/main/java/org/jboss/seam/faces/view/action/PhaseInstant.java (91%) create mode 100644 api/src/main/java/org/jboss/seam/faces/view/action/SimpleViewActionHandlerProvider.java create mode 100644 api/src/main/java/org/jboss/seam/faces/view/action/ViewActionHandler.java create mode 100644 api/src/main/java/org/jboss/seam/faces/view/action/ViewActionHandlerProvider.java create mode 100644 api/src/main/java/org/jboss/seam/faces/view/action/ViewActionHandlerProviderSupport.java create mode 100644 api/src/main/java/org/jboss/seam/faces/view/action/ViewActionHandlerUtils.java rename api/src/main/java/org/jboss/seam/faces/view/action/{ => binding}/ViewActionBindingType.java (92%) rename api/src/main/java/org/jboss/seam/faces/view/action/{ => controller}/ViewController.java (90%) create mode 100644 api/src/main/java/org/jboss/seam/faces/view/action/controller/ViewControllerHandlerProvider.java create mode 100644 api/src/main/java/org/jboss/seam/faces/view/action/el/BeginConversation.java create mode 100644 api/src/main/java/org/jboss/seam/faces/view/action/el/BeginConversationHandlerProvider.java create mode 100644 api/src/main/java/org/jboss/seam/faces/view/action/el/ElViewAction.java create mode 100644 api/src/main/java/org/jboss/seam/faces/view/action/el/ElViewActionHandlerProvider.java create mode 100644 impl/src/main/java/org/jboss/seam/faces/security/RestrictViewActionExtension.java create mode 100644 impl/src/main/java/org/jboss/seam/faces/security/RestrictViewActionHandlerProvider.java create mode 100644 impl/src/main/java/org/jboss/seam/faces/security/RestrictViewActionUtils.java delete mode 100644 impl/src/main/java/org/jboss/seam/faces/security/SecurityPhaseListener.java create mode 100644 impl/src/main/java/org/jboss/seam/faces/view/action/NonContextual.java delete mode 100644 impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionBindingTypeDescriptor.java create mode 100644 impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionExtension.java delete mode 100644 impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionStrategy.java delete mode 100644 impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionUtils.java delete mode 100644 impl/src/main/java/org/jboss/seam/faces/view/action/ViewControllerDescriptor.java delete mode 100644 impl/src/main/java/org/jboss/seam/faces/view/action/ViewControllerExtension.java delete mode 100644 impl/src/main/java/org/jboss/seam/faces/view/action/ViewControllerStore.java create mode 100644 impl/src/main/java/org/jboss/seam/faces/view/action/binding/ViewActionBindingTypeExtension.java create mode 100644 impl/src/main/java/org/jboss/seam/faces/view/action/binding/ViewActionBindingTypeHandler.java create mode 100644 testsuite/src/test/java/org/jboss/seam/faces/test/weld/security/RestrictViewActionTest.java delete mode 100644 testsuite/src/test/java/org/jboss/seam/faces/test/weld/security/SecurityPhaseListenerTest.java delete mode 100644 testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/ViewControllerDescriptorTest.java delete mode 100644 testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/annotation/ClientController.java delete mode 100644 testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/annotation/CountryController.java create mode 100644 testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/binding/AController.java rename testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/{annotation => binding}/AfterInvokeApplicationViewAction.java (79%) rename testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/{annotation => binding}/BeforeRenderResponseViewAction.java (74%) create mode 100644 testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/binding/HighOrderViewAction.java create mode 100644 testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/binding/LowOrderViewAction.java create mode 100644 testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/binding/MiddleOrderViewAction.java create mode 100644 testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/binding/OrderedController.java create mode 100644 testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/binding/ParameterizedOrderViewAction.java create mode 100644 testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/binding/ViewActionBindingTest.java create mode 100644 testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/binding/ViewConfigEnum.java create mode 100644 testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/controller/ClientController.java create mode 100644 testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/controller/CountryController.java create mode 100644 testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/controller/DependentBean.java rename testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/{annotation => controller}/ViewConfigEnum.java (70%) create mode 100644 testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/controller/ViewControllerTest.java create mode 100644 testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/el/ElViewActionBean.java create mode 100644 testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/el/ElViewActionConfigEnum.java create mode 100644 testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/el/ElViewActionTest.java diff --git a/api/pom.xml b/api/pom.xml index 0cda666..1a756c2 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -21,6 +21,10 @@ org.jboss.solder solder-api + + org.jboss.solder + solder-impl + org.jboss.seam.security seam-security-api diff --git a/api/src/main/java/org/jboss/seam/faces/event/PhaseIdType.java b/api/src/main/java/org/jboss/seam/faces/event/PhaseIdType.java index b9df349..047fcc2 100644 --- a/api/src/main/java/org/jboss/seam/faces/event/PhaseIdType.java +++ b/api/src/main/java/org/jboss/seam/faces/event/PhaseIdType.java @@ -16,6 +16,8 @@ */ package org.jboss.seam.faces.event; +import javax.faces.event.PhaseId; + /** * Enum values corresponding to the phases of the JSF life-cycle. * @@ -23,4 +25,50 @@ */ public enum PhaseIdType { ANY_PHASE, RESTORE_VIEW, APPLY_REQUEST_VALUES, PROCESS_VALIDATIONS, UPDATE_MODEL_VALUES, INVOKE_APPLICATION, RENDER_RESPONSE; + + public static PhaseId convert(PhaseIdType phaseIdType) { + switch (phaseIdType) { + case RESTORE_VIEW : + return PhaseId.RESTORE_VIEW; + case APPLY_REQUEST_VALUES : + return PhaseId.APPLY_REQUEST_VALUES; + case PROCESS_VALIDATIONS : + return PhaseId.PROCESS_VALIDATIONS; + case UPDATE_MODEL_VALUES : + return PhaseId.UPDATE_MODEL_VALUES; + case INVOKE_APPLICATION : + return PhaseId.INVOKE_APPLICATION; + case RENDER_RESPONSE : + return PhaseId.RENDER_RESPONSE; + case ANY_PHASE : + return PhaseId.ANY_PHASE; + default : + throw new IllegalArgumentException ("Couldn't convert "+phaseIdType+" to JSF phase Id"); + } + } + + public static PhaseIdType convert(PhaseId phaseId) { + if (PhaseId.RESTORE_VIEW.equals(phaseId)) { + return RESTORE_VIEW; + } + if (PhaseId.APPLY_REQUEST_VALUES.equals(phaseId)) { + return APPLY_REQUEST_VALUES; + } + if (PhaseId.PROCESS_VALIDATIONS.equals(phaseId)) { + return PROCESS_VALIDATIONS; + } + if (PhaseId.UPDATE_MODEL_VALUES.equals(phaseId)) { + return UPDATE_MODEL_VALUES; + } + if (PhaseId.INVOKE_APPLICATION.equals(phaseId)) { + return INVOKE_APPLICATION; + } + if (PhaseId.RENDER_RESPONSE.equals(phaseId)) { + return RENDER_RESPONSE; + } + if (PhaseId.ANY_PHASE.equals(phaseId)) { + return ANY_PHASE; + } + throw new IllegalArgumentException ("Couldn't convert "+phaseId+" to JSF phase Id"); + } } diff --git a/api/src/main/java/org/jboss/seam/faces/view/action/AnnotatedMethodViewActionHandler.java b/api/src/main/java/org/jboss/seam/faces/view/action/AnnotatedMethodViewActionHandler.java new file mode 100644 index 0000000..d207bbb --- /dev/null +++ b/api/src/main/java/org/jboss/seam/faces/view/action/AnnotatedMethodViewActionHandler.java @@ -0,0 +1,76 @@ +package org.jboss.seam.faces.view.action; + +import java.lang.reflect.Method; +import java.util.Set; + +import javax.enterprise.context.spi.CreationalContext; +import javax.enterprise.inject.spi.AnnotatedMethod; +import javax.enterprise.inject.spi.Bean; +import javax.enterprise.inject.spi.BeanManager; + +import org.jboss.solder.reflection.annotated.InjectableMethod; + +/** + * Invokes method on a CDI bean. + * + * Note : copy from Seam Security's SecurityExtension class. Should be extracted into common utility. + */ +public abstract class AnnotatedMethodViewActionHandler implements ViewActionHandler { + private Bean targetBean; + private BeanManager beanManager; + private InjectableMethod injectableMethod; + private AnnotatedMethod annotatedMethod; + + public AnnotatedMethodViewActionHandler(AnnotatedMethod annotatedMethod, BeanManager beanManager) { + this.beanManager = beanManager; + this.annotatedMethod = annotatedMethod; + } + + public AnnotatedMethod getAnnotatedMethod() { + return annotatedMethod; + } + + public void setAnnotatedMethod(AnnotatedMethod annotatedMethod) { + this.annotatedMethod = annotatedMethod; + } + + public BeanManager getBeanManager() { + return beanManager; + } + + public Object execute() { + if (targetBean == null) { + lookupTargetBean(); + } + CreationalContext cc = beanManager.createCreationalContext(targetBean); + Object reference = beanManager.getReference(targetBean, getAnnotatedMethod().getJavaMember().getDeclaringClass(), + cc); + return injectableMethod.invoke(reference, cc, null); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private synchronized void lookupTargetBean() { + if (targetBean == null) { + AnnotatedMethod annotatedMethod = getAnnotatedMethod(); + Method method = annotatedMethod.getJavaMember(); + Set> beans = beanManager.getBeans(method.getDeclaringClass()); + if (beans.size() == 1) { + targetBean = beans.iterator().next(); + } else if (beans.isEmpty()) { + throw new IllegalStateException("Exception looking up method bean - " + "no beans found for method [" + + method.getDeclaringClass() + "." + method.getName() + "]"); + } else if (beans.size() > 1) { + throw new IllegalStateException("Exception looking up method bean - " + "multiple beans found for method [" + + method.getDeclaringClass().getName() + "." + method.getName() + "]"); + } + injectableMethod = new InjectableMethod(annotatedMethod, targetBean, beanManager); + } + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(this.getClass().getSimpleName()); + builder.append("{method: ").append(getAnnotatedMethod()).append("}"); + return builder.toString(); + } +} diff --git a/api/src/main/java/org/jboss/seam/faces/view/action/Order.java b/api/src/main/java/org/jboss/seam/faces/view/action/Order.java new file mode 100644 index 0000000..5c5e709 --- /dev/null +++ b/api/src/main/java/org/jboss/seam/faces/view/action/Order.java @@ -0,0 +1,22 @@ +package org.jboss.seam.faces.view.action; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This optionnal annotation can be used to modify the execution order of viewActions. + * + *

viewActions will be executed from lowest to highest order. If order is not specified, + * The default value {@link OrderDefault#DEFAULT} is assumed.

+ * + * @author Adriàn Gonzalez + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Order { + int value(); +} diff --git a/api/src/main/java/org/jboss/seam/faces/view/action/OrderDefault.java b/api/src/main/java/org/jboss/seam/faces/view/action/OrderDefault.java new file mode 100644 index 0000000..03a3ee6 --- /dev/null +++ b/api/src/main/java/org/jboss/seam/faces/view/action/OrderDefault.java @@ -0,0 +1,24 @@ +/* + * JBoss, Home of Professional Open Source + * Copyright 2011, Red Hat, Inc., and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jboss.seam.faces.view.action; + +/** + * Default value for viewAction order. + */ +public class OrderDefault { + public static final int DEFAULT = 1000; +} diff --git a/impl/src/main/java/org/jboss/seam/faces/view/action/PhaseInstant.java b/api/src/main/java/org/jboss/seam/faces/view/action/PhaseInstant.java similarity index 91% rename from impl/src/main/java/org/jboss/seam/faces/view/action/PhaseInstant.java rename to api/src/main/java/org/jboss/seam/faces/view/action/PhaseInstant.java index 98b4419..41b0fcc 100644 --- a/impl/src/main/java/org/jboss/seam/faces/view/action/PhaseInstant.java +++ b/api/src/main/java/org/jboss/seam/faces/view/action/PhaseInstant.java @@ -32,6 +32,11 @@ public void setBefore(boolean before) { this.before = before; } + @Override + public String toString() { + return super.toString()+"{phase="+phaseId+",before"+before+"}"; + } + @Override public int hashCode() { return phaseId.hashCode(); diff --git a/api/src/main/java/org/jboss/seam/faces/view/action/SimpleViewActionHandlerProvider.java b/api/src/main/java/org/jboss/seam/faces/view/action/SimpleViewActionHandlerProvider.java new file mode 100644 index 0000000..205fbc2 --- /dev/null +++ b/api/src/main/java/org/jboss/seam/faces/view/action/SimpleViewActionHandlerProvider.java @@ -0,0 +1,36 @@ +package org.jboss.seam.faces.view.action; + +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.List; + +/** + * Base implementation for ViewActionHandlerProvider returning a single ViewActionHandler (view action 'injection points' + * executing a single action). + * + * @author Adriàn Gonzalez + */ +public abstract class SimpleViewActionHandlerProvider extends ViewActionHandlerProviderSupport + implements ViewActionHandler { + + private List actionHandlers; + + public SimpleViewActionHandlerProvider() { + actionHandlers = new ArrayList(); + actionHandlers.add(this); + } + + @Override + public List getActionHandlers() { + return actionHandlers; + } + + @Override + public boolean handles(PhaseInstant phaseInstant) { + return phaseInstant.equals(getPhaseInstant()); + } + + @Override + public abstract Object execute(); + +} diff --git a/api/src/main/java/org/jboss/seam/faces/view/action/ViewAction.java b/api/src/main/java/org/jboss/seam/faces/view/action/ViewAction.java index aeb8ea3..93fe0a4 100644 --- a/api/src/main/java/org/jboss/seam/faces/view/action/ViewAction.java +++ b/api/src/main/java/org/jboss/seam/faces/view/action/ViewAction.java @@ -1,37 +1,106 @@ package org.jboss.seam.faces.view.action; +import java.lang.annotation.Annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.jboss.seam.faces.event.qualifier.RenderResponse; +import org.jboss.seam.faces.event.qualifier.After; +import org.jboss.seam.faces.event.qualifier.Before; +import org.jboss.seam.faces.view.action.el.ElViewAction; +import org.jboss.seam.faces.view.action.el.ElViewActionHandlerProvider; /** - * The EL MethodExpression is executed when this annotation is applied to a ViewConfig. + * Used as a meta-annotation to use on a view action annotation. This view action annotation will be applied to a given view + * (see ViewConfig) with a given view action annotation. + * + *

+ * When creating a view action annotation, you must also implement ViewActionHandlerProvider and ViewActionHandler interfaces. + * Those implementations will execute the action itself. + *

+ * + *

+ * viewAction annotations can be further customized by + *

    + *
  • using the following attributes order (type Long), phase (type Class) and before (class Boolean) attributes.
  • + *
  • using the meta-annotations {@link Order}, {@link Before}, {@link After} or any annotations from package + * org.jboss.seam.faces.event.qualifier.
  • + *
+ *

+ * + *

+ * Sample usage: + * + *

+ * + *

+ * Sample view action annotation : + * + *

+ * @ViewAction(BeginConversationHandlerProvider.class)
+ * @Before
+ * @ApplyRequestValues
+ * @Order(OrderDefault.DEFAULT - 10)
+ * @Target(ElementType.FIELD)
+ * @Retention(RetentionPolicy.RUNTIME)
+ * @Documented
+ * public @interface BeginConversation {
+ *     boolean join() default false;
+ * }
+ * 
+ * + * Sample view action handler provider : + * + *
+ * public class BeginConversationHandlerProvider extends SimpleViewActionHandlerProvider<BeginConversation> {
+ *     private boolean join;
+ * 
+ *     @Inject
+ *     private Conversation conversation;
+ * 
+ *     @Override
+ *     public void doInitialize(BeginConversation annotation) {
+ *         join = annotation.join();
+ *     }
+ * 
+ *     @Override
+ *     public Object execute() {
+ *         if (join && !conversation.isTransient()) {
+ *             return null;
+ *         }
+ *         conversation.begin();
+ *         return null;
+ *     }
+ * }
+ * 
+ * + * Sample usage : + * + *
+ * @ViewConfig
+ * public interface ViewConfigEnum {
+ *     static enum Pages {
+ *         @ViewPattern("/happy/done.xhtml")
+ *         @BeginConversation
+ *         HAPPY_DONE(),
+ *     }
+ * }
+ * 
+ * + *

+ * + *

+ * See {@link ElViewAction} (the view action annotation) and {@link ElViewActionHandlerProvider} for sample usage. + *

* - * The MethodExpression is called by default before RENDER_RESPONSE phase. You can change this - * behaviour by using phase and before fields. - * * @author Adriàn Gonzalez */ -@Target(ElementType.FIELD) +@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ViewAction { - /** - * El MethodExpression - */ - String value(); - - /** - * On which JSF phase must this viewAction be executed ? - */ - Class phase() default RenderResponse.class; - - /** - * Is this viewAction executed before phase ? - */ - boolean before() default true; + /** class implementing view action custom logic. */ + Class> value(); } diff --git a/api/src/main/java/org/jboss/seam/faces/view/action/ViewActionHandler.java b/api/src/main/java/org/jboss/seam/faces/view/action/ViewActionHandler.java new file mode 100644 index 0000000..5feb203 --- /dev/null +++ b/api/src/main/java/org/jboss/seam/faces/view/action/ViewActionHandler.java @@ -0,0 +1,30 @@ +package org.jboss.seam.faces.view.action; + +/** + * This handler encapsulate an action executed during a jsf view processing. + * + * @author Adriàn Gonzalez + */ +public interface ViewActionHandler { + + /** + * Returns true if this action must be executed on phaseInstant JSF lifecycle. + * + *

The returned value should never change during a viewActionHandler life instance.

+ */ + boolean handles(PhaseInstant phaseInstant); + + /** + * Returns execution order. + * + * View actions will be executed from lower to higher precedence. + * This method can return null. In this case, this action will be the last one to be called. + * Default : null. + */ + Integer getOrder(); + + /** + * View action execution. + */ + Object execute(); +} diff --git a/api/src/main/java/org/jboss/seam/faces/view/action/ViewActionHandlerProvider.java b/api/src/main/java/org/jboss/seam/faces/view/action/ViewActionHandlerProvider.java new file mode 100644 index 0000000..b711995 --- /dev/null +++ b/api/src/main/java/org/jboss/seam/faces/view/action/ViewActionHandlerProvider.java @@ -0,0 +1,22 @@ +package org.jboss.seam.faces.view.action; + +import java.lang.annotation.Annotation; +import java.util.List; + +/** + * This interface must be implemented by algorithms handling ViewAction annotations. + * + * TODO : sample + * + * @author Adriàn Gonzalez + */ +public interface ViewActionHandlerProvider { + /** + * Returns list of handlers handling view actions for annotation X. + */ + List getActionHandlers(); + /** + * Called during initialization. + */ + void initialize(X annotation); +} diff --git a/api/src/main/java/org/jboss/seam/faces/view/action/ViewActionHandlerProviderSupport.java b/api/src/main/java/org/jboss/seam/faces/view/action/ViewActionHandlerProviderSupport.java new file mode 100644 index 0000000..7ab5bec --- /dev/null +++ b/api/src/main/java/org/jboss/seam/faces/view/action/ViewActionHandlerProviderSupport.java @@ -0,0 +1,38 @@ +package org.jboss.seam.faces.view.action; + +import java.lang.annotation.Annotation; + +/** + * This support class handles order and phaseInstant retrieval from annotation attributes or meta-annotations. + * + * @author Adriàn Gonzalez + */ +public abstract class ViewActionHandlerProviderSupport implements ViewActionHandlerProvider { + private PhaseInstant phaseInstant; + private Integer order; + + @Override + public final void initialize(X annotation) { + setPhaseInstant(ViewActionHandlerUtils.getPhaseInstantOrDefault(annotation, PhaseInstant.BEFORE_RENDER_RESPONSE)); + setOrder(ViewActionHandlerUtils.getOrder(annotation)); + doInitialize(annotation); + } + + protected abstract void doInitialize(X annotation); + + protected PhaseInstant getPhaseInstant() { + return phaseInstant; + } + + protected void setPhaseInstant(PhaseInstant phaseInstant) { + this.phaseInstant = phaseInstant; + } + + public Integer getOrder() { + return order; + } + + protected void setOrder(Integer order) { + this.order = order; + } +} diff --git a/api/src/main/java/org/jboss/seam/faces/view/action/ViewActionHandlerUtils.java b/api/src/main/java/org/jboss/seam/faces/view/action/ViewActionHandlerUtils.java new file mode 100644 index 0000000..8e12813 --- /dev/null +++ b/api/src/main/java/org/jboss/seam/faces/view/action/ViewActionHandlerUtils.java @@ -0,0 +1,201 @@ +package org.jboss.seam.faces.view.action; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.List; + +import javax.faces.event.PhaseId; + +import org.jboss.seam.faces.event.qualifier.After; +import org.jboss.seam.faces.event.qualifier.ApplyRequestValues; +import org.jboss.seam.faces.event.qualifier.Before; +import org.jboss.seam.faces.event.qualifier.InvokeApplication; +import org.jboss.seam.faces.event.qualifier.ProcessValidations; +import org.jboss.seam.faces.event.qualifier.RenderResponse; +import org.jboss.seam.faces.event.qualifier.UpdateModelValues; +import org.jboss.solder.reflection.Reflections; + +public class ViewActionHandlerUtils { + + // Utility class - no instanciation + private ViewActionHandlerUtils() { + } + + /** + * @see {@link #getPhaseInstant(Annotation)} + */ + public static PhaseInstant getPhaseInstantOrDefault(Annotation annotation, PhaseInstant defaultInstant) { + PhaseInstant phaseInstant = getPhaseInstant(annotation); + return phaseInstant != null ? phaseInstant : defaultInstant; + } + + /** + * Returns phaseInstant for the given annotation. + * + * phaseInstant is calculated from (in order of priority) : + *
    + *
  • attribute before (type boolean).
  • + *
  • attribute phase (type ApplyRequestValues, ProcessValidations, etc...)
  • + *
  • annotation Before or After on annotation.
  • + *
  • annotation phase (type ApplyRequestValues, ProcessValidations, etc...) on annotation.
  • + */ + public static PhaseInstant getPhaseInstant(Annotation annotation) { + PhaseInstant phaseInstant = getPhaseInstantValueFromAttribute(annotation); + if (phaseInstant == null) { + phaseInstant = getPhaseInstant(Arrays.asList(annotation.annotationType().getAnnotations()), annotation); + } + return phaseInstant; + } + + /** + * If annotation has phase or before attribute, returns a corresponding PhaseInstant. If no such attributes exists, returns + * null. + * + * @param annotation + * @return + * @exception IllegalArgumentException If one of those annotations exists but doesn't correspond to required types. + */ + private static PhaseInstant getPhaseInstantValueFromAttribute(Annotation annotation) { + Class phase = attributeValue("phase", Class.class, annotation); + Boolean before = attributeValue("before", Boolean.class, annotation); + if (phase != null || before != null) { + PhaseId phaseId = phase != null ? convert(phase) : PhaseId.RENDER_RESPONSE; + return new PhaseInstant(phaseId, (before != null ? before : true)); + } else { + return null; + } + } + + private static E attributeValue (String attribute, Class expectedType, Annotation annotation) { + Class clazz = annotation.annotationType(); + try { + Method method = Reflections.findDeclaredMethod(clazz, attribute, (Class[]) null); + if (method != null) { + return Reflections.invokeMethod(method, expectedType, annotation); + } else { + return null; + } + } catch (ClassCastException e) { + throw new IllegalArgumentException("invalid annotation " + annotation + + " : should return Class type (i.e. phase=InvokeApplication.class)"); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("invalid annotation " + annotation + " : error accessing " + attribute + + " attribute : " + e.toString(), e); + } catch (RuntimeException e) { + throw new IllegalArgumentException("invalid annotation " + annotation + " : error accessing " + attribute + + " attribute : " + e.toString(), e); + } + } + + public static PhaseInstant getPhaseInstantOrDefault(List annotations, Object parentElement, + PhaseInstant defaultInstant) { + PhaseInstant phaseInstant = getPhaseInstant(annotations, parentElement); + return phaseInstant != null ? phaseInstant : defaultInstant; + } + + /** + * Returns phaseInstant for the given annotation. + * + * phaseInstant is calculated from (in order of priority) : + *
      + *
    • attribute before (type boolean).
    • + *
    • attribute phase (type ApplyRequestValues, ProcessValidations, etc...)
    • + *
    • annotation Before or After on annotation.
    • + *
    • annotation phase (type ApplyRequestValues, ProcessValidations, etc...) on annotation.
    • + */ + public static PhaseInstant getPhaseInstant(List annotations, Object parentElement) { + Boolean before = null; + PhaseId phaseId = null; + for (Annotation annotation : annotations) { + Class annotationType = annotation.annotationType(); + if (annotationType == Before.class) { + if (before != null) { + throw new IllegalStateException("invalid " + parentElement + + ". Cannot be annotated simultaneously with multiples @Before and @After"); + } + before = true; + } else if (annotationType == After.class) { + if (before != null) { + throw new IllegalStateException("invalid " + parentElement + + ". Cannot be annotated simultaneously with multiples @Before and @After"); + } + before = false; + } else if (isPhaseQualifier(annotationType)) { + if (phaseId != null) { + throw new IllegalStateException("invalid " + parentElement + + ". Cannot be annotated simultaneously with multiples JSF lifecycle annotations (" + + annotationType + " and " + phaseId + ")"); + } + phaseId = convert(annotationType); + } + } + if (before == null && phaseId == null) { + return null; + } else if (before != null && phaseId != null) { + return new PhaseInstant(phaseId, before); + } else { + throw new IllegalStateException("invalid " + parentElement + + ". both phaseId and @Before/@After must be specified {phaseId: " + phaseId + ", before: " + before + "}"); + } + } + + /** + * Converts the annotations from package org.jboss.seam.faces.event.qualifier to their corresponding JSF PhaseId. + * + * @throws IllegalArgumentException if annotationType isn't a valid Jsf annotation. + */ + public static PhaseId convert(Class annotationType) { + PhaseId phaseId; + if (annotationType == ApplyRequestValues.class) { + phaseId = PhaseId.APPLY_REQUEST_VALUES; + } else if (annotationType == ProcessValidations.class) { + phaseId = PhaseId.PROCESS_VALIDATIONS; + } else if (annotationType == UpdateModelValues.class) { + phaseId = PhaseId.UPDATE_MODEL_VALUES; + } else if (annotationType == InvokeApplication.class) { + phaseId = PhaseId.INVOKE_APPLICATION; + } else if (annotationType == RenderResponse.class) { + phaseId = PhaseId.RENDER_RESPONSE; + } else { + throw new IllegalArgumentException("Annotation " + annotationType + " doesn't correspond to valid a Jsf phase."); + } + return phaseId; + } + + /** + * Returns true if annotationType is a valid JSF annotation + */ + public static boolean isPhaseQualifier(Class annotationType) { + if (annotationType == ApplyRequestValues.class || annotationType == ProcessValidations.class + || annotationType == UpdateModelValues.class || annotationType == InvokeApplication.class + || annotationType == RenderResponse.class) { + return true; + } else { + return false; + } + } + + /** + * Returns phaseInstant for the given annotation. + * + * phaseInstant is calculated from (in order of priority) : + *
        + *
      • attribute before (type boolean).
      • + *
      • attribute phase (type ApplyRequestValues, ProcessValidations, etc...)
      • + *
      • annotation Before or After on annotation.
      • + *
      • annotation phase (type ApplyRequestValues, ProcessValidations, etc...) on annotation.
      • + */ + public static Integer getOrder(Annotation annotation) { + Integer orderAnnotation = attributeValue("order", Integer.class, annotation); + if (orderAnnotation != null) { + return orderAnnotation; + } + Order order = annotation.annotationType().getAnnotation(Order.class); + if (order != null) { + return order.value(); + } + return null; + } +} diff --git a/api/src/main/java/org/jboss/seam/faces/view/action/ViewActionBindingType.java b/api/src/main/java/org/jboss/seam/faces/view/action/binding/ViewActionBindingType.java similarity index 92% rename from api/src/main/java/org/jboss/seam/faces/view/action/ViewActionBindingType.java rename to api/src/main/java/org/jboss/seam/faces/view/action/binding/ViewActionBindingType.java index 6f54760..31f8e50 100644 --- a/api/src/main/java/org/jboss/seam/faces/view/action/ViewActionBindingType.java +++ b/api/src/main/java/org/jboss/seam/faces/view/action/binding/ViewActionBindingType.java @@ -1,4 +1,4 @@ -package org.jboss.seam.faces.view.action; +package org.jboss.seam.faces.view.action.binding; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; diff --git a/api/src/main/java/org/jboss/seam/faces/view/action/ViewController.java b/api/src/main/java/org/jboss/seam/faces/view/action/controller/ViewController.java similarity index 90% rename from api/src/main/java/org/jboss/seam/faces/view/action/ViewController.java rename to api/src/main/java/org/jboss/seam/faces/view/action/controller/ViewController.java index f040d1a..c3925c4 100644 --- a/api/src/main/java/org/jboss/seam/faces/view/action/ViewController.java +++ b/api/src/main/java/org/jboss/seam/faces/view/action/controller/ViewController.java @@ -1,4 +1,4 @@ -package org.jboss.seam.faces.view.action; +package org.jboss.seam.faces.view.action.controller; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; @@ -13,6 +13,7 @@ import org.jboss.seam.faces.event.qualifier.ProcessValidations; import org.jboss.seam.faces.event.qualifier.RenderResponse; import org.jboss.seam.faces.event.qualifier.UpdateModelValues; +import org.jboss.seam.faces.view.action.ViewAction; /** * This annotation must be used on a ViewConfig to specify its viewControllers. @@ -31,6 +32,7 @@ * * @author Adriàn Gonzalez */ +@ViewAction(ViewControllerHandlerProvider.class) @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) @Documented diff --git a/api/src/main/java/org/jboss/seam/faces/view/action/controller/ViewControllerHandlerProvider.java b/api/src/main/java/org/jboss/seam/faces/view/action/controller/ViewControllerHandlerProvider.java new file mode 100644 index 0000000..f5fe4ac --- /dev/null +++ b/api/src/main/java/org/jboss/seam/faces/view/action/controller/ViewControllerHandlerProvider.java @@ -0,0 +1,88 @@ +package org.jboss.seam.faces.view.action.controller; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.enterprise.inject.spi.AnnotatedMethod; +import javax.enterprise.inject.spi.AnnotatedType; +import javax.enterprise.inject.spi.BeanManager; +import javax.inject.Inject; + +import org.jboss.seam.faces.view.action.AnnotatedMethodViewActionHandler; +import org.jboss.seam.faces.view.action.PhaseInstant; +import org.jboss.seam.faces.view.action.ViewActionHandler; +import org.jboss.seam.faces.view.action.ViewActionHandlerProvider; +import org.jboss.seam.faces.view.action.ViewActionHandlerUtils; + +public class ViewControllerHandlerProvider implements ViewActionHandlerProvider { + + @Inject + private BeanManager beanManager; + private List> viewControllerClasses = new ArrayList>(); + private List viewActionHandlers = new ArrayList(); + + @Override + public void initialize(ViewController annotation) { + this.viewControllerClasses = Arrays.asList(annotation.value()); + for (Class viewControllerClass : viewControllerClasses) { + Class current = viewControllerClass; + while (current != Object.class) { + for (Method method : current.getDeclaredMethods()) { + PhaseInstant phaseInstant = ViewActionHandlerUtils.getPhaseInstant(Arrays.asList(method.getAnnotations()), + method); + if (phaseInstant != null) { + viewActionHandlers.add(new ViewControllerHandler(method, beanManager)); + } + } + current = current.getSuperclass(); + } + } + } + + @Override + public List getActionHandlers() { + return viewActionHandlers; + } + + /** + * Invokes method on a CDI bean. + * + * Note : copy from Seam Security's SecurityExtension class. Should be extracted into common utility. + */ + private static class ViewControllerHandler extends AnnotatedMethodViewActionHandler { + + private PhaseInstant phaseInstant; + + public ViewControllerHandler(Method method, BeanManager beanManager) { + super(null, beanManager); + setAnnotatedMethod(convert(method)); + phaseInstant = ViewActionHandlerUtils.getPhaseInstant(Arrays.asList(method.getAnnotations()), method); + } + + @Override + public boolean handles(PhaseInstant phaseInstant) { + return this.phaseInstant.equals(phaseInstant); + } + + private AnnotatedMethod convert(Method method) { + AnnotatedType annotatedType = getBeanManager().createAnnotatedType(method.getDeclaringClass()); + AnnotatedMethod annotatedMethod = null; + for (AnnotatedMethod current : annotatedType.getMethods()) { + if (current.getJavaMember().equals(method)) { + annotatedMethod = current; + } + } + if (annotatedMethod == null) { + throw new IllegalStateException("No matching annotated method found for method : " + method); + } + return annotatedMethod; + } + + @Override + public Integer getOrder() { + return null; + } + } +} diff --git a/api/src/main/java/org/jboss/seam/faces/view/action/el/BeginConversation.java b/api/src/main/java/org/jboss/seam/faces/view/action/el/BeginConversation.java new file mode 100644 index 0000000..1ec5388 --- /dev/null +++ b/api/src/main/java/org/jboss/seam/faces/view/action/el/BeginConversation.java @@ -0,0 +1,24 @@ +package org.jboss.seam.faces.view.action.el; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.jboss.seam.faces.event.qualifier.ApplyRequestValues; +import org.jboss.seam.faces.event.qualifier.Before; +import org.jboss.seam.faces.view.action.Order; +import org.jboss.seam.faces.view.action.OrderDefault; +import org.jboss.seam.faces.view.action.ViewAction; + +@ViewAction(BeginConversationHandlerProvider.class) +@Before +@ApplyRequestValues +@Order(OrderDefault.DEFAULT-10) +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface BeginConversation { + boolean join() default false; +} diff --git a/api/src/main/java/org/jboss/seam/faces/view/action/el/BeginConversationHandlerProvider.java b/api/src/main/java/org/jboss/seam/faces/view/action/el/BeginConversationHandlerProvider.java new file mode 100644 index 0000000..47651c5 --- /dev/null +++ b/api/src/main/java/org/jboss/seam/faces/view/action/el/BeginConversationHandlerProvider.java @@ -0,0 +1,27 @@ +package org.jboss.seam.faces.view.action.el; + +import javax.enterprise.context.Conversation; +import javax.inject.Inject; + +import org.jboss.seam.faces.view.action.SimpleViewActionHandlerProvider; + +public class BeginConversationHandlerProvider extends SimpleViewActionHandlerProvider { + private boolean join; + + @Inject + private Conversation conversation; + + @Override + public void doInitialize(BeginConversation annotation) { + join = annotation.join(); + } + + @Override + public Object execute() { + if (join && !conversation.isTransient()) { + return null; + } + conversation.begin(); + return null; + } +} diff --git a/api/src/main/java/org/jboss/seam/faces/view/action/el/ElViewAction.java b/api/src/main/java/org/jboss/seam/faces/view/action/el/ElViewAction.java new file mode 100644 index 0000000..62b4398 --- /dev/null +++ b/api/src/main/java/org/jboss/seam/faces/view/action/el/ElViewAction.java @@ -0,0 +1,39 @@ +package org.jboss.seam.faces.view.action.el; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.jboss.seam.faces.event.qualifier.RenderResponse; +import org.jboss.seam.faces.view.action.ViewAction; + +/** + * The EL MethodExpression is executed when this annotation is applied to a ViewConfig. + * + * The MethodExpression is called by default before RENDER_RESPONSE phase. You can change this + * behaviour by using phase and before fields. + * + * @author Adriàn Gonzalez + */ +@ViewAction(ElViewActionHandlerProvider.class) +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface ElViewAction { + /** + * El MethodExpression + */ + String value(); + + /** + * On which JSF phase must this viewAction be executed ? + */ + Class phase() default RenderResponse.class; + + /** + * Is this viewAction executed before phase ? + */ + boolean before() default true; +} diff --git a/api/src/main/java/org/jboss/seam/faces/view/action/el/ElViewActionHandlerProvider.java b/api/src/main/java/org/jboss/seam/faces/view/action/el/ElViewActionHandlerProvider.java new file mode 100644 index 0000000..9c98644 --- /dev/null +++ b/api/src/main/java/org/jboss/seam/faces/view/action/el/ElViewActionHandlerProvider.java @@ -0,0 +1,26 @@ +package org.jboss.seam.faces.view.action.el; + +import javax.el.MethodExpression; +import javax.inject.Inject; + +import org.jboss.seam.faces.view.action.SimpleViewActionHandlerProvider; +import org.jboss.solder.el.Expressions; + +public class ElViewActionHandlerProvider extends SimpleViewActionHandlerProvider { + + @Inject + private Expressions expressions; + + private MethodExpression methodExpression; + + @Override + protected void doInitialize(ElViewAction annotation) { + methodExpression = expressions.getExpressionFactory().createMethodExpression(expressions.getELContext(), + annotation.value(), null, new Class[] {}); + } + + @Override + public Object execute() { + return methodExpression.invoke(expressions.getELContext(), new Object[] {}); + } +} diff --git a/api/src/main/java/org/jboss/seam/faces/view/config/ViewConfigDescriptor.java b/api/src/main/java/org/jboss/seam/faces/view/config/ViewConfigDescriptor.java index bba2d1e..2e275b4 100644 --- a/api/src/main/java/org/jboss/seam/faces/view/config/ViewConfigDescriptor.java +++ b/api/src/main/java/org/jboss/seam/faces/view/config/ViewConfigDescriptor.java @@ -3,9 +3,17 @@ import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import javax.faces.event.PhaseId; + +import org.jboss.seam.faces.view.action.OrderDefault; +import org.jboss.seam.faces.view.action.PhaseInstant; +import org.jboss.seam.faces.view.action.ViewActionHandler; import org.jboss.solder.logging.Logger; /** @@ -21,6 +29,8 @@ public class ViewConfigDescriptor { private List metaData = new ArrayList(); private final ConcurrentHashMap, Annotation> metaDataByAnnotation = new ConcurrentHashMap, Annotation>(); private ConcurrentHashMap, List> metaDataByQualifier = new ConcurrentHashMap, List>(); + private List viewActionHandlers = new ArrayList(); + private Map> viewActionHandlersPerPhaseInstant = new HashMap>(); /** * ViewConfigDescriptor for view viewId @@ -44,7 +54,7 @@ public void addValue(Object value) { values.add(value); } } - + public List getValues() { return values; } @@ -55,10 +65,10 @@ public void setValues(List values) { public void addMetaData(Annotation metaData) { this.metaData.add(metaData); - //add to metaDataByAnnotation + // add to metaDataByAnnotation metaDataByAnnotation.put(metaData.annotationType(), metaData); log.debugf("Putting new annotation (type: %s) for viewId: %s", metaData.annotationType().getName(), getViewId()); - //add to metaDataByQualifier + // add to metaDataByQualifier Annotation[] annotations = metaData.annotationType().getAnnotations(); for (Annotation qualifier : annotations) { if (qualifier.annotationType().getName().startsWith("java.")) { @@ -66,17 +76,60 @@ public void addMetaData(Annotation metaData) { continue; } List qualifiedAnnotations = new ArrayList(); - List exisitngQualifiedAnnotations = metaDataByQualifier.get(qualifier - .annotationType()); + List exisitngQualifiedAnnotations = metaDataByQualifier.get(qualifier.annotationType()); if (exisitngQualifiedAnnotations != null && !exisitngQualifiedAnnotations.isEmpty()) { qualifiedAnnotations.addAll(exisitngQualifiedAnnotations); } qualifiedAnnotations.add(metaData); - log.debugf("Adding new annotation (type: %s) for Qualifier %s", metaData.annotationType().getName(), qualifier.annotationType().getName()); + log.debugf("Adding new annotation (type: %s) for Qualifier %s", metaData.annotationType().getName(), qualifier + .annotationType().getName()); metaDataByQualifier.put(qualifier.annotationType(), qualifiedAnnotations); } } - + + public List getViewActionHandlers() { + return viewActionHandlers; + } + + public void addViewActionHandler(ViewActionHandler viewActionHandler) { + this.viewActionHandlers.add(viewActionHandler); + Collections.sort(this.viewActionHandlers, ViewActionHandlerComparator.INSTANCE); + for (PhaseId phaseId : PhaseId.VALUES) { + addViewActionHandlerPerPhaseInstant(new PhaseInstant(phaseId, true), viewActionHandler); + addViewActionHandlerPerPhaseInstant(new PhaseInstant(phaseId, false), viewActionHandler); + } + } + + private void addViewActionHandlerPerPhaseInstant(PhaseInstant phaseInstant, ViewActionHandler viewActionHandler) { + if (viewActionHandler.handles(phaseInstant)) { + List viewActionHandlers = viewActionHandlersPerPhaseInstant.get(phaseInstant); + if (viewActionHandlers == null) { + viewActionHandlers = new ArrayList(); + viewActionHandlersPerPhaseInstant.put(phaseInstant, viewActionHandlers); + } + viewActionHandlers.add(viewActionHandler); + } + } + + public void executeBeforePhase(PhaseId phaseId) { + List viewActionHandlers = viewActionHandlersPerPhaseInstant.get(new PhaseInstant(phaseId, true)); + if (viewActionHandlers != null) { + for (int i = viewActionHandlers.size(); --i >= 0;) { + ViewActionHandler viewActionHandler = viewActionHandlers.get(i); + viewActionHandler.execute(); + } + } + } + + public void executeAfterPhase(PhaseId phaseId) { + List viewActionHandlers = viewActionHandlersPerPhaseInstant.get(new PhaseInstant(phaseId, false)); + if (viewActionHandlers != null) { + for (ViewActionHandler viewActionHandler : viewActionHandlers) { + viewActionHandler.execute(); + } + } + } + /** * Returns read-only list. * @@ -85,14 +138,14 @@ public void addMetaData(Annotation metaData) { public List getMetaData() { return Collections.unmodifiableList(metaData); } - + /** * returns all metaData of the corresponding type. */ public T getMetaData(Class type) { return (T) metaDataByAnnotation.get(type); } - + /** * returns all qualified data from metadata annotations. * @@ -101,10 +154,36 @@ public T getMetaData(Class type) { @SuppressWarnings("unchecked") public List getAllQualifierData(Class qualifier) { List metaData = metaDataByQualifier.get(qualifier); - return metaData!=null ? Collections.unmodifiableList(metaData) : Collections.EMPTY_LIST; + return metaData != null ? Collections.unmodifiableList(metaData) : Collections.EMPTY_LIST; } - + public String toString() { - return super.toString()+"{viewId="+getViewId()+"}"; + return super.toString() + "{viewId=" + getViewId() + "}"; + } + + private static class ViewActionHandlerComparator implements Comparator { + + @Override + public int compare(ViewActionHandler o1, ViewActionHandler o2) { + Integer o1Order = o1.getOrder(); + Integer o2Order = o2.getOrder(); + if (o1Order == null) { + o1Order = OrderDefault.DEFAULT; + } + if (o2Order == null) { + o2Order = OrderDefault.DEFAULT; + } + if (o1Order == o2Order) { + return 0; + } + if (o1Order > o2Order) { + return 1; + } else { + return -1; + } + } + + public static final ViewActionHandlerComparator INSTANCE = new ViewActionHandlerComparator(); + } } diff --git a/api/src/main/java/org/jboss/seam/faces/view/config/ViewConfigStore.java b/api/src/main/java/org/jboss/seam/faces/view/config/ViewConfigStore.java index f8b85de..19aec1b 100644 --- a/api/src/main/java/org/jboss/seam/faces/view/config/ViewConfigStore.java +++ b/api/src/main/java/org/jboss/seam/faces/view/config/ViewConfigStore.java @@ -59,5 +59,19 @@ public interface ViewConfigStore { /** * return the registered viewConfigs */ - public List getAllViewConfigDescriptors(); + public List getViewConfigDescriptors(); + + /** + * return the viewConfig for the given viewId + */ + public ViewConfigDescriptor getViewConfigDescriptor(String viewId); + + /** + * Similar to {@link #getViewConfigDescriptor(String)} except that the previous + * method can have patterned viewIds (i.e. /client/*). + * + * This method merges all ViewConfigDescriptor corresponding to the + * requested viewId into one instance of ViewConfigDescriptor (in order to speed up performance). + */ + public ViewConfigDescriptor getRuntimeViewConfigDescriptor(String viewId); } diff --git a/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/MyAppViewConfig.java b/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/MyAppViewConfig.java index c2700e5..60011b2 100644 --- a/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/MyAppViewConfig.java +++ b/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/MyAppViewConfig.java @@ -22,8 +22,8 @@ import org.jboss.seam.faces.rewrite.UrlMapping; import org.jboss.seam.faces.security.AccessDeniedView; import org.jboss.seam.faces.security.LoginView; -import org.jboss.seam.faces.view.action.ViewAction; -import org.jboss.seam.faces.view.action.ViewController; +import org.jboss.seam.faces.view.action.controller.ViewController; +import org.jboss.seam.faces.view.action.el.ElViewAction; import org.jboss.seam.faces.view.config.ViewConfig; import org.jboss.seam.faces.view.config.ViewPattern; @@ -53,7 +53,7 @@ static enum Pages { VIEW_ACTION_BINDING_TYPE, @ViewPattern("/viewaction.xhtml") - @ViewAction("#{viewActionController.preRenderAction}") + @ElViewAction("#{viewActionController.preRenderAction}") VIEW_ACTION, @FacesRedirect diff --git a/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/MyViewAction.java b/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/MyViewAction.java index 8725143..9bceb39 100644 --- a/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/MyViewAction.java +++ b/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/MyViewAction.java @@ -5,7 +5,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.jboss.seam.faces.view.action.ViewActionBindingType; +import org.jboss.seam.faces.view.action.binding.ViewActionBindingType; @ViewActionBindingType @Retention(RetentionPolicy.RUNTIME) diff --git a/impl/src/main/java/org/jboss/seam/faces/security/RestrictViewActionExtension.java b/impl/src/main/java/org/jboss/seam/faces/security/RestrictViewActionExtension.java new file mode 100644 index 0000000..e243ce3 --- /dev/null +++ b/impl/src/main/java/org/jboss/seam/faces/security/RestrictViewActionExtension.java @@ -0,0 +1,92 @@ +package org.jboss.seam.faces.security; + +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import javax.enterprise.event.Observes; +import javax.enterprise.inject.spi.AfterDeploymentValidation; +import javax.enterprise.inject.spi.BeanManager; +import javax.enterprise.inject.spi.Extension; +import javax.faces.context.FacesContext; + +import org.jboss.seam.faces.event.PhaseIdType; +import org.jboss.seam.faces.event.PostLoginEvent; +import org.jboss.seam.faces.event.PreNavigateEvent; +import org.jboss.seam.faces.view.action.NonContextual; +import org.jboss.seam.faces.view.action.NonContextual.Instance; +import org.jboss.seam.faces.view.action.ViewActionHandler; +import org.jboss.seam.faces.view.config.ViewConfigDescriptor; +import org.jboss.seam.faces.view.config.ViewConfigStore; +import org.jboss.seam.security.Identity; +import org.jboss.seam.security.annotations.SecurityBindingType; +import org.jboss.solder.logging.Logger; + +public class RestrictViewActionExtension implements Extension { + private final Logger log = Logger.getLogger(RestrictViewActionExtension.class); + private ViewConfigStore viewConfigStore; + private BeanManager beanManager; + private List> nonContextualObjects = new ArrayList>(); + + public void setup(@Observes AfterDeploymentValidation event, javax.enterprise.inject.Instance viewConfigStore, BeanManager beanManager) { + this.beanManager = beanManager; + if (viewConfigStore.isUnsatisfied()) { + // extension disabled : surely because we're in come UT context (i.e. ProjectStageExtensionTest) + // not adding ViewConfigStore in ShrinkWrap archive + log.warn("ViewConfigStore dependency missing - RestrictViewActionExtension disabled"); + return; + } + this.viewConfigStore = viewConfigStore.get(); + registerViewActions(); + } + + private void registerViewActions() { + List viewConfigDescriptors = viewConfigStore.getViewConfigDescriptors(); + for (ViewConfigDescriptor viewConfigDescriptor : viewConfigDescriptors) { + List allSecurityAnnotations = viewConfigDescriptor + .getAllQualifierData(SecurityBindingType.class); + RestrictViewActionHandlerProvider provider = newViewActionProvider(allSecurityAnnotations, + RestrictViewActionUtils.getDefaultPhases(viewConfigDescriptor.getViewId(), viewConfigStore)); + for (ViewActionHandler viewActionHandler : provider.getActionHandlers()) { + viewConfigDescriptor.addViewActionHandler(viewActionHandler); + } + } + } + + /** + * Monitor PreNavigationEvents, looking for a successful navigation from the Seam Security login button. When such a + * navigation is encountered, redirect to the the viewId captured before the login redirect was triggered. + * + * @param event + */ + public void observePreNavigateEvent(@Observes PreNavigateEvent event) { + log.debugf("PreNavigateEvent observed %s, %s", event.getOutcome(), event.getFromAction()); + if (Identity.RESPONSE_LOGIN_SUCCESS.equals(event.getOutcome()) && "#{identity.login}".equals(event.getFromAction())) { + FacesContext context = event.getContext(); + Map sessionMap = context.getExternalContext().getSessionMap(); + beanManager.fireEvent(new PostLoginEvent(context, sessionMap)); + } + } + + /** + * Creates a viewActionProvider which can be injected using CDI annotations. + * + * @param annotation + * @param providerClazz + * @return + */ + private RestrictViewActionHandlerProvider newViewActionProvider(List securedAnnotations, + PhaseIdType[] defaultPhases) { + NonContextual nonContextual = new NonContextual( + beanManager, RestrictViewActionHandlerProvider.class); + Instance instance = nonContextual.newInstance(); + instance.produce(); + instance.inject(); + instance.postConstruct(); + nonContextualObjects.add(instance); + RestrictViewActionHandlerProvider provider = instance.get(); + provider.initialize(securedAnnotations, defaultPhases); + return provider; + } +} diff --git a/impl/src/main/java/org/jboss/seam/faces/security/RestrictViewActionHandlerProvider.java b/impl/src/main/java/org/jboss/seam/faces/security/RestrictViewActionHandlerProvider.java new file mode 100644 index 0000000..46b6c15 --- /dev/null +++ b/impl/src/main/java/org/jboss/seam/faces/security/RestrictViewActionHandlerProvider.java @@ -0,0 +1,178 @@ +package org.jboss.seam.faces.security; + +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import javax.enterprise.inject.Instance; +import javax.enterprise.inject.spi.BeanManager; +import javax.faces.application.NavigationHandler; +import javax.faces.component.UIViewRoot; +import javax.faces.context.FacesContext; +import javax.inject.Inject; + +import org.jboss.seam.faces.event.PhaseIdType; +import org.jboss.seam.faces.event.PreLoginEvent; +import org.jboss.seam.faces.view.action.PhaseInstant; +import org.jboss.seam.faces.view.action.ViewActionHandler; +import org.jboss.seam.faces.view.config.ViewConfigStore; +import org.jboss.seam.security.Identity; +import org.jboss.seam.security.events.AuthorizationCheckEvent; +import org.jboss.seam.security.events.NotAuthorizedEvent; +import org.jboss.solder.logging.Logger; + +public class RestrictViewActionHandlerProvider { + + private final Logger log = Logger.getLogger(RestrictViewActionHandlerProvider.class); + public static int ORDER = 10; + private List viewActionHandlers; + private List securedAnnotations; + @Inject + private BeanManager beanManager; + @Inject + private Instance identity; + @Inject + private ViewConfigStore viewConfigStore; + + public RestrictViewActionHandlerProvider() { + } + + /** + * This method must be called just after instanciation. + * + * @param securedAnnotations + */ + public void initialize(List securedAnnotations, PhaseIdType[] defaultPhases) { + this.securedAnnotations = securedAnnotations; + this.viewActionHandlers = viewActionHandlers(defaultPhases); + } + + private List viewActionHandlers(PhaseIdType[] defaultPhases) { + List viewActionHandlers = new ArrayList(); + for (PhaseIdType phase : PhaseIdType.values()) { + List applicableSecurityAnnotations = new ArrayList(); + for (Annotation annotation : securedAnnotations) { + if (RestrictViewActionUtils.isAnnotationApplicableToPhase(annotation, phase, defaultPhases, beanManager)) { + if (applicableSecurityAnnotations == null) { // avoid spawning arrays at all phases of the lifecycle + applicableSecurityAnnotations = new ArrayList(); + } + applicableSecurityAnnotations.add(annotation); + } + } + if (applicableSecurityAnnotations.size() > 0) { + viewActionHandlers.add(new RestrictViewActionHandler(applicableSecurityAnnotations, new PhaseInstant( + PhaseIdType.convert(phase), true))); + } + } + return viewActionHandlers; + } + + public List getActionHandlers() { + return viewActionHandlers; + } + + /** + * Invokes method on a CDI bean. + * + * Note : copy from Seam Security's SecurityExtension class. Should be extracted into common utility. + */ + public class RestrictViewActionHandler implements ViewActionHandler { + + private PhaseInstant phaseInstant; + private List annotations; + + public RestrictViewActionHandler(List securedAnnotations, PhaseInstant phaseInstant) { + this.phaseInstant = phaseInstant; + this.annotations = securedAnnotations; + } + + @Override + public boolean handles(PhaseInstant phaseInstant) { + return this.phaseInstant.equals(phaseInstant); + } + + @Override + public Integer getOrder() { + return ORDER; + } + + @Override + public Object execute() { + if (annotations == null || annotations.isEmpty()) { + log.debug("Annotations is null/empty"); + return null; + } + AuthorizationCheckEvent event = new AuthorizationCheckEvent(annotations); + beanManager.fireEvent(event); + FacesContext context = FacesContext.getCurrentInstance(); + UIViewRoot viewRoot = context.getViewRoot(); + if (!event.isPassed()) { + if (!identity.get().isLoggedIn()) { + log.debug("Access denied - not logged in"); + redirectToLoginPage(context, viewRoot); + return null; + } else { + log.debug("Access denied - not authorized"); + beanManager.fireEvent(new NotAuthorizedEvent()); + redirectToAccessDeniedView(context, viewRoot); + return null; + } + } else { + log.debug("Access granted"); + } + return null; + } + + /** + * Perform the navigation to the @LoginView. If not @LoginView is defined, return a 401 response. The original view id + * requested by the user is stored in the session map, for use after a successful login. + * + * @param context + * @param viewRoot + */ + private void redirectToLoginPage(FacesContext context, UIViewRoot viewRoot) { + Map sessionMap = context.getExternalContext().getSessionMap(); + beanManager.fireEvent(new PreLoginEvent(context, sessionMap)); + LoginView loginView = viewConfigStore.getAnnotationData(viewRoot.getViewId(), LoginView.class); + if (loginView == null || loginView.value() == null || loginView.value().isEmpty()) { + log.debug("Returning 401 response (login required)"); + context.getExternalContext().setResponseStatus(401); + context.responseComplete(); + return; + } + String loginViewId = loginView.value(); + log.debugf("Redirecting to configured LoginView %s", loginViewId); + NavigationHandler navHandler = context.getApplication().getNavigationHandler(); + navHandler.handleNavigation(context, "", loginViewId); + context.renderResponse(); + } + + /** + * Perform the navigation to the @AccessDeniedView. If not @AccessDeniedView is defined, return a 401 response + * + * @param context + * @param viewRoot + */ + private void redirectToAccessDeniedView(FacesContext context, UIViewRoot viewRoot) { + // If a user has already done a redirect and rendered the response (possibly in an observer) we cannot do this + // output + if (!(context.getResponseComplete() || context.getRenderResponse())) { + AccessDeniedView accessDeniedView = viewConfigStore.getAnnotationData(viewRoot.getViewId(), + AccessDeniedView.class); + if (accessDeniedView == null || accessDeniedView.value() == null || accessDeniedView.value().isEmpty()) { + log.warn("No AccessDeniedView is configured, returning 401 response (access denied). Please configure an AccessDeniedView in the ViewConfig."); + context.getExternalContext().setResponseStatus(401); + context.responseComplete(); + return; + } + String accessDeniedViewId = accessDeniedView.value(); + log.debugf("Redirecting to configured AccessDenied %s", accessDeniedViewId); + NavigationHandler navHandler = context.getApplication().getNavigationHandler(); + navHandler.handleNavigation(context, "", accessDeniedViewId); + context.renderResponse(); + } + } + } + +} diff --git a/impl/src/main/java/org/jboss/seam/faces/security/RestrictViewActionUtils.java b/impl/src/main/java/org/jboss/seam/faces/security/RestrictViewActionUtils.java new file mode 100644 index 0000000..dcad580 --- /dev/null +++ b/impl/src/main/java/org/jboss/seam/faces/security/RestrictViewActionUtils.java @@ -0,0 +1,114 @@ +package org.jboss.seam.faces.security; + +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; + +import javax.enterprise.inject.spi.BeanManager; + +import org.jboss.seam.faces.event.PhaseIdType; +import org.jboss.seam.faces.view.config.ViewConfigStore; +import org.jboss.solder.logging.Logger; +import org.jboss.solder.reflection.AnnotationInspector; + +/** + * A utility providing @Restrict manipulation functionnality. + * + * Usefull for internal unit testing. + */ +public class RestrictViewActionUtils { + + private static final Logger log = Logger.getLogger(RestrictViewActionUtils.class); + + /** + * Inspect an annotation to see if it specifies a view in which it should be. Fall back on default view otherwise. + * + * @param annotation + * @param currentPhase + * @param defaultPhases + * @param beanManager + * @return true if the annotation is applicable to this view and phase, false otherwise + */ + public static boolean isAnnotationApplicableToPhase(Annotation annotation, PhaseIdType currentPhase, + PhaseIdType[] defaultPhases, BeanManager beanManager) { + Method restrictAtViewMethod = getRestrictAtViewMethod(annotation); + PhaseIdType[] phasedIds = null; + if (restrictAtViewMethod != null) { + log.warnf("Annotation %s is using the restrictAtViewMethod. Use a @RestrictAtPhase qualifier on the annotation instead."); + phasedIds = getRestrictedPhaseIds(restrictAtViewMethod, annotation); + } + RestrictAtPhase restrictAtPhaseQualifier = AnnotationInspector.getAnnotation(annotation.annotationType(), + RestrictAtPhase.class, beanManager); + if (restrictAtPhaseQualifier != null) { + log.debug("Using Phases found in @RestrictAtView qualifier on the annotation."); + phasedIds = restrictAtPhaseQualifier.value(); + } + if (phasedIds == null) { + log.debug("Falling back on default phase ids"); + phasedIds = defaultPhases; + } + if (Arrays.binarySearch(phasedIds, currentPhase) >= 0) { + return true; + } + return false; + } + + /** + * Utility method to extract the "restrictAtPhase" method from an annotation + * + * @param annotation + * @return restrictAtViewMethod if found, null otherwise + */ + public static Method getRestrictAtViewMethod(Annotation annotation) { + Method restrictAtViewMethod; + try { + restrictAtViewMethod = annotation.annotationType().getDeclaredMethod("restrictAtPhase"); + } catch (NoSuchMethodException ex) { + restrictAtViewMethod = null; + } catch (SecurityException ex) { + throw new IllegalArgumentException("restrictAtView method must be accessible", ex); + } + return restrictAtViewMethod; + } + + /** + * Retrieve the default PhaseIdTypes defined by the restrictAtViewMethod in the annotation + * + * @param restrictAtViewMethod + * @param annotation + * @return PhaseIdTypes from the restrictAtViewMethod, null if empty + */ + public static PhaseIdType[] getRestrictedPhaseIds(Method restrictAtViewMethod, Annotation annotation) { + PhaseIdType[] phaseIds; + try { + phaseIds = (PhaseIdType[]) restrictAtViewMethod.invoke(annotation); + } catch (IllegalAccessException ex) { + throw new IllegalArgumentException("restrictAtView method must be accessible", ex); + } catch (InvocationTargetException ex) { + throw new RuntimeException(ex); + } + return phaseIds; + } + + /** + * Get the default phases at which restrictions should be applied, by looking for a @RestrictAtPhase on a matching + * + * @param viewId + * @param viewConfigStore + * @return default phases for a view + * @ViewPattern, falling back on global defaults if none are found + */ + public static PhaseIdType[] getDefaultPhases(String viewId, ViewConfigStore viewConfigStore) { + PhaseIdType[] defaultPhases = null; + RestrictAtPhase restrictAtPhase = viewConfigStore.getAnnotationData(viewId, RestrictAtPhase.class); + if (restrictAtPhase != null) { + defaultPhases = restrictAtPhase.value(); + } + if (defaultPhases == null) { + defaultPhases = RestrictAtPhaseDefault.DEFAULT_PHASES; + } + return defaultPhases; + } + +} diff --git a/impl/src/main/java/org/jboss/seam/faces/security/SecurityPhaseListener.java b/impl/src/main/java/org/jboss/seam/faces/security/SecurityPhaseListener.java deleted file mode 100644 index b3ac7e9..0000000 --- a/impl/src/main/java/org/jboss/seam/faces/security/SecurityPhaseListener.java +++ /dev/null @@ -1,362 +0,0 @@ -/* - * JBoss, Home of Professional Open Source - * Copyright 2011, Red Hat, Inc., and individual contributors - * by the @authors tag. See the copyright.txt in the distribution for a - * full listing of individual contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.jboss.seam.faces.security; - -import java.lang.annotation.Annotation; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; - -import javax.enterprise.event.Event; -import javax.enterprise.event.Observes; -import javax.enterprise.inject.spi.BeanManager; -import javax.faces.application.NavigationHandler; -import javax.faces.component.UIViewRoot; -import javax.faces.context.FacesContext; -import javax.faces.event.PhaseEvent; -import javax.inject.Inject; - -import org.jboss.seam.faces.event.PhaseIdType; -import org.jboss.seam.faces.event.PostLoginEvent; -import org.jboss.seam.faces.event.PreLoginEvent; -import org.jboss.seam.faces.event.PreNavigateEvent; -import org.jboss.seam.faces.event.qualifier.After; -import org.jboss.seam.faces.event.qualifier.ApplyRequestValues; -import org.jboss.seam.faces.event.qualifier.Before; -import org.jboss.seam.faces.event.qualifier.InvokeApplication; -import org.jboss.seam.faces.event.qualifier.ProcessValidations; -import org.jboss.seam.faces.event.qualifier.RenderResponse; -import org.jboss.seam.faces.event.qualifier.RestoreView; -import org.jboss.seam.faces.event.qualifier.UpdateModelValues; -import org.jboss.seam.faces.view.config.ViewConfigStore; -import org.jboss.solder.logging.Logger; -import org.jboss.seam.security.Identity; -import org.jboss.seam.security.annotations.SecurityBindingType; -import org.jboss.seam.security.events.AuthorizationCheckEvent; -import org.jboss.seam.security.events.NotAuthorizedEvent; -import org.jboss.solder.core.Requires; -import org.jboss.solder.reflection.AnnotationInspector; - -/** - * Use the annotations stored in the ViewConfigStore to restrict view access. - * Authorization is delegated to Seam Security through by firing a AuthorizationCheckEvent. - * - * @author Brian Leathem - */ -@Requires("org.jboss.seam.security.SecurityExtension") -public class SecurityPhaseListener { - - private transient final Logger log = Logger.getLogger(SecurityPhaseListener.class); - - @Inject - private ViewConfigStore viewConfigStore; - @Inject - private Event authorizationCheckEvent; - @Inject - private Event preLoginEvent; - @Inject - private Event postLoginEvent; - @Inject - private Event notAuthorizedEventEvent; - @Inject - private BeanManager beanManager; - @Inject - private Identity identity; - - /** - * Enforce any security annotations applicable to the RestoreView phase - * - * @param event - */ - public void observeRestoreView(@Observes @After @RestoreView PhaseEvent event) { - log.debug("After Restore View event"); - performObservation(event, PhaseIdType.RESTORE_VIEW); - } - - /** - * Enforce any security annotations applicable to the ApplyRequestValues phase - * - * @param event - */ - public void observeApplyRequestValues(@Observes @Before @ApplyRequestValues PhaseEvent event) { - log.debug("After Apply Request Values event"); - performObservation(event, PhaseIdType.APPLY_REQUEST_VALUES); - } - - /** - * Enforce any security annotations applicable to the ProcessValidations phase - * - * @param event - */ - public void observeProcessValidations(@Observes @Before @ProcessValidations PhaseEvent event) { - log.debug("After Process Validations event"); - performObservation(event, PhaseIdType.PROCESS_VALIDATIONS); - } - - /** - * Enforce any security annotations applicable to the UpdateModelValues phase - * - * @param event - */ - public void observeUpdateModelValues(@Observes @Before @UpdateModelValues PhaseEvent event) { - log.debug("After Update Model Values event"); - performObservation(event, PhaseIdType.UPDATE_MODEL_VALUES); - } - - /** - * Enforce any security annotations applicable to the InvokeApplication phase - * - * @param event - */ - public void observeInvokeApplication(@Observes @Before @InvokeApplication PhaseEvent event) { - log.debug("Before Render Response event"); - performObservation(event, PhaseIdType.INVOKE_APPLICATION); - } - - /** - * Enforce any security annotations applicable to the RenderResponse phase - * - * @param event - */ - public void observeRenderResponse(@Observes @Before @RenderResponse PhaseEvent event) { - log.debug("Before Render Response event"); - performObservation(event, PhaseIdType.RENDER_RESPONSE); - } - - /** - * Inspect the annotations in the ViewConfigStore, enforcing any restrictions applicable to this phase - * - * @param event - * @param phaseIdType - */ - private void performObservation(PhaseEvent event, PhaseIdType phaseIdType) { - UIViewRoot viewRoot = (UIViewRoot) event.getFacesContext().getViewRoot(); - List restrictionsForPhase = getRestrictionsForPhase(phaseIdType, viewRoot.getViewId()); - if (restrictionsForPhase != null) { - log.debugf("Enforcing on phase %s", phaseIdType); - enforce(event.getFacesContext(), viewRoot, restrictionsForPhase); - } - } - - /** - * Retrieve all annotations from the ViewConfigStore for a given a JSF phase, and a view id, - * and where the annotation is qualified by @SecurityBindingType - * - * @param currentPhase - * @param viewId - * @return list of restrictions applicable to this viewId and PhaseTypeId - */ - public List getRestrictionsForPhase(PhaseIdType currentPhase, String viewId) { - List allSecurityAnnotations = viewConfigStore.getAllQualifierData(viewId, SecurityBindingType.class); - List applicableSecurityAnnotations = null; - for (Annotation annotation : allSecurityAnnotations) { - PhaseIdType[] defaultPhases = getDefaultPhases(viewId); - if (isAnnotationApplicableToPhase(annotation, currentPhase, defaultPhases)) { - if (applicableSecurityAnnotations == null) { // avoid spawning arrays at all phases of the lifecycle - applicableSecurityAnnotations = new ArrayList(); - } - applicableSecurityAnnotations.add(annotation); - } - } - return applicableSecurityAnnotations; - } - - /** - * Inspect an annotation to see if it specifies a view in which it should be. Fall back on default view otherwise. - * - * @param annotation - * @param currentPhase - * @param defaultPhases - * @return true if the annotation is applicable to this view and phase, false otherwise - */ - public boolean isAnnotationApplicableToPhase(Annotation annotation, PhaseIdType currentPhase, PhaseIdType[] defaultPhases) { - Method restrictAtViewMethod = getRestrictAtViewMethod(annotation); - PhaseIdType[] phasedIds = null; - if (restrictAtViewMethod != null) { - log.warnf("Annotation %s is using the restrictAtViewMethod. Use a @RestrictAtPhase qualifier on the annotation instead."); - phasedIds = getRestrictedPhaseIds(restrictAtViewMethod, annotation); - } - RestrictAtPhase restrictAtPhaseQualifier = AnnotationInspector.getAnnotation(annotation.annotationType(), RestrictAtPhase.class, beanManager); - if (restrictAtPhaseQualifier != null) { - log.debug("Using Phases found in @RestrictAtView qualifier on the annotation."); - phasedIds = restrictAtPhaseQualifier.value(); - } - if (phasedIds == null) { - log.debug("Falling back on default phase ids"); - phasedIds = defaultPhases; - } - if (Arrays.binarySearch(phasedIds, currentPhase) >= 0) { - return true; - } - return false; - } - - /** - * Get the default phases at which restrictions should be applied, by looking for a @RestrictAtPhase on a matching - * - * @param viewId - * @return default phases for a view - * @ViewPattern, falling back on global defaults if none are found - */ - public PhaseIdType[] getDefaultPhases(String viewId) { - PhaseIdType[] defaultPhases = null; - RestrictAtPhase restrictAtPhase = viewConfigStore.getAnnotationData(viewId, RestrictAtPhase.class); - if (restrictAtPhase != null) { - defaultPhases = restrictAtPhase.value(); - } - if (defaultPhases == null) { - defaultPhases = RestrictAtPhaseDefault.DEFAULT_PHASES; - } - return defaultPhases; - } - - /** - * Utility method to extract the "restrictAtPhase" method from an annotation - * - * @param annotation - * @return restrictAtViewMethod if found, null otherwise - */ - public Method getRestrictAtViewMethod(Annotation annotation) { - Method restrictAtViewMethod; - try { - restrictAtViewMethod = annotation.annotationType().getDeclaredMethod("restrictAtPhase"); - } catch (NoSuchMethodException ex) { - restrictAtViewMethod = null; - } catch (SecurityException ex) { - throw new IllegalArgumentException("restrictAtView method must be accessible", ex); - } - return restrictAtViewMethod; - } - - /** - * Retrieve the default PhaseIdTypes defined by the restrictAtViewMethod in the annotation - * - * @param restrictAtViewMethod - * @param annotation - * @return PhaseIdTypes from the restrictAtViewMethod, null if empty - */ - public PhaseIdType[] getRestrictedPhaseIds(Method restrictAtViewMethod, Annotation annotation) { - PhaseIdType[] phaseIds; - try { - phaseIds = (PhaseIdType[]) restrictAtViewMethod.invoke(annotation); - } catch (IllegalAccessException ex) { - throw new IllegalArgumentException("restrictAtView method must be accessible", ex); - } catch (InvocationTargetException ex) { - throw new RuntimeException(ex); - } - return phaseIds; - } - - /** - * Enforce the list of applicable annotations, by firing an AuthorizationCheckEvent. The event is then inspected to - * determine if access is allowed. Faces navigation is then re-routed to the @LoginView if the user is not logged in, - * otherwise to the @AccessDenied view. - * - * @param context - * @param viewRoot - * @param annotations - */ - private void enforce(FacesContext context, UIViewRoot viewRoot, List annotations) { - if (annotations == null || annotations.isEmpty()) { - log.debug("Annotations is null/empty"); - return; - } - AuthorizationCheckEvent event = new AuthorizationCheckEvent(annotations); - authorizationCheckEvent.fire(event); - if (!event.isPassed()) { - if (!identity.isLoggedIn()) { - log.debug("Access denied - not logged in"); - redirectToLoginPage(context, viewRoot); - return; - } else { - log.debug("Access denied - not authorized"); - notAuthorizedEventEvent.fire(new NotAuthorizedEvent()); - redirectToAccessDeniedView(context, viewRoot); - return; - } - } else { - log.debug("Access granted"); - } - } - - /** - * Perform the navigation to the @LoginView. If not @LoginView is defined, return a 401 response. - * The original view id requested by the user is stored in the session map, for use after a successful login. - * - * @param context - * @param viewRoot - */ - private void redirectToLoginPage(FacesContext context, UIViewRoot viewRoot) { - Map sessionMap = context.getExternalContext().getSessionMap(); - preLoginEvent.fire(new PreLoginEvent(context, sessionMap)); - LoginView loginView = viewConfigStore.getAnnotationData(viewRoot.getViewId(), LoginView.class); - if (loginView == null || loginView.value() == null || loginView.value().isEmpty()) { - log.debug("Returning 401 response (login required)"); - context.getExternalContext().setResponseStatus(401); - context.responseComplete(); - return; - } - String loginViewId = loginView.value(); - log.debugf("Redirecting to configured LoginView %s", loginViewId); - NavigationHandler navHandler = context.getApplication().getNavigationHandler(); - navHandler.handleNavigation(context, "", loginViewId); - context.renderResponse(); - } - - /** - * Perform the navigation to the @AccessDeniedView. If not @AccessDeniedView is defined, return a 401 response - * - * @param context - * @param viewRoot - */ - private void redirectToAccessDeniedView(FacesContext context, UIViewRoot viewRoot) { - // If a user has already done a redirect and rendered the response (possibly in an observer) we cannot do this output - if (!(context.getResponseComplete() || context.getRenderResponse())) { - AccessDeniedView accessDeniedView = viewConfigStore.getAnnotationData(viewRoot.getViewId(), AccessDeniedView.class); - if (accessDeniedView == null || accessDeniedView.value() == null || accessDeniedView.value().isEmpty()) { - log.warn("No AccessDeniedView is configured, returning 401 response (access denied). Please configure an AccessDeniedView in the ViewConfig."); - context.getExternalContext().setResponseStatus(401); - context.responseComplete(); - return; - } - String accessDeniedViewId = accessDeniedView.value(); - log.debugf("Redirecting to configured AccessDenied %s", accessDeniedViewId); - NavigationHandler navHandler = context.getApplication().getNavigationHandler(); - navHandler.handleNavigation(context, "", accessDeniedViewId); - context.renderResponse(); - } - } - - /** - * Monitor PreNavigationEvents, looking for a successful navigation from the Seam Security login button. When such a - * navigation is encountered, redirect to the the viewId captured before the login redirect was triggered. - * - * @param event - */ - public void observePreNavigateEvent(@Observes PreNavigateEvent event) { - log.debugf("PreNavigateEvent observed %s, %s", event.getOutcome(), event.getFromAction()); - if (Identity.RESPONSE_LOGIN_SUCCESS.equals(event.getOutcome()) - && "#{identity.login}".equals(event.getFromAction())) { - FacesContext context = event.getContext(); - Map sessionMap = context.getExternalContext().getSessionMap(); - postLoginEvent.fire(new PostLoginEvent(context, sessionMap)); - } - } -} diff --git a/impl/src/main/java/org/jboss/seam/faces/view/action/NonContextual.java b/impl/src/main/java/org/jboss/seam/faces/view/action/NonContextual.java new file mode 100644 index 0000000..8378065 --- /dev/null +++ b/impl/src/main/java/org/jboss/seam/faces/view/action/NonContextual.java @@ -0,0 +1,142 @@ +package org.jboss.seam.faces.view.action; + +import javax.enterprise.context.spi.CreationalContext; +import javax.enterprise.inject.spi.AnnotatedType; +import javax.enterprise.inject.spi.BeanManager; +import javax.enterprise.inject.spi.InjectionTarget; + +/** + * This class handles non-contextual injection for a thirdparty framework. + * + *

        For each non-contextual type you want to provide CDI services (like injection, @PostConstruct, @PreDestroy) to, you need to + * create an instance of this class. You'll need to pass in the BeanManager; assuming you are in a EE environment, the easiest + * way to do this is to look it up in JNDI - java:comp/BeanManager, otherwise you'll have to resort to some non-portable method. + * You then need to ensure to call nonContextual.newInstance() to create an instance. You can then use (the order here is + * recommended) produce(), inject(), postConstruct(), preDestroy(), dispose() to create and cleanup the instance. Call get() to + * get the instance.

        + * + * See also http://seamframework.org/Documentation/HowDoIDoNoncontextualInjectionForAThirdpartyFramework. + * + * @author Adriàn Gonzalez + */ +public class NonContextual { + + // Store the injection target. The CDI spec doesn't require an implementation to cache it, so we do + private final InjectionTarget injectionTarget; + + // Store a reference to the CDI BeanManager + private final BeanManager beanManager; + + /** + * Create an injector for the given class + */ + public NonContextual(BeanManager manager, Class clazz) { + this.beanManager = manager; + + // Generate an "Annotated Type" + AnnotatedType type = manager.createAnnotatedType(clazz); + + // Generate the InjectionTarget + this.injectionTarget = manager.createInjectionTarget(type); + } + + public Instance newInstance() { + return new Instance(beanManager, injectionTarget); + } + + /** + * Represents a non-contextual instance + */ + public static class Instance { + + private final CreationalContext ctx; + private final InjectionTarget injectionTarget; + private T instance; + private boolean disposed = false; + + private Instance(BeanManager beanManager, InjectionTarget injectionTarget) { + this.injectionTarget = injectionTarget; + this.ctx = beanManager.createCreationalContext(null); + } + + /** + * Get the instance + */ + public T get() { + return instance; + } + + /** + * Create the instance + */ + public Instance produce() { + if (this.instance != null) { + throw new IllegalStateException("Trying to call produce() on already constructed instance"); + } + if (disposed) { + throw new IllegalStateException("Trying to call produce() on an already disposed instance"); + } + this.instance = injectionTarget.produce(ctx); + return this; + } + + /** + * Inject the instance + */ + public Instance inject() { + if (this.instance == null) { + throw new IllegalStateException("Trying to call inject() before produce() was called"); + } + if (disposed) { + throw new IllegalStateException("Trying to call inject() on already disposed instance"); + } + injectionTarget.inject(instance, ctx); + return this; + } + + /** + * Call the @PostConstruct callback + */ + public Instance postConstruct() { + if (this.instance == null) { + throw new IllegalStateException("Trying to call postConstruct() before produce() was called"); + } + if (disposed) { + throw new IllegalStateException("Trying to call preDestroy() on already disposed instance"); + } + injectionTarget.postConstruct(instance); + return this; + } + + /** + * Call the @PreDestroy callback + */ + public Instance preDestroy() { + if (this.instance == null) { + throw new IllegalStateException("Trying to call preDestroy() before produce() was called"); + } + if (disposed) { + throw new IllegalStateException("Trying to call preDestroy() on already disposed instance"); + } + injectionTarget.preDestroy(instance); + return this; + } + + /** + * Dispose of the instance, doing any necessary cleanup + */ + public Instance dispose() { + if (this.instance == null) { + throw new IllegalStateException("Trying to call dispose() before produce() was called"); + } + if (disposed) { + throw new IllegalStateException("Trying to call dispose() on already disposed instance"); + } + injectionTarget.dispose(instance); + ctx.release(); + return this; + } + + } + +} diff --git a/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionBindingTypeDescriptor.java b/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionBindingTypeDescriptor.java deleted file mode 100644 index 4e4e4e6..0000000 --- a/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionBindingTypeDescriptor.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.jboss.seam.faces.view.action; - -import java.lang.annotation.Annotation; -import java.util.Arrays; - -import javax.enterprise.inject.spi.AnnotatedMethod; - -import static org.jboss.seam.faces.view.action.PhaseInstant.BEFORE_RENDER_RESPONSE; - -public class ViewActionBindingTypeDescriptor { - private Annotation annotation; - private Object viewControllerValue; - private AnnotatedMethod annotatedMethod; - private PhaseInstant phaseInstant; - - public ViewActionBindingTypeDescriptor(AnnotatedMethod annotatedMethod, Annotation annotation, - Object viewControllerValue) { - this.annotatedMethod = annotatedMethod; - this.annotation = annotation; - this.viewControllerValue = viewControllerValue; - this.phaseInstant = ViewActionUtils.getPhaseInstantOrDefault(Arrays.asList(annotation.annotationType().getAnnotations()), - annotation.annotationType(), BEFORE_RENDER_RESPONSE); - } - - public Annotation getAnnotation() { - return annotation; - } - - public void setAnnotation(Annotation annotation) { - this.annotation = annotation; - } - - public Object getViewControllerValue() { - return viewControllerValue; - } - - public void setViewControllerValue(Object viewControllerValue) { - this.viewControllerValue = viewControllerValue; - } - - public AnnotatedMethod getAnnotatedMethod() { - return annotatedMethod; - } - - public void setAnnotatedMethod(AnnotatedMethod annotatedMethod) { - this.annotatedMethod = annotatedMethod; - } - - public PhaseInstant getPhaseInstant() { - return phaseInstant; - } -} diff --git a/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionExtension.java b/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionExtension.java new file mode 100644 index 0000000..fe7001e --- /dev/null +++ b/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionExtension.java @@ -0,0 +1,89 @@ +package org.jboss.seam.faces.view.action; + +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.List; + +import javax.annotation.PreDestroy; +import javax.enterprise.event.Observes; +import javax.enterprise.inject.spi.AfterDeploymentValidation; +import javax.enterprise.inject.spi.BeanManager; +import javax.enterprise.inject.spi.Extension; + +import org.jboss.seam.faces.view.action.NonContextual.Instance; +import org.jboss.seam.faces.view.config.ViewConfigDescriptor; +import org.jboss.seam.faces.view.config.ViewConfigStore; +import org.jboss.solder.logging.Logger; +import org.jboss.solder.reflection.AnnotationInspector; + +/** + * Retrieves all annotations qualified by ViewAction, and add their corresponding ViewActionHandler as view actions. + * + * @author Adrian Gonzalez + */ +public class ViewActionExtension implements Extension { + private final Logger log = Logger.getLogger(ViewActionExtension.class); + private BeanManager beanManager; + private ViewConfigStore viewConfigStore; + private List>> nonContextualObjects = new ArrayList>>(); + + public void setup(@Observes AfterDeploymentValidation event, javax.enterprise.inject.Instance viewConfigStore, BeanManager beanManager) { + this.beanManager = beanManager; + if (viewConfigStore.isUnsatisfied()) { + // extension disabled : surely because we're in come UT context (i.e. ProjectStageExtensionTest) + // not adding ViewConfigStore in ShrinkWrap archive + log.warn("ViewConfigStore dependency missing - ViewActionExtension disabled"); + return; + } + this.viewConfigStore = viewConfigStore.get(); + registerViewActions(); + } + + @PreDestroy + public void destroy() { + for (Instance> instance : nonContextualObjects) { + instance.preDestroy(); + instance.dispose(); + } + } + + private void registerViewActions() { + List viewConfigDescriptors = viewConfigStore.getViewConfigDescriptors(); + for (ViewConfigDescriptor viewConfigDescriptor : viewConfigDescriptors) { + for (Annotation metaData : viewConfigDescriptor.getMetaData()) { + ViewAction viewAction = AnnotationInspector.getAnnotation(metaData.annotationType(), ViewAction.class, + beanManager); + if (viewAction != null) { + Class> providerClazz = viewAction.value(); + ViewActionHandlerProvider provider = newViewActionProvider(metaData, providerClazz); + for (ViewActionHandler viewActionHandler : provider.getActionHandlers()) { + viewConfigDescriptor.addViewActionHandler(viewActionHandler); + } + } + } + } + } + + /** + * Creates a viewActionProvider which can be injected using CDI annotations. + * + * @param annotation + * @param providerClazz + * @return + */ + @SuppressWarnings("unchecked") + private ViewActionHandlerProvider newViewActionProvider(Annotation annotation, + Class> providerClazz) { + NonContextual> nonContextual = new NonContextual>(beanManager, + (Class>) providerClazz); + Instance> instance = nonContextual.newInstance(); + instance.produce(); + instance.inject(); + instance.postConstruct(); + nonContextualObjects.add(instance); + ViewActionHandlerProvider provider = (ViewActionHandlerProvider) instance.get(); + provider.initialize((E) annotation); + return provider; + } + +} diff --git a/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionPhaseListener.java b/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionPhaseListener.java index 1b0766d..6604289 100644 --- a/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionPhaseListener.java +++ b/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionPhaseListener.java @@ -1,7 +1,5 @@ package org.jboss.seam.faces.view.action; -import java.util.List; - import javax.enterprise.event.Observes; import javax.faces.event.PhaseEvent; import javax.faces.event.PhaseId; @@ -9,6 +7,8 @@ import org.jboss.seam.faces.event.qualifier.After; import org.jboss.seam.faces.event.qualifier.Before; +import org.jboss.seam.faces.view.config.ViewConfigDescriptor; +import org.jboss.seam.faces.view.config.ViewConfigStore; import org.jboss.solder.logging.Logger; /** @@ -23,9 +23,8 @@ public class ViewActionPhaseListener { private transient final Logger log = Logger.getLogger(ViewActionPhaseListener.class); @Inject - private ViewControllerStore viewControllerStore; + private ViewConfigStore viewConfigStore; - // TODO : should be executed after SecurityPhaseListener public void observerBeforePhase(@Observes @Before PhaseEvent event) { PhaseId phaseId = event.getPhaseId(); log.debugf("Before {1} event", phaseId); @@ -33,21 +32,16 @@ public void observerBeforePhase(@Observes @Before PhaseEvent event) { log.debug("viewRoot null, skipping view actions"); return; } - List viewControllers = viewControllerStore.getControllerDescriptors(event.getFacesContext() + ViewConfigDescriptor viewDescriptor = viewConfigStore.getRuntimeViewConfigDescriptor(event.getFacesContext() .getViewRoot().getViewId()); - for (int i = viewControllers.size(); --i >= 0;) { - ViewControllerDescriptor viewControllerDescriptor = viewControllers.get(i); - viewControllerDescriptor.executeBeforePhase(event.getPhaseId()); - } + viewDescriptor.executeBeforePhase(event.getPhaseId()); } public void observerAfterPhase(@Observes @After PhaseEvent event) { PhaseId phaseId = event.getPhaseId(); log.debugf("After {1} event", phaseId); - List viewControllers = viewControllerStore.getControllerDescriptors(event.getFacesContext() + ViewConfigDescriptor viewDescriptor = viewConfigStore.getRuntimeViewConfigDescriptor(event.getFacesContext() .getViewRoot().getViewId()); - for (ViewControllerDescriptor viewControllerDescriptor : viewControllers) { - viewControllerDescriptor.executeAfterPhase(event.getPhaseId()); - } + viewDescriptor.executeAfterPhase(event.getPhaseId()); } } diff --git a/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionStrategy.java b/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionStrategy.java deleted file mode 100644 index 676a956..0000000 --- a/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionStrategy.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.jboss.seam.faces.view.action; - -/** - * Interface encapsulating view action implementation. - * - * The implementation can be : - *
          - *
        • a viewController method call.
        • - *
        • an annotated ViewActionBindingType method call.
        • - *
        • an El contained in a ViewAction annotation.
        • - *
        • ... or any other logic...
        • - *
        - * - * @author Adriàn Gonzalez - */ -public interface ViewActionStrategy { - public Object execute(); -} diff --git a/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionUtils.java b/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionUtils.java deleted file mode 100644 index 938ceb0..0000000 --- a/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionUtils.java +++ /dev/null @@ -1,99 +0,0 @@ -package org.jboss.seam.faces.view.action; - -import java.lang.annotation.Annotation; -import java.util.List; - -import javax.faces.event.PhaseId; - -import org.jboss.seam.faces.event.qualifier.After; -import org.jboss.seam.faces.event.qualifier.ApplyRequestValues; -import org.jboss.seam.faces.event.qualifier.Before; -import org.jboss.seam.faces.event.qualifier.InvokeApplication; -import org.jboss.seam.faces.event.qualifier.ProcessValidations; -import org.jboss.seam.faces.event.qualifier.RenderResponse; -import org.jboss.seam.faces.event.qualifier.UpdateModelValues; - -public class ViewActionUtils { - - // Utility class - no instanciation - private ViewActionUtils() { - } - - public static PhaseInstant getPhaseInstantOrDefault(List annotations, Object parentElement, - PhaseInstant defaultInstant) { - PhaseInstant phaseInstant = getPhaseInstant(annotations, parentElement); - return phaseInstant != null ? phaseInstant : defaultInstant; - } - - public static PhaseInstant getPhaseInstant(List annotations, Object parentElement) { - Boolean before = null; - PhaseId phaseId = null; - for (Annotation annotation : annotations) { - Class annotationType = annotation.annotationType(); - if (annotationType == Before.class) { - if (before != null) { - throw new IllegalStateException("invalid " + parentElement - + ". Cannot be annotated simultaneously with multiples @Before and @After"); - } - before = true; - } else if (annotationType == After.class) { - if (before != null) { - throw new IllegalStateException("invalid " + parentElement - + ". Cannot be annotated simultaneously with multiples @Before and @After"); - } - before = false; - } else if (isPhaseQualifier(annotationType)) { - if (phaseId != null) { - throw new IllegalStateException("invalid " + parentElement - + ". Cannot be annotated simultaneously with multiples JSF lifecycle annotations (" - + annotationType + " and " + phaseId + ")"); - } - phaseId = convert(annotationType); - } - } - if (before == null && phaseId == null) { - return null; - } else if (before != null && phaseId != null) { - return new PhaseInstant(phaseId, before); - } else { - throw new IllegalStateException("invalid " + parentElement - + ". both phaseId and @Before/@After must be specified {phaseId: " + phaseId + ", before: " + before + "}"); - } - } - - /** - * Converts the annotations from package org.jboss.seam.faces.event.qualifier to their corresponding JSF PhaseId. - * - * @throws IllegalArgumentException if annotationType isn't a valid Jsf annotation. - */ - public static PhaseId convert(Class annotationType) { - PhaseId phaseId; - if (annotationType == ApplyRequestValues.class) { - phaseId = PhaseId.APPLY_REQUEST_VALUES; - } else if (annotationType == ProcessValidations.class) { - phaseId = PhaseId.PROCESS_VALIDATIONS; - } else if (annotationType == UpdateModelValues.class) { - phaseId = PhaseId.UPDATE_MODEL_VALUES; - } else if (annotationType == InvokeApplication.class) { - phaseId = PhaseId.INVOKE_APPLICATION; - } else if (annotationType == RenderResponse.class) { - phaseId = PhaseId.RENDER_RESPONSE; - } else { - throw new IllegalArgumentException("Annotation " + annotationType + " doesn't correspond to valid a Jsf phase."); - } - return phaseId; - } - - /** - * Returns true if annotationType is a valid JSF annotation - */ - public static boolean isPhaseQualifier(Class annotationType) { - if (annotationType == ApplyRequestValues.class || annotationType == ProcessValidations.class - || annotationType == UpdateModelValues.class || annotationType == InvokeApplication.class - || annotationType == RenderResponse.class) { - return true; - } else { - return false; - } - } -} diff --git a/impl/src/main/java/org/jboss/seam/faces/view/action/ViewControllerDescriptor.java b/impl/src/main/java/org/jboss/seam/faces/view/action/ViewControllerDescriptor.java deleted file mode 100644 index d63c83f..0000000 --- a/impl/src/main/java/org/jboss/seam/faces/view/action/ViewControllerDescriptor.java +++ /dev/null @@ -1,267 +0,0 @@ -package org.jboss.seam.faces.view.action; - -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.el.MethodExpression; -import javax.enterprise.context.spi.CreationalContext; -import javax.enterprise.inject.spi.AnnotatedMethod; -import javax.enterprise.inject.spi.AnnotatedType; -import javax.enterprise.inject.spi.Bean; -import javax.enterprise.inject.spi.BeanManager; -import javax.faces.context.FacesContext; -import javax.faces.event.PhaseId; - -import org.jboss.seam.faces.view.config.ViewConfig; -import org.jboss.solder.logging.Logger; -import org.jboss.solder.reflection.annotated.InjectableMethod; - -/** - * Information about a particular controller. - * - * A Controller is managed bean which is associated to a given view (a given {@link ViewConfig}). - * - * @author Adriàn Gonzalez - */ -public class ViewControllerDescriptor { - - private transient final Logger log = Logger.getLogger(ViewControllerDescriptor.class); - - private String viewId; - private Class viewControllerClass; - private BeanManager beanManager; - private Map> phaseMethods = new HashMap>(); - - /** - * Creates descriptor. - * - * Lifecycle callback registration is up to the caller. - * - * Note : beanManager parameter is horrible, should be a way to send beanManager more elegantly - */ - public ViewControllerDescriptor(String viewId, BeanManager beanManager) { - this.viewId = viewId; - this.beanManager = beanManager; - log.debugf("Created viewController #0", this); - } - - /** - * Creates descriptor by reading controllerViewClass methods. - * - * Register controllerViewClass lifecycle callback methods. - * - * Note : beanManager parameter is horrible, should be a way to send beanManager more elegantly - */ - public ViewControllerDescriptor(String viewId, Class viewControllerClass, BeanManager beanManager) { - this.viewId = viewId; - this.viewControllerClass = viewControllerClass; - this.beanManager = beanManager; - registerCallbacks(); - log.debugf("Created viewController #0", this); - } - - /** - * Register any lifecycle methods declared in this viewController class (or inherited). - */ - private void registerCallbacks() { - Class current = viewControllerClass; - while (current != Object.class) { - for (Method method : current.getDeclaredMethods()) { - // if (method.isAnnotationPresent(BeforeRenderView.class)) { - // beforeRenderViewMethods.add(new MethodInvoker(method, beanManager)); - // } - // if (method.isAnnotationPresent(AfterRenderView.class)) { - // afterRenderViewMethods.add(new MethodInvoker(method, beanManager)); - // } - PhaseInstant phaseInstant = ViewActionUtils.getPhaseInstant(Arrays.asList(method.getAnnotations()), method); - if (phaseInstant != null) { - addMethod(phaseInstant, new MethodInvoker(method, beanManager)); - } - } - current = current.getSuperclass(); - } - } - - public void executeBeforePhase(PhaseId phaseId) { - List actions = phaseMethods.get(new PhaseInstant(phaseId, true)); - if (actions != null) { - for (ViewActionStrategy action : actions) { - action.execute(); - } - } - } - - public void executeAfterPhase(PhaseId phaseId) { - List actions = phaseMethods.get(new PhaseInstant(phaseId, false)); - if (actions != null) { - for (ViewActionStrategy action : actions) { - action.execute(); - } - } - } - - public void addMethod(PhaseInstant phaseInstant, ViewActionStrategy method) { - List methods = phaseMethods.get(phaseInstant); - if (methods == null) { - methods = new ArrayList(); - phaseMethods.put(phaseInstant, methods); - } - methods.add(method); - } - - public Map> getPhaseMethods() { - return phaseMethods; - } - - public void setPhaseMethods(Map> phaseMethods) { - this.phaseMethods = phaseMethods; - } - - public String getViewId() { - return viewId; - } - - public void setViewId(String viewId) { - this.viewId = viewId; - } - - public Class getViewControllerClass() { - return viewControllerClass; - } - - public void setViewControllerClass(Class viewControllerClass) { - this.viewControllerClass = viewControllerClass; - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(super.toString()); - builder.append("{viewId: ").append(getViewId()).append(", viewControllerClass: ").append(getViewControllerClass()) - .append("}, phaseMethods: {").append(getPhaseMethods()).append("}}"); - return builder.toString(); - } - - /** - * Invokes method on a CDI bean. - * - * Note : copy from Seam Security's SecurityExtension class. Should be extracted into common utility. - */ - public static class AnnotatedMethodInvoker implements ViewActionStrategy { - private Bean targetBean; - private BeanManager beanManager; - private InjectableMethod injectableMethod; - private AnnotatedMethod annotatedMethod; - - public AnnotatedMethodInvoker(AnnotatedMethod annotatedMethod, BeanManager beanManager) { - this.beanManager = beanManager; - this.annotatedMethod = annotatedMethod; - } - - public AnnotatedMethod getAnnotatedMethod() { - return annotatedMethod; - } - - public void setAnnotatedMethod(AnnotatedMethod annotatedMethod) { - this.annotatedMethod = annotatedMethod; - } - - public BeanManager getBeanManager() { - return beanManager; - } - - public Object execute() { - if (targetBean == null) { - lookupTargetBean(); - } - CreationalContext cc = beanManager.createCreationalContext(targetBean); - Object reference = beanManager.getReference(targetBean, getAnnotatedMethod().getJavaMember().getDeclaringClass(), - cc); - return injectableMethod.invoke(reference, cc, null); - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - private synchronized void lookupTargetBean() { - if (targetBean == null) { - AnnotatedMethod annotatedMethod = getAnnotatedMethod(); - Method method = annotatedMethod.getJavaMember(); - Set> beans = beanManager.getBeans(method.getDeclaringClass()); - if (beans.size() == 1) { - targetBean = beans.iterator().next(); - } else if (beans.isEmpty()) { - throw new IllegalStateException("Exception looking up method bean - " + "no beans found for method [" - + method.getDeclaringClass() + "." + method.getName() + "]"); - } else if (beans.size() > 1) { - throw new IllegalStateException("Exception looking up method bean - " + "multiple beans found for method [" - + method.getDeclaringClass().getName() + "." + method.getName() + "]"); - } - injectableMethod = new InjectableMethod(annotatedMethod, targetBean, beanManager); - } - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(this.getClass().getSimpleName()); - builder.append("{method: ").append(getAnnotatedMethod()).append("}"); - return builder.toString(); - } - } - - /** - * Invokes method on a CDI bean. - * - * Note : copy from Seam Security's SecurityExtension class. Should be extracted into common utility. - */ - public static class MethodInvoker extends AnnotatedMethodInvoker { - - public MethodInvoker(Method method, BeanManager beanManager) { - super(null, beanManager); - setAnnotatedMethod(convert(method)); - - } - - private AnnotatedMethod convert(Method method) { - AnnotatedType annotatedType = getBeanManager().createAnnotatedType(method.getDeclaringClass()); - AnnotatedMethod annotatedMethod = null; - for (AnnotatedMethod current : annotatedType.getMethods()) { - if (current.getJavaMember().equals(method)) { - annotatedMethod = current; - } - } - if (annotatedMethod == null) { - throw new IllegalStateException("No matching annotated method found for method : " + method); - } - return annotatedMethod; - } - } - - /** - * Invokes a method expression. - */ - public static class MethodExpressionInvoker implements ViewActionStrategy { - private MethodExpression methodExpression; - private String methodExpressionAsString; - - public MethodExpressionInvoker(String methodExpressionAsString) { - this.methodExpressionAsString = methodExpressionAsString; - } - - public String getMethodExpressionString() { - return methodExpressionAsString; - } - - @Override - public Object execute() { - FacesContext facesContext = FacesContext.getCurrentInstance(); - if (methodExpression == null) { - methodExpression = facesContext.getApplication().getExpressionFactory() - .createMethodExpression(facesContext.getELContext(), methodExpressionAsString, null, new Class[] {}); - } - return methodExpression.invoke(FacesContext.getCurrentInstance().getELContext(), null); - } - } -} diff --git a/impl/src/main/java/org/jboss/seam/faces/view/action/ViewControllerExtension.java b/impl/src/main/java/org/jboss/seam/faces/view/action/ViewControllerExtension.java deleted file mode 100644 index 6eedc5e..0000000 --- a/impl/src/main/java/org/jboss/seam/faces/view/action/ViewControllerExtension.java +++ /dev/null @@ -1,80 +0,0 @@ -package org.jboss.seam.faces.view.action; - -import java.lang.annotation.Annotation; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.enterprise.event.Observes; -import javax.enterprise.inject.spi.AnnotatedMethod; -import javax.enterprise.inject.spi.AnnotatedType; -import javax.enterprise.inject.spi.Extension; -import javax.enterprise.inject.spi.ProcessAnnotatedType; - -/** - * Scans for viewController classes and view actions. - * - * @author Adriàn Gonzalez - */ -public class ViewControllerExtension implements Extension { - - private final Map> descriptors = new HashMap>(); - - public void processAnnotatedType(@Observes ProcessAnnotatedType event) { - AnnotatedType tp = event.getAnnotatedType(); - for (final AnnotatedMethod m : tp.getMethods()) { - for (final Annotation annotation : m.getAnnotations()) { - if (annotation.annotationType().isAnnotationPresent(ViewActionBindingType.class)) { - Object viewConfigValue = getValue(annotation); - if (viewConfigValue == null) { - throw new IllegalArgumentException("Annotation " + annotation - + " invalid : no view specified"); - } - List actions = descriptors.get(viewConfigValue); - if (actions == null) { - actions = new ArrayList(); - descriptors.put(viewConfigValue, actions); - } - ViewActionBindingTypeDescriptor descriptor = new ViewActionBindingTypeDescriptor(m, annotation, viewConfigValue); - actions.add(descriptor); - } - } - } - } - - public Map> getViewActionBindingTypeDescriptors() { - return descriptors; - } - - - // Utility methods for viewAction, TODO move this block out of this class - - /** - * Retrieve the view defined by the value() method in the annotation - * - * @param annotation - * @return the result of value() call - * @throws IllegalArgumentException if no value() method was found - */ - private Object getValue(Annotation annotation) { - Method valueMethod; - try { - valueMethod = annotation.annotationType().getDeclaredMethod("value"); - } catch (NoSuchMethodException ex) { - throw new IllegalArgumentException("value method must be declared and must resolve to a valid view", ex); - } catch (SecurityException ex) { - throw new IllegalArgumentException("value method must be accessible", ex); - } - try { - return valueMethod.invoke(annotation); - } catch (IllegalAccessException ex) { - throw new IllegalArgumentException("value method must be accessible", ex); - } catch (InvocationTargetException ex) { - throw new RuntimeException(ex); - } - } - -} diff --git a/impl/src/main/java/org/jboss/seam/faces/view/action/ViewControllerStore.java b/impl/src/main/java/org/jboss/seam/faces/view/action/ViewControllerStore.java deleted file mode 100644 index becc122..0000000 --- a/impl/src/main/java/org/jboss/seam/faces/view/action/ViewControllerStore.java +++ /dev/null @@ -1,166 +0,0 @@ -package org.jboss.seam.faces.view.action; - -import java.lang.annotation.Annotation; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -import javax.enterprise.inject.spi.BeanManager; -import javax.inject.Inject; - -import org.jboss.seam.faces.view.config.ViewConfigDescriptor; -import org.jboss.seam.faces.view.config.ViewConfigStore; - -/** - * Data store for view controllers. - * - * @author Adriàn gonzalez - */ -public class ViewControllerStore { - /** map containing view pattern / controller */ - private Map> viewPatternControllerDescriptors = new HashMap>(); - private ConcurrentHashMap> viewControllerDescriptorsCache = new ConcurrentHashMap>(); - - /** - * Initialization : Retrieves any ViewControllers associated to ViewConfig objects - */ - @Inject - public void setup(ViewControllerExtension viewControllerExtension, ViewConfigStore viewConfigStore, BeanManager beanManager) { - registerViewControllers(viewConfigStore, beanManager); - registerViewActions(viewConfigStore, beanManager); - registerViewActionBindingTypes(viewControllerExtension, viewConfigStore, beanManager); - } - - private void registerViewControllers(ViewConfigStore viewConfigStore, BeanManager beanManager) { - Map views = viewConfigStore.getAllAnnotationViewMap(ViewController.class); - for (Map.Entry entry : views.entrySet()) { - ViewController annotation = (ViewController) entry.getValue(); - if (annotation.value() == null) { - throw new IllegalArgumentException("Invalid ViewConfig for view '" + entry.getKey() - + "' : @ViewController must have a non null value."); - } - for (Class viewControllerClass : annotation.value()) { - ViewControllerDescriptor viewControllerDescriptor = createViewControllerDescriptor(entry.getKey(), - viewControllerClass, beanManager); - addControllerDescriptor(viewControllerDescriptor); - } - } - } - - private void registerViewActionBindingTypes(ViewControllerExtension viewControllerExtension, - ViewConfigStore viewConfigStore, BeanManager beanManager) { - List viewConfigDescriptors = viewConfigStore.getAllViewConfigDescriptors(); - for (ViewConfigDescriptor viewConfigDescriptor : viewConfigDescriptors) { - List viewActionBindingTypes = new ArrayList(); - for (Object value : viewConfigDescriptor.getValues()) { - List current = viewControllerExtension.getViewActionBindingTypeDescriptors() - .get(value); - if (current != null) { - viewActionBindingTypes.addAll(current); - } - } - if (viewActionBindingTypes.size() > 0) { - ViewControllerDescriptor viewControllerDescriptor = new ViewControllerDescriptor( - viewConfigDescriptor.getViewId(), beanManager); - for (ViewActionBindingTypeDescriptor viewActionBindingTypeDescriptor : viewActionBindingTypes) { - viewControllerDescriptor.addMethod( - viewActionBindingTypeDescriptor.getPhaseInstant(), - new ViewControllerDescriptor.AnnotatedMethodInvoker(viewActionBindingTypeDescriptor - .getAnnotatedMethod(), beanManager)); - } - addControllerDescriptor(viewControllerDescriptor); - } - } - } - - private void registerViewActions(ViewConfigStore viewConfigStore, BeanManager beanManager) { - List viewConfigDescriptors = viewConfigStore.getAllViewConfigDescriptors(); - for (ViewConfigDescriptor viewConfigDescriptor : viewConfigDescriptors) { - ViewAction viewAction = viewConfigDescriptor.getMetaData(ViewAction.class); - if (viewAction != null) { - ViewControllerDescriptor viewControllerDescriptor = new ViewControllerDescriptor( - viewConfigDescriptor.getViewId(), beanManager); - PhaseInstant phaseInstant = new PhaseInstant(ViewActionUtils.convert(viewAction.phase()), viewAction.before()); - viewControllerDescriptor.addMethod(phaseInstant, new ViewControllerDescriptor.MethodExpressionInvoker( - viewAction.value())); - addControllerDescriptor(viewControllerDescriptor); - } - } - } - - private ViewControllerDescriptor createViewControllerDescriptor(String viewId, Class controllerViewClass, - BeanManager beanManager) { - return new ViewControllerDescriptor(viewId, controllerViewClass, beanManager); - } - - public void addControllerDescriptor(ViewControllerDescriptor controllerDescriptor) { - List descriptors = viewPatternControllerDescriptors.get(controllerDescriptor.getViewId()); - if (descriptors == null) { - descriptors = new ArrayList(); - } - descriptors.add(controllerDescriptor); - viewPatternControllerDescriptors.put(controllerDescriptor.getViewId(), descriptors); - } - - /** - * Returns contollers matching a viewId. - * - * Controllers are ordered from best matching viewId (longest) to least matching one. - */ - public List getControllerDescriptors(String viewId) { - List controllers = viewControllerDescriptorsCache.get(viewId); - if (controllers == null) { - controllers = new ArrayList(); - List viewPatterns = findViewsWithPatternsThatMatch(viewId, viewPatternControllerDescriptors.keySet()); - for (String viewPattern : viewPatterns) { - List viewPatternControllers = viewPatternControllerDescriptors.get(viewPattern); - controllers.addAll(viewPatternControllers); - } - viewControllerDescriptorsCache.putIfAbsent(viewId, controllers); - } - return controllers; - } - - // Copied from ViewConfigStoreImpl : extract into utility method // - - private List findViewsWithPatternsThatMatch(String viewId, Set viewPatterns) { - List resultingViews = new ArrayList(); - for (String viewPattern : viewPatterns) { - if (viewPattern.endsWith("*")) { - String cutView = viewPattern.substring(0, viewPattern.length() - 1); - if (viewId.startsWith(cutView)) { - resultingViews.add(viewPattern); - } - } else { - if (viewPattern.equals(viewId)) { - resultingViews.add(viewPattern); - } - } - } - // sort the keys by length, longest is the most specific and so should go first - Collections.sort(resultingViews, StringLengthComparator.INSTANCE); - return resultingViews; - } - - private static class StringLengthComparator implements Comparator { - - @Override - public int compare(String o1, String o2) { - if (o1.length() > o2.length()) { - return -1; - } - if (o1.length() < o2.length()) { - return 1; - } - return 0; - } - - public static final StringLengthComparator INSTANCE = new StringLengthComparator(); - - } -} diff --git a/impl/src/main/java/org/jboss/seam/faces/view/action/binding/ViewActionBindingTypeExtension.java b/impl/src/main/java/org/jboss/seam/faces/view/action/binding/ViewActionBindingTypeExtension.java new file mode 100644 index 0000000..b8db828 --- /dev/null +++ b/impl/src/main/java/org/jboss/seam/faces/view/action/binding/ViewActionBindingTypeExtension.java @@ -0,0 +1,116 @@ +package org.jboss.seam.faces.view.action.binding; + +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.enterprise.event.Observes; +import javax.enterprise.inject.Instance; +import javax.enterprise.inject.spi.AfterDeploymentValidation; +import javax.enterprise.inject.spi.AnnotatedMethod; +import javax.enterprise.inject.spi.AnnotatedType; +import javax.enterprise.inject.spi.BeanManager; +import javax.enterprise.inject.spi.Extension; +import javax.enterprise.inject.spi.ProcessAnnotatedType; + +import org.jboss.seam.faces.security.RestrictViewActionExtension; +import org.jboss.seam.faces.view.action.ViewActionHandler; +import org.jboss.seam.faces.view.config.ViewConfigDescriptor; +import org.jboss.seam.faces.view.config.ViewConfigStore; +import org.jboss.solder.logging.Logger; + +/** + * Scans for viewController classes and view actions. + * + * @author Adriàn Gonzalez + */ +public class ViewActionBindingTypeExtension implements Extension { + private final Logger log = Logger.getLogger(RestrictViewActionExtension.class); + private final Map> viewActionHandlers = new HashMap>(); + + public void setup(@Observes AfterDeploymentValidation event, Instance viewConfigStore, BeanManager beanManager) { + if (viewConfigStore.isUnsatisfied()) { + // extension disable : surely because we're in come UT context (i.e. ProjectStageExtensionTest) + // not adding ViewConfigStore in ShrinkWrap archive + log.warn("ViewConfigStore dependency missing - RestrictViewActionExtension disabled"); + return; + } + registerViewActionBindingTypes(viewConfigStore.get(), beanManager); + } + + private void registerViewActionBindingTypes(ViewConfigStore viewConfigStore, BeanManager beanManager) { + List viewConfigDescriptors = viewConfigStore.getViewConfigDescriptors(); + for (ViewConfigDescriptor viewConfigDescriptor : viewConfigDescriptors) { + for (Object value : viewConfigDescriptor.getValues()) { + if (viewActionHandlers.containsKey(value)) { + for (ViewActionHandler viewActionHandler : viewActionHandlers.get(value)) { + viewConfigDescriptor.addViewActionHandler(viewActionHandler); + } + } + } + } + } + + public void processAnnotatedType(@Observes ProcessAnnotatedType event, BeanManager beanManager) { + AnnotatedType tp = event.getAnnotatedType(); + for (final AnnotatedMethod m : tp.getMethods()) { + for (final Annotation annotation : m.getAnnotations()) { + if (annotation.annotationType().isAnnotationPresent(ViewActionBindingType.class)) { + ViewActionHandler viewActionHandler = newViewActionHandler(m, annotation, beanManager); + Object viewConfigValue = getValue(annotation); + if (viewConfigValue == null) { + throw new IllegalArgumentException("Annotation " + annotation + " invalid : no view specified"); + } + registerViewActionHandler(viewConfigValue, viewActionHandler); + } + } + } + } + + protected ViewActionHandler newViewActionHandler(AnnotatedMethod m, Annotation annotation, BeanManager beanManager) { + return new ViewActionBindingTypeHandler(m, annotation, beanManager); + } + + protected void registerViewActionHandler(Object viewConfigValue, ViewActionHandler viewActionHandler) { + List actions = viewActionHandlers.get(viewConfigValue); + if (actions == null) { + actions = new ArrayList(); + viewActionHandlers.put(viewConfigValue, actions); + } + actions.add(viewActionHandler); + } + + protected Map> getViewActionHandlers() { + return viewActionHandlers; + } + + /** + * Retrieve the view defined by the value() method in the annotation + * + * @param annotation + * @return the result of value() call + * @throws IllegalArgumentException if no value() method was found + */ + private Object getValue(Annotation annotation) { + Method valueMethod; + try { + valueMethod = annotation.annotationType().getDeclaredMethod("value"); + } catch (NoSuchMethodException ex) { + throw new IllegalArgumentException("value method must be declared and must resolve to a valid view", ex); + } catch (SecurityException ex) { + throw new IllegalArgumentException("value method must be accessible", ex); + } + try { + return valueMethod.invoke(annotation); + } catch (IllegalAccessException ex) { + throw new IllegalArgumentException("value method must be accessible", ex); + } catch (InvocationTargetException ex) { + throw new RuntimeException(ex); + } + } + +} diff --git a/impl/src/main/java/org/jboss/seam/faces/view/action/binding/ViewActionBindingTypeHandler.java b/impl/src/main/java/org/jboss/seam/faces/view/action/binding/ViewActionBindingTypeHandler.java new file mode 100644 index 0000000..2136aad --- /dev/null +++ b/impl/src/main/java/org/jboss/seam/faces/view/action/binding/ViewActionBindingTypeHandler.java @@ -0,0 +1,36 @@ +package org.jboss.seam.faces.view.action.binding; + +import static org.jboss.seam.faces.view.action.PhaseInstant.BEFORE_RENDER_RESPONSE; + +import java.lang.annotation.Annotation; +import java.util.Arrays; + +import javax.enterprise.inject.spi.AnnotatedMethod; +import javax.enterprise.inject.spi.BeanManager; + +import org.jboss.seam.faces.view.action.AnnotatedMethodViewActionHandler; +import org.jboss.seam.faces.view.action.PhaseInstant; +import org.jboss.seam.faces.view.action.ViewActionHandlerUtils; + +public class ViewActionBindingTypeHandler extends AnnotatedMethodViewActionHandler { + private PhaseInstant phaseInstant; + private Integer order; + + public ViewActionBindingTypeHandler(AnnotatedMethod annotatedMethod, Annotation annotation, BeanManager beanManager) { + super(annotatedMethod, beanManager); + order = ViewActionHandlerUtils.getOrder(annotation); + this.phaseInstant = ViewActionHandlerUtils.getPhaseInstantOrDefault( + Arrays.asList(annotation.annotationType().getAnnotations()), annotation.annotationType(), + BEFORE_RENDER_RESPONSE); + } + + @Override + public boolean handles(PhaseInstant phaseInstant) { + return this.phaseInstant.equals(phaseInstant); + } + + @Override + public Integer getOrder() { + return order; + } +} diff --git a/impl/src/main/java/org/jboss/seam/faces/view/config/ViewConfigStoreImpl.java b/impl/src/main/java/org/jboss/seam/faces/view/config/ViewConfigStoreImpl.java index 84f89af..f04daa9 100644 --- a/impl/src/main/java/org/jboss/seam/faces/view/config/ViewConfigStoreImpl.java +++ b/impl/src/main/java/org/jboss/seam/faces/view/config/ViewConfigStoreImpl.java @@ -28,25 +28,27 @@ import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; -import org.jboss.seam.faces.security.SecurityPhaseListener; +import org.jboss.seam.faces.view.action.ViewActionHandler; import org.jboss.solder.logging.Logger; /** * Data store for view specific data. - * + * * @author Stuart Douglas * @author Brian Leathem */ @ApplicationScoped public class ViewConfigStoreImpl implements ViewConfigStore { - private transient final Logger log = Logger.getLogger(SecurityPhaseListener.class); + private transient final Logger log = Logger.getLogger(ViewConfigStoreImpl.class); /** * cache of viewId to a given data list */ private final ConcurrentHashMap, ConcurrentHashMap>> annotationCache = new ConcurrentHashMap, ConcurrentHashMap>>(); private final ConcurrentHashMap, ConcurrentHashMap>> qualifierCache = new ConcurrentHashMap, ConcurrentHashMap>>(); - private Map viewConfigDescriptors = new ConcurrentHashMap(); + private Map viewConfigDescriptors = new ConcurrentHashMap(); + private Map runtimeViewConfigDescriptor = new ConcurrentHashMap(); + private final ConcurrentHashMap, ConcurrentHashMap> viewPatternDataByAnnotation = new ConcurrentHashMap, ConcurrentHashMap>(); private final ConcurrentHashMap, ConcurrentHashMap>> viewPatternDataByQualifier = new ConcurrentHashMap, ConcurrentHashMap>>(); @@ -68,6 +70,7 @@ public void setup(ViewConfigExtension extension) { @Override public synchronized void addAnnotationData(String viewId, Annotation annotation) { + runtimeViewConfigDescriptor.clear(); ConcurrentHashMap annotationMap = viewPatternDataByAnnotation.get(annotation.annotationType()); if (annotationMap == null) { annotationMap = new ConcurrentHashMap(); @@ -96,7 +99,8 @@ public synchronized void addAnnotationData(String viewId, Annotation annotation) qualifiedAnnotations.addAll(exisitngQualifiedAnnotations); } qualifiedAnnotations.add(annotation); - log.debugf("Adding new annotation (type: %s) for viewId: %s and Qualifier %s", annotation.annotationType().getName(), viewId, qualifier.annotationType().getName()); + log.debugf("Adding new annotation (type: %s) for viewId: %s and Qualifier %s", annotation.annotationType() + .getName(), viewId, qualifier.annotationType().getName()); qualifierMap.put(viewId, qualifiedAnnotations); } } @@ -134,13 +138,49 @@ public List getAllQualifierData(String viewId, Class getAllViewConfigDescriptors() { + public List getViewConfigDescriptors() { return Collections.unmodifiableList(new ArrayList(viewConfigDescriptors.values())); } + @Override + public ViewConfigDescriptor getViewConfigDescriptor(String viewId) { + return viewConfigDescriptors.get(viewId); + } + + @Override + public ViewConfigDescriptor getRuntimeViewConfigDescriptor(String viewId) { + ViewConfigDescriptor viewConfigDescriptor = runtimeViewConfigDescriptor.get(viewId); + if (viewConfigDescriptor == null) { + List resultingViews = findViewsWithPatternsThatMatch(viewId, viewConfigDescriptors.keySet()); + List resultingDescriptors = new ArrayList(); + for (String currentViewId : resultingViews) { + resultingDescriptors.add(getViewConfigDescriptor(currentViewId)); + } + viewConfigDescriptor = merge(viewId, resultingDescriptors); + runtimeViewConfigDescriptor.put(viewId, viewConfigDescriptor); + } + return viewConfigDescriptor; + } + + private ViewConfigDescriptor merge(String viewId, List descriptors) { + ViewConfigDescriptor result = new ViewConfigDescriptor(viewId, null); + for (ViewConfigDescriptor descriptor : descriptors) { + for (Object value : descriptor.getValues()) { + result.addValue(value); + } + for (Annotation metaData : descriptor.getMetaData()) { + result.addMetaData(metaData); + } + for (ViewActionHandler viewActionHandler : descriptor.getViewActionHandlers()) { + result.addViewActionHandler(viewActionHandler); + } + } + return result; + } + private List prepareAnnotationCache(String viewId, Class annotationType, - ConcurrentHashMap, ConcurrentHashMap>> cache, - ConcurrentHashMap, ConcurrentHashMap> viewPatternData) { + ConcurrentHashMap, ConcurrentHashMap>> cache, + ConcurrentHashMap, ConcurrentHashMap> viewPatternData) { // we need to synchronize to make sure that no threads see a half // completed list due to instruction re-ordering ConcurrentHashMap> map = cache.get(annotationType); diff --git a/impl/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension b/impl/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension index b6c1bcb..bed6235 100644 --- a/impl/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension +++ b/impl/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension @@ -4,6 +4,8 @@ org.jboss.seam.faces.context.ViewScopedExtension org.jboss.seam.faces.context.RenderScopedExtension org.jboss.seam.faces.context.FacesAnnotationsAdapterExtension org.jboss.seam.faces.view.config.ViewConfigExtension -org.jboss.seam.faces.view.action.ViewControllerExtension +org.jboss.seam.faces.security.RestrictViewActionExtension +org.jboss.seam.faces.view.action.binding.ViewActionBindingTypeExtension +org.jboss.seam.faces.view.action.ViewActionExtension org.jboss.seam.faces.projectstage.ProjectStageExtension diff --git a/testsuite/pom.xml b/testsuite/pom.xml index 989b78b..c55e833 100644 --- a/testsuite/pom.xml +++ b/testsuite/pom.xml @@ -1,302 +1,375 @@ - - - 4.0.0 - - - org.jboss.seam.faces - seam-faces-parent - ../pom.xml - 3.1.0-SNAPSHOT - - - 3.1.0-SNAPSHOT - seam-faces-testsuite - - Seam Faces Test Suite - jar - - - - org.jboss.arquillian.junit - arquillian-junit-container - - - - org.jboss.shrinkwrap.resolver - shrinkwrap-resolver-api-maven - - - - org.jboss.shrinkwrap.resolver - shrinkwrap-resolver-impl-maven - - - - org.jboss.seam.faces - seam-faces-api - - - - org.jboss.seam.faces - seam-faces - compile - - - - org.jboss.seam.security - seam-security-api - - - - org.jboss.seam.security - seam-security - - - - org.jboss.seam.international - seam-international - compile - - - - org.jboss.test-jsf - jsf-mock - compile - - - - com.ocpsoft - prettyfaces-jsf2 - compile - - - - org.jboss.spec - jboss-javaee-6.0 - pom - provided - - - - org.jboss.solder - solder-api - - - - org.jboss.solder - solder-impl - - - - org.jboss.solder - solder-tooling - - - - org.jboss.weld - weld-core - provided - - - - junit - junit - - - - - - - org.apache.maven.plugins - maven-surefire-plugin - - true - - ${arquillian} - ${arquillian} - - - - - - surefire-it - integration-test - - test - - - false - true - false - true - - - - - - - - - - weld-ee-embedded-1.1 - - true - - arquillian - weld-ee-embedded-1.1 - - - - - - org.jboss.seam.test - weld-ee-embedded-1.1 - pom - test - - - - - org.glassfish.web - el-impl - test - - - - - - - - org.apache.maven.plugins - maven-surefire-plugin - - - weld-ee-embedded-1.1 - weld-ee-embedded-1.1 - - - **/conversation/** - - - - - - - - - jbossas-managed-7 - - - arquillian - jbossas-managed-7 - - - - - - org.jboss.seam.test - jbossas-managed-7 - pom - test - - - - - - - org.apache.maven.plugins - maven-surefire-plugin - - - **/weld/** - - - - - - - - - glassfish-embedded-3.1 - - - arquillian - glassfish-embedded-3.1 - - - - - - org.jboss.seam.test - glassfish-embedded-3.1 - pom - test - - - - - - - org.apache.maven.plugins - maven-surefire-plugin - - - **/weld/** - - - - - - - - - glassfish-remote-3.1 - - - arquillian - glassfish-remote-3.1 - - - - - - org.jboss.seam.test - glassfish-remote-3.1 - pom - test - - - - - - - org.apache.maven.plugins - maven-surefire-plugin - - - **/weld/** - - - - - - - + + + 4.0.0 + + + org.jboss.seam.faces + seam-faces-parent + ../pom.xml + 3.1.0-SNAPSHOT + + + 3.1.0-SNAPSHOT + seam-faces-testsuite + + Seam Faces Test Suite + jar + + + + org.jboss.arquillian.junit + arquillian-junit-container + + + + org.jboss.shrinkwrap.resolver + shrinkwrap-resolver-api-maven + test + 1.0.0-beta-5 + + + + org.jboss.shrinkwrap.resolver + shrinkwrap-resolver-impl-maven + test + 1.0.0-beta-5 + + + + org.jboss.seam.faces + seam-faces-api + + + + org.jboss.seam.faces + seam-faces + compile + + + + org.jboss.seam.security + seam-security-api + + + + org.jboss.seam.security + seam-security + + + + org.jboss.seam.international + seam-international + compile + + + + org.jboss.test-jsf + jsf-mock + compile + + + + org.mockito + mockito-all + test + + + + com.ocpsoft + prettyfaces-jsf2 + compile + + + + org.jboss.spec + jboss-javaee-6.0 + pom + provided + + + + org.jboss.solder + solder-api + + + + org.jboss.solder + solder-impl + + + + org.jboss.solder + solder-tooling + + + + org.jboss.weld + weld-core + provided + + + + junit + junit + + + + + + + + + + org.eclipse.m2e + lifecycle-mapping + 1.0.0 + + + + + + org.apache.maven.plugins + maven-dependency-plugin + [1.0,) + + copy + + + + + false + + + + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + ${arquillian} + ${arquillian} + + + + + + surefire-it + integration-test + + test + + + false + true + false + true + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-test-libs + process-test-resources + + + + org.jboss.solder + solder-api + + + org.jboss.solder + solder-impl + + + org.jboss.solder + solder-tooling + + + + ${project.build.directory}/test-libs + + true + + + copy + + + + + + + + + + + weld-ee-embedded-1.1 + + true + + arquillian + weld-ee-embedded-1.1 + + + + + + org.jboss.seam.test + weld-ee-embedded-1.1 + pom + test + + + + + org.glassfish.web + el-impl + test + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + weld-ee-embedded-1.1 + weld-ee-embedded-1.1 + + + **/conversation/** + + + + + + + + + jbossas-managed-7 + + + arquillian + jbossas-managed-7 + + + + + + org.jboss.seam.test + jbossas-managed-7 + pom + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + **/weld/** + + + + + + + + + glassfish-embedded-3.1 + + + arquillian + glassfish-embedded-3.1 + + + + + + org.jboss.seam.test + glassfish-embedded-3.1 + pom + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + **/weld/** + + + + + + + + + glassfish-remote-3.1 + + + arquillian + glassfish-remote-3.1 + + + + + + org.jboss.seam.test + glassfish-remote-3.1 + pom + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + **/weld/** + + + + + + + diff --git a/testsuite/src/test/java/org/jboss/seam/faces/test/weld/config/ViewConfigTest.java b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/config/ViewConfigTest.java index 6eee302..8c35f73 100644 --- a/testsuite/src/test/java/org/jboss/seam/faces/test/weld/config/ViewConfigTest.java +++ b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/config/ViewConfigTest.java @@ -97,7 +97,7 @@ public void testViewConfigStore() { public void testViewConfigDescriptor() { Map descriptorMap = new HashMap(); - for (ViewConfigDescriptor descriptor : store.getAllViewConfigDescriptors()) { + for (ViewConfigDescriptor descriptor : store.getViewConfigDescriptors()) { Assert.assertFalse("duplicated viewId "+descriptor.getViewId(), descriptorMap.containsKey(descriptor.getViewId())); descriptorMap.put(descriptor.getViewId(), descriptor); } diff --git a/testsuite/src/test/java/org/jboss/seam/faces/test/weld/security/RestrictViewActionTest.java b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/security/RestrictViewActionTest.java new file mode 100644 index 0000000..84374fd --- /dev/null +++ b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/security/RestrictViewActionTest.java @@ -0,0 +1,145 @@ +/* + * JBoss, Home of Professional Open Source + * Copyright 2011, Red Hat, Inc., and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jboss.seam.faces.test.weld.security; + +import java.util.ArrayList; +import java.util.List; + +import javax.enterprise.inject.spi.BeanManager; +import javax.inject.Inject; + +import junit.framework.Assert; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.junit.Arquillian; +import org.jboss.seam.faces.event.PhaseIdType; +import org.jboss.seam.faces.event.qualifier.RenderResponse; +import org.jboss.seam.faces.security.RestrictAtPhaseDefault; +import org.jboss.seam.faces.security.RestrictViewActionExtension; +import org.jboss.seam.faces.security.RestrictViewActionHandlerProvider; +import org.jboss.seam.faces.security.RestrictViewActionUtils; +import org.jboss.seam.faces.test.weld.config.annotation.RestrictedAtRestoreViewLiteral; +import org.jboss.seam.faces.test.weld.config.annotation.RestrictedDefaultLiteral; +import org.jboss.seam.faces.test.weld.config.annotation.ViewConfigEnum; +import org.jboss.seam.faces.view.action.PhaseInstant; +import org.jboss.seam.faces.view.action.ViewActionHandler; +import org.jboss.seam.faces.view.config.ViewConfigStore; +import org.jboss.seam.faces.view.config.ViewConfigStoreImpl; +import org.jboss.seam.security.annotations.SecurityBindingType; +import org.jboss.seam.security.events.AuthorizationCheckEvent; +import org.jboss.seam.security.extension.SecurityExtension; +import org.jboss.shrinkwrap.api.Archive; +import org.jboss.shrinkwrap.api.ArchivePaths; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.ByteArrayAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * @author Brian Leathem + */ +@RunWith(Arquillian.class) +public class RestrictViewActionTest { + @Deployment + public static Archive createTestArchive() { + JavaArchive archive = ShrinkWrap.create(JavaArchive.class).addClass(ViewConfigStoreImpl.class) + .addClass(RestrictViewActionExtension.class) + .addClass(AuthorizationCheckEvent.class) + .addClass(SecurityBindingType.class) + .addClass(SecurityExtension.class) + .addClass(PhaseIdType.class) + .addClass(RestrictAtPhaseDefault.class) + .addClass(IdentityMock.class) + .addPackage(RenderResponse.class.getPackage()) + .addPackage(ViewConfigEnum.class.getPackage()) + .addAsManifestResource(new ByteArrayAsset(new byte[0]), ArchivePaths.create("beans.xml")); + return archive; + } + + @Inject + private ViewConfigStore store; + + @Inject + private BeanManager beanManager; + + @Test + public void testIsAnnotationApplicableToPhase() { + + Assert.assertEquals(true, RestrictViewActionUtils.isAnnotationApplicableToPhase(new RestrictedAtRestoreViewLiteral(), PhaseIdType.RESTORE_VIEW, RestrictAtPhaseDefault.DEFAULT_PHASES, beanManager)); + Assert.assertEquals(false, RestrictViewActionUtils.isAnnotationApplicableToPhase(new RestrictedAtRestoreViewLiteral(), PhaseIdType.INVOKE_APPLICATION, RestrictAtPhaseDefault.DEFAULT_PHASES, beanManager)); + Assert.assertEquals(false, RestrictViewActionUtils.isAnnotationApplicableToPhase(new RestrictedAtRestoreViewLiteral(), PhaseIdType.RENDER_RESPONSE, RestrictAtPhaseDefault.DEFAULT_PHASES, beanManager)); + Assert.assertEquals(false, RestrictViewActionUtils.isAnnotationApplicableToPhase(new RestrictedDefaultLiteral(), PhaseIdType.RESTORE_VIEW, RestrictAtPhaseDefault.DEFAULT_PHASES, beanManager)); + Assert.assertEquals(true, RestrictViewActionUtils.isAnnotationApplicableToPhase(new RestrictedDefaultLiteral(), PhaseIdType.INVOKE_APPLICATION, RestrictAtPhaseDefault.DEFAULT_PHASES, beanManager)); + Assert.assertEquals(true, RestrictViewActionUtils.isAnnotationApplicableToPhase(new RestrictedDefaultLiteral(), PhaseIdType.RENDER_RESPONSE, RestrictAtPhaseDefault.DEFAULT_PHASES, beanManager)); + } + + + @Test + public void testIsRestrictPhase() { + + List viewActionHandlers; + viewActionHandlers = getRestrictViewActionHandlersForPhase(PhaseIdType.RESTORE_VIEW, "/qualified/yes.xhtml"); + Assert.assertEquals(1, viewActionHandlers.size()); + + viewActionHandlers = getRestrictViewActionHandlersForPhase(PhaseIdType.INVOKE_APPLICATION, "/qualified/yes.xhtml"); + Assert.assertEquals(1, viewActionHandlers.size()); + + viewActionHandlers = getRestrictViewActionHandlersForPhase(PhaseIdType.RESTORE_VIEW, "/qualified/yes.xhtml"); + Assert.assertEquals(1, viewActionHandlers.size()); + + viewActionHandlers = getRestrictViewActionHandlersForPhase(PhaseIdType.RENDER_RESPONSE, "/qualified/no.xhtml"); + Assert.assertEquals(0, viewActionHandlers.size()); + + viewActionHandlers = getRestrictViewActionHandlersForPhase(PhaseIdType.INVOKE_APPLICATION, "/qualified/no.xhtml"); + Assert.assertEquals(1, viewActionHandlers.size()); + + viewActionHandlers = getRestrictViewActionHandlersForPhase(PhaseIdType.RENDER_RESPONSE, "/happy/cat.xhtml"); + Assert.assertEquals(0, viewActionHandlers.size()); + + } + + @Test + public void testGetDefaultPhases() { + PhaseIdType[] defaults; + defaults = RestrictViewActionUtils.getDefaultPhases("/qualified/no.xhtml", store); + Assert.assertEquals(1, defaults.length); + Assert.assertEquals(PhaseIdType.INVOKE_APPLICATION, defaults[0]); + } + + private List getRestrictViewActionHandlersForPhase(PhaseIdType phase, String viewId) { + List securedViewActionHandlers = getRestrictViewActionHandlers(viewId); + List phaseActionHandlers = new ArrayList(); + for (ViewActionHandler viewActionHandler : securedViewActionHandlers) { + if (viewActionHandler.handles(new PhaseInstant(PhaseIdType.convert(phase), true))) { + phaseActionHandlers.add(viewActionHandler); + } + } + return phaseActionHandlers; + } + + private List getRestrictViewActionHandlers(String viewId) { + List viewActionHandlers = store.getRuntimeViewConfigDescriptor(viewId).getViewActionHandlers(); + List securedViewActionHandlers = new ArrayList(); + for (ViewActionHandler viewActionHandler : viewActionHandlers) { + if (viewActionHandler instanceof RestrictViewActionHandlerProvider.RestrictViewActionHandler) { + securedViewActionHandlers.add(viewActionHandler); + } + } + return securedViewActionHandlers; + } +} diff --git a/testsuite/src/test/java/org/jboss/seam/faces/test/weld/security/SecurityPhaseListenerTest.java b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/security/SecurityPhaseListenerTest.java deleted file mode 100644 index 0253fe3..0000000 --- a/testsuite/src/test/java/org/jboss/seam/faces/test/weld/security/SecurityPhaseListenerTest.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * JBoss, Home of Professional Open Source - * Copyright 2011, Red Hat, Inc., and individual contributors - * by the @authors tag. See the copyright.txt in the distribution for a - * full listing of individual contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.jboss.seam.faces.test.weld.security; - -import java.lang.annotation.Annotation; -import java.util.List; - -import javax.inject.Inject; - -import junit.framework.Assert; -import org.jboss.arquillian.container.test.api.Deployment; -import org.jboss.arquillian.junit.Arquillian; -import org.jboss.seam.faces.event.PhaseIdType; -import org.jboss.seam.faces.event.qualifier.RenderResponse; -import org.jboss.seam.faces.security.RestrictAtPhaseDefault; -import org.jboss.seam.faces.security.SecurityPhaseListener; -import org.jboss.seam.faces.test.weld.config.annotation.RestrictedAtRestoreViewLiteral; -import org.jboss.seam.faces.test.weld.config.annotation.RestrictedDefaultLiteral; -import org.jboss.seam.faces.test.weld.config.annotation.ViewConfigEnum; -import org.jboss.seam.faces.view.config.ViewConfigStore; -import org.jboss.seam.faces.view.config.ViewConfigStoreImpl; -import org.jboss.seam.security.annotations.SecurityBindingType; -import org.jboss.seam.security.events.AuthorizationCheckEvent; -import org.jboss.seam.security.extension.SecurityExtension; -import org.jboss.shrinkwrap.api.Archive; -import org.jboss.shrinkwrap.api.ArchivePaths; -import org.jboss.shrinkwrap.api.ShrinkWrap; -import org.jboss.shrinkwrap.api.asset.ByteArrayAsset; -import org.jboss.shrinkwrap.api.spec.JavaArchive; -import org.junit.Test; -import org.junit.runner.RunWith; - -/** - * @author Brian Leathem - */ -@RunWith(Arquillian.class) -public class SecurityPhaseListenerTest { - @Deployment - public static Archive createTestArchive() { - JavaArchive archive = ShrinkWrap.create(JavaArchive.class).addClass(ViewConfigStoreImpl.class) - .addClass(SecurityPhaseListener.class) - .addClass(AuthorizationCheckEvent.class) - .addClass(SecurityBindingType.class) - .addClass(SecurityExtension.class) - .addClass(PhaseIdType.class) - .addClass(RestrictAtPhaseDefault.class) - .addClass(IdentityMock.class) - .addPackage(RenderResponse.class.getPackage()) - .addPackage(ViewConfigEnum.class.getPackage()) - .addAsManifestResource(new ByteArrayAsset(new byte[0]), ArchivePaths.create("beans.xml")); - return archive; - } - - @Inject - private ViewConfigStore store; - - @Inject - private SecurityPhaseListener listener; - - @Test - public void testIsAnnotationApplicableToPhase() { - Assert.assertEquals(true, listener.isAnnotationApplicableToPhase(new RestrictedAtRestoreViewLiteral(), PhaseIdType.RESTORE_VIEW, RestrictAtPhaseDefault.DEFAULT_PHASES)); - Assert.assertEquals(false, listener.isAnnotationApplicableToPhase(new RestrictedAtRestoreViewLiteral(), PhaseIdType.INVOKE_APPLICATION, RestrictAtPhaseDefault.DEFAULT_PHASES)); - Assert.assertEquals(false, listener.isAnnotationApplicableToPhase(new RestrictedAtRestoreViewLiteral(), PhaseIdType.RENDER_RESPONSE, RestrictAtPhaseDefault.DEFAULT_PHASES)); - Assert.assertEquals(false, listener.isAnnotationApplicableToPhase(new RestrictedDefaultLiteral(), PhaseIdType.RESTORE_VIEW, RestrictAtPhaseDefault.DEFAULT_PHASES)); - Assert.assertEquals(true, listener.isAnnotationApplicableToPhase(new RestrictedDefaultLiteral(), PhaseIdType.INVOKE_APPLICATION, RestrictAtPhaseDefault.DEFAULT_PHASES)); - Assert.assertEquals(true, listener.isAnnotationApplicableToPhase(new RestrictedDefaultLiteral(), PhaseIdType.RENDER_RESPONSE, RestrictAtPhaseDefault.DEFAULT_PHASES)); - } - - - @Test - public void testIsRestrictPhase() { - List restrict; - restrict = listener.getRestrictionsForPhase(PhaseIdType.RESTORE_VIEW, "/qualified/yes.xhtml"); - Assert.assertEquals(1, restrict.size()); - - restrict = listener.getRestrictionsForPhase(PhaseIdType.INVOKE_APPLICATION, "/qualified/yes.xhtml"); - Assert.assertEquals(1, restrict.size()); - - restrict = listener.getRestrictionsForPhase(PhaseIdType.RESTORE_VIEW, "/qualified/yes.xhtml"); - Assert.assertEquals(1, restrict.size()); - - restrict = listener.getRestrictionsForPhase(PhaseIdType.RENDER_RESPONSE, "/qualified/no.xhtml"); - Assert.assertEquals(null, restrict); - - restrict = listener.getRestrictionsForPhase(PhaseIdType.INVOKE_APPLICATION, "/qualified/no.xhtml"); - Assert.assertEquals(1, restrict.size()); - - restrict = listener.getRestrictionsForPhase(PhaseIdType.RENDER_RESPONSE, "/happy/cat.xhtml"); - Assert.assertEquals(null, restrict); - - } - - @Test - public void testGetDefaultPhases() { - PhaseIdType[] defaults; - defaults = listener.getDefaultPhases("/qualified/no.xhtml"); - Assert.assertEquals(1, defaults.length); - Assert.assertEquals(PhaseIdType.INVOKE_APPLICATION, defaults[0]); - } -} diff --git a/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/ViewControllerDescriptorTest.java b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/ViewControllerDescriptorTest.java deleted file mode 100644 index 1046724..0000000 --- a/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/ViewControllerDescriptorTest.java +++ /dev/null @@ -1,77 +0,0 @@ -package org.jboss.seam.faces.test.weld.view.action; - -import java.util.List; -import java.util.Map; - -import javax.faces.event.PhaseId; -import javax.inject.Inject; - -import junit.framework.Assert; - -import org.jboss.arquillian.container.test.api.Deployment; -import org.jboss.arquillian.junit.Arquillian; -import org.jboss.seam.faces.test.weld.view.action.annotation.AfterInvokeApplicationViewAction; -import org.jboss.seam.faces.test.weld.view.action.annotation.BeforeRenderResponseViewAction; -import org.jboss.seam.faces.test.weld.view.action.annotation.ClientController; -import org.jboss.seam.faces.test.weld.view.action.annotation.CountryController; -import org.jboss.seam.faces.test.weld.view.action.annotation.ViewConfigEnum; -import org.jboss.seam.faces.view.action.PhaseInstant; -import org.jboss.seam.faces.view.action.ViewActionStrategy; -import org.jboss.seam.faces.view.action.ViewControllerDescriptor; -import org.jboss.seam.faces.view.action.ViewControllerExtension; -import org.jboss.seam.faces.view.action.ViewControllerStore; -import org.jboss.seam.faces.view.config.ViewConfigStoreImpl; -import org.jboss.shrinkwrap.api.Archive; -import org.jboss.shrinkwrap.api.ArchivePaths; -import org.jboss.shrinkwrap.api.ShrinkWrap; -import org.jboss.shrinkwrap.api.asset.ByteArrayAsset; -import org.jboss.shrinkwrap.api.spec.JavaArchive; -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(Arquillian.class) -public class ViewControllerDescriptorTest { - @Deployment - public static Archive createTestArchive() { - JavaArchive archive = ShrinkWrap.create(JavaArchive.class).addClass(ViewConfigStoreImpl.class) - .addClass(ViewControllerStore.class).addClass(ViewControllerExtension.class) - .addClass(AfterInvokeApplicationViewAction.class).addClass(BeforeRenderResponseViewAction.class) - .addClass(ClientController.class).addClass(CountryController.class).addClass(ViewConfigEnum.class) - .addPackage(ViewConfigEnum.class.getPackage()) - .addAsManifestResource(new ByteArrayAsset(new byte[0]), ArchivePaths.create("beans.xml")); - return archive; - } - - @Inject - private ViewControllerStore store; - - @Test - public void testGetControllerDescriptors() { - - List descriptors = store.getControllerDescriptors("/client/done.xhtml"); - Assert.assertEquals(2, descriptors.size()); - ViewControllerDescriptor descriptor = descriptors.get(0); - Assert.assertEquals("/client/*", descriptor.getViewId()); - Map> phaseMethods = descriptor.getPhaseMethods(); - List viewActions = phaseMethods.get(PhaseInstant.BEFORE_RENDER_RESPONSE); - Assert.assertEquals(1, viewActions.size()); - ViewActionStrategy actionStrategy = viewActions.get(0); - Assert.assertEquals("#{clientController.viewAction}", - ((ViewControllerDescriptor.MethodExpressionInvoker) actionStrategy).getMethodExpressionString()); - descriptor = descriptors.get(1); - Assert.assertEquals("/client/*", descriptor.getViewId()); - phaseMethods = descriptor.getPhaseMethods(); - Assert.assertEquals(2, phaseMethods.size()); - Assert.assertEquals(1, phaseMethods.get(PhaseInstant.BEFORE_RENDER_RESPONSE).size()); - Assert.assertEquals(1, phaseMethods.get(new PhaseInstant(PhaseId.INVOKE_APPLICATION, false)).size()); - - descriptors = store.getControllerDescriptors("/country/done.xhtml"); - Assert.assertEquals(1, descriptors.size()); - descriptor = descriptors.get(0); - Assert.assertEquals("/country/*", descriptor.getViewId()); - Assert.assertEquals(CountryController.class, descriptor.getViewControllerClass()); - phaseMethods = descriptor.getPhaseMethods(); - Assert.assertEquals(1, phaseMethods.size()); - Assert.assertEquals(1, phaseMethods.get(PhaseInstant.BEFORE_RENDER_RESPONSE).size()); - } -} diff --git a/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/annotation/ClientController.java b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/annotation/ClientController.java deleted file mode 100644 index b6cd8dd..0000000 --- a/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/annotation/ClientController.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.jboss.seam.faces.test.weld.view.action.annotation; - -import javax.inject.Named; - -@Named -public class ClientController { - public void viewAction() { - } - - @BeforeRenderResponseViewAction(ViewConfigEnum.Pages.CLIENTS) - public void beforeRenderResponse() { - } - - @AfterInvokeApplicationViewAction(ViewConfigEnum.Pages.CLIENTS) - public void afterInvokeApplication() { - } -} diff --git a/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/annotation/CountryController.java b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/annotation/CountryController.java deleted file mode 100644 index af6b5a4..0000000 --- a/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/annotation/CountryController.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.jboss.seam.faces.test.weld.view.action.annotation; - -import javax.inject.Named; - -import org.jboss.seam.faces.event.qualifier.Before; -import org.jboss.seam.faces.event.qualifier.RenderResponse; - -@Named -public class CountryController { - - @Before - @RenderResponse - public void beforeRenderResponse() { - } -} diff --git a/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/binding/AController.java b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/binding/AController.java new file mode 100644 index 0000000..ec97382 --- /dev/null +++ b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/binding/AController.java @@ -0,0 +1,30 @@ +package org.jboss.seam.faces.test.weld.view.action.binding; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Named; + +@Named +@ApplicationScoped +public class AController { + + private AController mock; + + @BeforeRenderResponseViewAction(ViewConfigEnum.Pages.CLIENTS) + public void beforeRenderResponse() { + mock.beforeRenderResponse(); + } + + @AfterInvokeApplicationViewAction(ViewConfigEnum.Pages.CLIENTS) + public void afterInvokeApplication() { + mock.afterInvokeApplication(); + } + + public AController getMock() { + return mock; + } + + public void setMock(AController mock) { + this.mock = mock; + } + +} diff --git a/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/annotation/AfterInvokeApplicationViewAction.java b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/binding/AfterInvokeApplicationViewAction.java similarity index 79% rename from testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/annotation/AfterInvokeApplicationViewAction.java rename to testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/binding/AfterInvokeApplicationViewAction.java index 60a1ef2..1f96feb 100644 --- a/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/annotation/AfterInvokeApplicationViewAction.java +++ b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/binding/AfterInvokeApplicationViewAction.java @@ -1,4 +1,4 @@ -package org.jboss.seam.faces.test.weld.view.action.annotation; +package org.jboss.seam.faces.test.weld.view.action.binding; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -7,7 +7,7 @@ import org.jboss.seam.faces.event.qualifier.After; import org.jboss.seam.faces.event.qualifier.InvokeApplication; -import org.jboss.seam.faces.view.action.ViewActionBindingType; +import org.jboss.seam.faces.view.action.binding.ViewActionBindingType; @ViewActionBindingType @After diff --git a/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/annotation/BeforeRenderResponseViewAction.java b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/binding/BeforeRenderResponseViewAction.java similarity index 74% rename from testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/annotation/BeforeRenderResponseViewAction.java rename to testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/binding/BeforeRenderResponseViewAction.java index 168b393..74749ef 100644 --- a/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/annotation/BeforeRenderResponseViewAction.java +++ b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/binding/BeforeRenderResponseViewAction.java @@ -1,11 +1,11 @@ -package org.jboss.seam.faces.test.weld.view.action.annotation; +package org.jboss.seam.faces.test.weld.view.action.binding; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.jboss.seam.faces.view.action.ViewActionBindingType; +import org.jboss.seam.faces.view.action.binding.ViewActionBindingType; @ViewActionBindingType @Retention(RetentionPolicy.RUNTIME) diff --git a/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/binding/HighOrderViewAction.java b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/binding/HighOrderViewAction.java new file mode 100644 index 0000000..66baf6a --- /dev/null +++ b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/binding/HighOrderViewAction.java @@ -0,0 +1,21 @@ +package org.jboss.seam.faces.test.weld.view.action.binding; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.jboss.seam.faces.event.qualifier.After; +import org.jboss.seam.faces.event.qualifier.InvokeApplication; +import org.jboss.seam.faces.view.action.Order; +import org.jboss.seam.faces.view.action.binding.ViewActionBindingType; + +@ViewActionBindingType +@After +@InvokeApplication +@Order(1000) +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE}) +public @interface HighOrderViewAction { + ViewConfigEnum.Pages value(); +} diff --git a/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/binding/LowOrderViewAction.java b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/binding/LowOrderViewAction.java new file mode 100644 index 0000000..c0782d8 --- /dev/null +++ b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/binding/LowOrderViewAction.java @@ -0,0 +1,21 @@ +package org.jboss.seam.faces.test.weld.view.action.binding; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.jboss.seam.faces.event.qualifier.After; +import org.jboss.seam.faces.event.qualifier.InvokeApplication; +import org.jboss.seam.faces.view.action.Order; +import org.jboss.seam.faces.view.action.binding.ViewActionBindingType; + +@ViewActionBindingType +@After +@InvokeApplication +@Order(100) +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE}) +public @interface LowOrderViewAction { + ViewConfigEnum.Pages value(); +} diff --git a/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/binding/MiddleOrderViewAction.java b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/binding/MiddleOrderViewAction.java new file mode 100644 index 0000000..f7dfb5b --- /dev/null +++ b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/binding/MiddleOrderViewAction.java @@ -0,0 +1,21 @@ +package org.jboss.seam.faces.test.weld.view.action.binding; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.jboss.seam.faces.event.qualifier.After; +import org.jboss.seam.faces.event.qualifier.InvokeApplication; +import org.jboss.seam.faces.view.action.Order; +import org.jboss.seam.faces.view.action.binding.ViewActionBindingType; + +@ViewActionBindingType +@After +@InvokeApplication +@Order(500) +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE}) +public @interface MiddleOrderViewAction { + ViewConfigEnum.Pages value(); +} diff --git a/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/binding/OrderedController.java b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/binding/OrderedController.java new file mode 100644 index 0000000..c98db2c --- /dev/null +++ b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/binding/OrderedController.java @@ -0,0 +1,45 @@ +package org.jboss.seam.faces.test.weld.view.action.binding; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Named; + +@Named +@ApplicationScoped +public class OrderedController { + + private OrderedController mock; + + @HighOrderViewAction(ViewConfigEnum.Pages.ORDER_TEST) + public void highOrder() { + mock.highOrder(); + } + + @LowOrderViewAction(ViewConfigEnum.Pages.ORDER_TEST) + public void lowOrder() { + mock.lowOrder(); + } + + @MiddleOrderViewAction(ViewConfigEnum.Pages.ORDER_TEST) + public void middleOrder() { + mock.middleOrder(); + } + + @ParameterizedOrderViewAction(value=ViewConfigEnum.Pages.ORDER_TEST, order=1) + public void order1() { + mock.order1(); + } + + @ParameterizedOrderViewAction(value=ViewConfigEnum.Pages.ORDER_TEST, order=600) + public void order600() { + mock.order600(); + } + + public OrderedController getMock() { + return mock; + } + + public void setMock(OrderedController mock) { + this.mock = mock; + } + +} diff --git a/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/binding/ParameterizedOrderViewAction.java b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/binding/ParameterizedOrderViewAction.java new file mode 100644 index 0000000..30fe355 --- /dev/null +++ b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/binding/ParameterizedOrderViewAction.java @@ -0,0 +1,22 @@ +package org.jboss.seam.faces.test.weld.view.action.binding; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.jboss.seam.faces.event.qualifier.After; +import org.jboss.seam.faces.event.qualifier.InvokeApplication; +import org.jboss.seam.faces.view.action.Order; +import org.jboss.seam.faces.view.action.binding.ViewActionBindingType; + +@ViewActionBindingType +@After +@InvokeApplication +@Order(100) +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE}) +public @interface ParameterizedOrderViewAction { + ViewConfigEnum.Pages value(); + int order() default 1000; +} diff --git a/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/binding/ViewActionBindingTest.java b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/binding/ViewActionBindingTest.java new file mode 100644 index 0000000..ac14805 --- /dev/null +++ b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/binding/ViewActionBindingTest.java @@ -0,0 +1,96 @@ +package org.jboss.seam.faces.test.weld.view.action.binding; + +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +import javax.faces.event.PhaseId; +import javax.inject.Inject; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.junit.Arquillian; +import org.jboss.seam.faces.view.config.ViewConfigDescriptor; +import org.jboss.seam.faces.view.config.ViewConfigStore; +import org.jboss.seam.faces.view.config.ViewConfigStoreImpl; +import org.jboss.shrinkwrap.api.Archive; +import org.jboss.shrinkwrap.api.ArchivePaths; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.ByteArrayAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InOrder; + +@RunWith(Arquillian.class) +public class ViewActionBindingTest { + @Deployment + public static Archive createTestArchive() { + JavaArchive archive = ShrinkWrap.create(JavaArchive.class).addClass(ViewConfigStoreImpl.class) + .addPackage(ViewConfigEnum.class.getPackage()) + .addAsManifestResource(new ByteArrayAsset(new byte[0]), ArchivePaths.create("beans.xml")); + return archive; + } + + @Inject + private ViewConfigStore store; + + @Inject + private AController aController; + + private AController aControllerMock; + + @Inject + private OrderedController orderedController; + + private OrderedController orderedControllerMock; + + @Before + public void setUp() { + aControllerMock = mock(AController.class); + aController.setMock(aControllerMock); + orderedControllerMock = mock(OrderedController.class); + orderedController.setMock(orderedControllerMock); + } + + @Test + public void testAController() { + + ViewConfigDescriptor viewConfigDescriptor = store.getRuntimeViewConfigDescriptor("/client/done.xhtml"); + callPhaseAndVerifyZeroInteractions(viewConfigDescriptor, PhaseId.RESTORE_VIEW, PhaseId.APPLY_REQUEST_VALUES, + PhaseId.APPLY_REQUEST_VALUES, PhaseId.UPDATE_MODEL_VALUES); + viewConfigDescriptor.executeBeforePhase(PhaseId.INVOKE_APPLICATION); + verifyZeroInteractions(aControllerMock); + viewConfigDescriptor.executeAfterPhase(PhaseId.INVOKE_APPLICATION); + verify(aControllerMock).afterInvokeApplication(); + reset(aControllerMock); + viewConfigDescriptor.executeBeforePhase(PhaseId.RENDER_RESPONSE); + verify(aControllerMock).beforeRenderResponse(); + reset(aControllerMock); + viewConfigDescriptor.executeAfterPhase(PhaseId.RENDER_RESPONSE); + verifyZeroInteractions(aControllerMock); + } + + @Test + public void testOrder() { + + ViewConfigDescriptor viewConfigDescriptor = store.getRuntimeViewConfigDescriptor("/order.xhtml"); + viewConfigDescriptor.executeAfterPhase(PhaseId.INVOKE_APPLICATION); + InOrder inOrder = inOrder(orderedControllerMock); + inOrder.verify(orderedControllerMock).order1(); + inOrder.verify(orderedControllerMock).lowOrder(); + inOrder.verify(orderedControllerMock).middleOrder(); + inOrder.verify(orderedControllerMock).order600(); + inOrder.verify(orderedControllerMock).highOrder(); + } + + private void callPhaseAndVerifyZeroInteractions(ViewConfigDescriptor viewConfigDescriptor, PhaseId... phases) { + for (PhaseId phase : phases) { + viewConfigDescriptor.executeBeforePhase(phase); + viewConfigDescriptor.executeAfterPhase(phase); + verifyZeroInteractions(aControllerMock); + } + } +} diff --git a/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/binding/ViewConfigEnum.java b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/binding/ViewConfigEnum.java new file mode 100644 index 0000000..8db603b --- /dev/null +++ b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/binding/ViewConfigEnum.java @@ -0,0 +1,40 @@ +/* + * JBoss, Home of Professional Open Source + * Copyright 2011, Red Hat, Inc., and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jboss.seam.faces.test.weld.view.action.binding; + +import org.jboss.seam.faces.view.config.ViewConfig; +import org.jboss.seam.faces.view.config.ViewPattern; + +@ViewConfig +public interface ViewConfigEnum { + static enum Pages { + @ViewPattern("/*") + DEFAULT, + + @ViewPattern("/client/*") + CLIENTS, + + @ViewPattern("/country/*") + COUNTRIES(), + + @ViewPattern("/order.xhtml") + ORDER_TEST(), + + @ViewPattern("/client/done.xhtml") + CLIENT_CONFIRMED(), + } +} diff --git a/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/controller/ClientController.java b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/controller/ClientController.java new file mode 100644 index 0000000..08a381a --- /dev/null +++ b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/controller/ClientController.java @@ -0,0 +1,49 @@ +package org.jboss.seam.faces.test.weld.view.action.controller; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Named; + +import org.jboss.seam.faces.event.qualifier.After; +import org.jboss.seam.faces.event.qualifier.Before; +import org.jboss.seam.faces.event.qualifier.InvokeApplication; +import org.jboss.seam.faces.event.qualifier.RenderResponse; + +@Named +@ApplicationScoped +public class ClientController { + + private ClientController mock; + + @Before + @RenderResponse + public void beforeRenderResponse() { + mock.beforeRenderResponse(); + } + + @Before + @InvokeApplication + public void beforeInvokeApplication() { + mock.beforeInvokeApplication(); + } + + @After + @InvokeApplication + public void afterInvokeApplication() { + mock.afterInvokeApplication(); + } + + @After + @InvokeApplication + public void afterInvokeApplication2() { + mock.afterInvokeApplication2(); + } + + public ClientController getMock() { + return mock; + } + + public void setMock(ClientController mock) { + this.mock = mock; + } + +} diff --git a/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/controller/CountryController.java b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/controller/CountryController.java new file mode 100644 index 0000000..7b98d01 --- /dev/null +++ b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/controller/CountryController.java @@ -0,0 +1,35 @@ +package org.jboss.seam.faces.test.weld.view.action.controller; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Named; + +import org.jboss.seam.faces.event.qualifier.After; +import org.jboss.seam.faces.event.qualifier.Before; +import org.jboss.seam.faces.event.qualifier.RenderResponse; + +@Named +@ApplicationScoped +public class CountryController { + + private CountryController mock; + + @Before + @RenderResponse + public void beforeRenderResponse() { + mock.beforeRenderResponse(); + } + + @After + @RenderResponse + public void afterRenderResponse(DependentBean dependenBean) { + mock.afterRenderResponse(dependenBean); + } + + public CountryController getMock() { + return mock; + } + + public void setMock(CountryController mock) { + this.mock = mock; + } +} diff --git a/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/controller/DependentBean.java b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/controller/DependentBean.java new file mode 100644 index 0000000..5eee696 --- /dev/null +++ b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/controller/DependentBean.java @@ -0,0 +1,8 @@ +package org.jboss.seam.faces.test.weld.view.action.controller; + +import javax.enterprise.context.ApplicationScoped; + +@ApplicationScoped +public class DependentBean { + +} diff --git a/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/annotation/ViewConfigEnum.java b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/controller/ViewConfigEnum.java similarity index 70% rename from testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/annotation/ViewConfigEnum.java rename to testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/controller/ViewConfigEnum.java index 39a0573..af04ed7 100644 --- a/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/annotation/ViewConfigEnum.java +++ b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/controller/ViewConfigEnum.java @@ -14,12 +14,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.jboss.seam.faces.test.weld.view.action.annotation; +package org.jboss.seam.faces.test.weld.view.action.controller; -import org.jboss.seam.faces.event.PhaseIdType; -import org.jboss.seam.faces.security.RestrictAtPhase; -import org.jboss.seam.faces.view.action.ViewAction; -import org.jboss.seam.faces.view.action.ViewController; +import org.jboss.seam.faces.view.action.controller.ViewController; import org.jboss.seam.faces.view.config.ViewConfig; import org.jboss.seam.faces.view.config.ViewPattern; @@ -30,7 +27,6 @@ static enum Pages { DEFAULT, @ViewPattern("/client/*") - @ViewAction("#{clientController.viewAction}") CLIENTS, @ViewPattern("/country/*") @@ -38,14 +34,12 @@ static enum Pages { COUNTRIES(), @ViewPattern("/client/done.xhtml") + @ViewController(ClientController.class) CLIENT_CONFIRMED(), - @ViewPattern("/qualified/*") - @RestrictAtPhase(PhaseIdType.INVOKE_APPLICATION) - QUALIFIED, - - @ViewPattern("/qualified/yes.xhtml") - QUALIFIED_YES; + @ViewPattern("/multiple/*") + @ViewController({ CountryController.class, ClientController.class }) + MULTIPLE() } } diff --git a/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/controller/ViewControllerTest.java b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/controller/ViewControllerTest.java new file mode 100644 index 0000000..d3b8a1e --- /dev/null +++ b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/controller/ViewControllerTest.java @@ -0,0 +1,124 @@ +package org.jboss.seam.faces.test.weld.view.action.controller; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +import javax.faces.event.PhaseId; +import javax.inject.Inject; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.junit.Arquillian; +import org.jboss.seam.faces.view.config.ViewConfigDescriptor; +import org.jboss.seam.faces.view.config.ViewConfigStore; +import org.jboss.seam.faces.view.config.ViewConfigStoreImpl; +import org.jboss.shrinkwrap.api.Archive; +import org.jboss.shrinkwrap.api.ArchivePaths; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.ByteArrayAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(Arquillian.class) +public class ViewControllerTest { + @Deployment + public static Archive createTestArchive() { + JavaArchive archive = ShrinkWrap.create(JavaArchive.class).addClass(ViewConfigStoreImpl.class) + .addPackage(ViewConfigEnum.class.getPackage()) + .addAsManifestResource(new ByteArrayAsset(new byte[0]), ArchivePaths.create("beans.xml")); + return archive; + } + + @Inject + private ViewConfigStore store; + + @Inject + private CountryController countryController; + + private CountryController countryControllerMock; + + @Inject + private ClientController clientController; + + @Inject + private DependentBean dependentBean; + + private ClientController clientControllerMock; + + @Before + public void setUp() { + countryControllerMock = mock(CountryController.class); + countryController.setMock(countryControllerMock); + clientControllerMock = mock(ClientController.class); + clientController.setMock(clientControllerMock); + } + + @Test + public void testClientController() { + + ViewConfigDescriptor viewConfigDescriptor = store.getRuntimeViewConfigDescriptor("/client/done.xhtml"); + callPhaseAndVerifyZeroInteractions(viewConfigDescriptor, PhaseId.RESTORE_VIEW, PhaseId.APPLY_REQUEST_VALUES, + PhaseId.APPLY_REQUEST_VALUES, PhaseId.UPDATE_MODEL_VALUES); + viewConfigDescriptor.executeBeforePhase(PhaseId.INVOKE_APPLICATION); + verify(clientControllerMock).beforeInvokeApplication(); + reset(clientControllerMock); + viewConfigDescriptor.executeAfterPhase(PhaseId.INVOKE_APPLICATION); + verify(clientControllerMock).afterInvokeApplication(); + verify(clientControllerMock).afterInvokeApplication2(); + reset(clientControllerMock); + viewConfigDescriptor.executeBeforePhase(PhaseId.RENDER_RESPONSE); + verify(clientControllerMock).beforeRenderResponse(); + reset(clientControllerMock); + viewConfigDescriptor.executeAfterPhase(PhaseId.RENDER_RESPONSE); + verifyZeroInteractions(clientControllerMock); + verifyZeroInteractions(countryControllerMock); + } + + @Test + public void testCountryController() { + + ViewConfigDescriptor viewConfigDescriptor = store.getRuntimeViewConfigDescriptor("/country/done.xhtml"); + callPhaseAndVerifyZeroInteractions(viewConfigDescriptor, PhaseId.RESTORE_VIEW, PhaseId.APPLY_REQUEST_VALUES, + PhaseId.APPLY_REQUEST_VALUES, PhaseId.UPDATE_MODEL_VALUES, PhaseId.INVOKE_APPLICATION); + viewConfigDescriptor.executeBeforePhase(PhaseId.RENDER_RESPONSE); + verify(countryControllerMock).beforeRenderResponse(); + reset(countryControllerMock); + viewConfigDescriptor.executeAfterPhase(PhaseId.RENDER_RESPONSE); + verify(countryControllerMock).afterRenderResponse(dependentBean); + verifyZeroInteractions(clientControllerMock); + } + + @Test + public void testMultipleControllers() { + + ViewConfigDescriptor viewConfigDescriptor = store.getRuntimeViewConfigDescriptor("/multiple/done.xhtml"); + callPhaseAndVerifyZeroInteractions(viewConfigDescriptor, PhaseId.RESTORE_VIEW, PhaseId.APPLY_REQUEST_VALUES, + PhaseId.APPLY_REQUEST_VALUES, PhaseId.UPDATE_MODEL_VALUES); + viewConfigDescriptor.executeBeforePhase(PhaseId.INVOKE_APPLICATION); + verify(clientControllerMock).beforeInvokeApplication(); + reset(clientControllerMock); + viewConfigDescriptor.executeAfterPhase(PhaseId.INVOKE_APPLICATION); + verify(clientControllerMock).afterInvokeApplication(); + verify(clientControllerMock).afterInvokeApplication2(); + reset(clientControllerMock); + verifyZeroInteractions(countryControllerMock); + viewConfigDescriptor.executeBeforePhase(PhaseId.RENDER_RESPONSE); + verify(countryControllerMock).beforeRenderResponse(); + verify(clientControllerMock).beforeRenderResponse(); + reset(clientControllerMock, countryControllerMock); + viewConfigDescriptor.executeAfterPhase(PhaseId.RENDER_RESPONSE); + verify(countryControllerMock).afterRenderResponse(dependentBean); + verifyZeroInteractions(clientControllerMock); + } + + private void callPhaseAndVerifyZeroInteractions(ViewConfigDescriptor viewConfigDescriptor, PhaseId... phases) { + for (PhaseId phase : phases) { + viewConfigDescriptor.executeBeforePhase(phase); + viewConfigDescriptor.executeAfterPhase(phase); + verifyZeroInteractions(countryControllerMock, clientControllerMock); + } + } +} diff --git a/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/el/ElViewActionBean.java b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/el/ElViewActionBean.java new file mode 100644 index 0000000..b00703f --- /dev/null +++ b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/el/ElViewActionBean.java @@ -0,0 +1,26 @@ +package org.jboss.seam.faces.test.weld.view.action.el; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Named; + +@Named +@ApplicationScoped +public class ElViewActionBean { + private ElViewActionBean mock; + + public void viewAction() { + mock.viewAction(); + } + + public void parameterizedViewAction(String message) { + mock.parameterizedViewAction(message); + } + + public ElViewActionBean getMock() { + return mock; + } + + public void setMock(ElViewActionBean mock) { + this.mock = mock; + } +} diff --git a/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/el/ElViewActionConfigEnum.java b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/el/ElViewActionConfigEnum.java new file mode 100644 index 0000000..c719716 --- /dev/null +++ b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/el/ElViewActionConfigEnum.java @@ -0,0 +1,40 @@ +package org.jboss.seam.faces.test.weld.view.action.el; + +import org.jboss.seam.faces.event.qualifier.InvokeApplication; +import org.jboss.seam.faces.view.action.el.ElViewAction; +import org.jboss.seam.faces.view.config.ViewConfig; +import org.jboss.seam.faces.view.config.ViewPattern; + +@ViewConfig +public interface ElViewActionConfigEnum { + static enum Pages { + @ViewPattern("/*") + DEFAULT, + + @ViewPattern("/client/*") + @ElViewAction("#{elViewActionBean.viewAction}") + CLIENTS, + + @ViewPattern("/client/done.xhtml") + CLIENT_CONFIRMED(), + + @ViewPattern("/country/*") + @ElViewAction("#{elViewActionBean.parameterizedViewAction('COUNTRIES')}") + COUNTRIES(), + + @ViewPattern("/country/done.xhtml") + @ElViewAction("#{elViewActionBean.parameterizedViewAction('COUNTRY_CONFIRMED')}") + COUNTRY_CONFIRMED(), + + @ViewPattern("/explicit-phase/*") + @ElViewAction(value="#{elViewActionBean.viewAction}", phase=InvokeApplication.class) + EXPLICIT_PHASE(), + + @ViewPattern("/qualified/*") + QUALIFIED, + + @ViewPattern("/qualified/yes.xhtml") + QUALIFIED_YES; + + } +} diff --git a/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/el/ElViewActionTest.java b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/el/ElViewActionTest.java new file mode 100644 index 0000000..e5ab213 --- /dev/null +++ b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/el/ElViewActionTest.java @@ -0,0 +1,117 @@ +package org.jboss.seam.faces.test.weld.view.action.el; + +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +import java.io.File; + +import javax.faces.event.PhaseId; +import javax.inject.Inject; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.junit.Arquillian; +import org.jboss.seam.faces.view.config.ViewConfigDescriptor; +import org.jboss.seam.faces.view.config.ViewConfigStore; +import org.jboss.seam.faces.view.config.ViewConfigStoreImpl; +import org.jboss.shrinkwrap.api.Archive; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.EmptyAsset; +import org.jboss.shrinkwrap.api.importer.ZipImporter; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InOrder; + +@RunWith(Arquillian.class) +public class ElViewActionTest { + private static final String SOLDER_API = "solder-api.jar"; + private static final String SOLDER_IMPL = "solder-impl.jar"; + private static final String SOLDER_LIB_DIR = "target/test-libs/"; + + @Deployment + public static Archive createTestArchive() { + WebArchive archive = ShrinkWrap + .create(WebArchive.class, "test.war") + .addAsLibraries( + ShrinkWrap.create(ZipImporter.class, SOLDER_API).importFrom(new File(SOLDER_LIB_DIR + SOLDER_API)) + .as(JavaArchive.class), + ShrinkWrap.create(ZipImporter.class, SOLDER_IMPL).importFrom(new File(SOLDER_LIB_DIR + SOLDER_IMPL)) + .as(JavaArchive.class)) + .addClass(ViewConfigStoreImpl.class).addClass(ElViewActionTest.class) + .addClass(ElViewActionBean.class).addClass(ElViewActionConfigEnum.class) + .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml"); + return archive; + } + + @Inject + private ViewConfigStore store; + + @Inject + private ElViewActionBean elViewActionBean; + + private ElViewActionBean mock; + + @Before + public void setUp() { + mock = mock(ElViewActionBean.class); + elViewActionBean.setMock(mock); + } + + @Test + public void testViewAction() { + ViewConfigDescriptor viewConfigDescriptor = store.getRuntimeViewConfigDescriptor("/client/done.xhtml"); + callPhaseAndVerifyZeroInteractions(viewConfigDescriptor, PhaseId.RESTORE_VIEW, PhaseId.APPLY_REQUEST_VALUES, + PhaseId.APPLY_REQUEST_VALUES, PhaseId.UPDATE_MODEL_VALUES, PhaseId.INVOKE_APPLICATION); + viewConfigDescriptor.executeBeforePhase(PhaseId.RENDER_RESPONSE); + verify(mock).viewAction(); + reset(mock); + viewConfigDescriptor.executeAfterPhase(PhaseId.RENDER_RESPONSE); + verifyZeroInteractions(mock); + } + + @Test + public void testParameterizedViewAction() { + ViewConfigDescriptor viewConfigDescriptor = store.getRuntimeViewConfigDescriptor("/country/done.xhtml"); + callPhaseAndVerifyZeroInteractions(viewConfigDescriptor, PhaseId.RESTORE_VIEW, PhaseId.APPLY_REQUEST_VALUES, + PhaseId.APPLY_REQUEST_VALUES, PhaseId.UPDATE_MODEL_VALUES, PhaseId.INVOKE_APPLICATION); + viewConfigDescriptor.executeBeforePhase(PhaseId.RENDER_RESPONSE); + InOrder inOrder = inOrder(mock); + inOrder.verify(mock).parameterizedViewAction("COUNTRIES"); + inOrder.verify(mock).parameterizedViewAction("COUNTRY_CONFIRMED"); + reset(mock); + viewConfigDescriptor.executeAfterPhase(PhaseId.RENDER_RESPONSE); + verifyZeroInteractions(mock); + } + + @Test + public void testInexistantViewId() { + ViewConfigDescriptor viewConfigDescriptor = store.getRuntimeViewConfigDescriptor("/inexistant.xhtml"); + callPhaseAndVerifyZeroInteractions(viewConfigDescriptor, PhaseId.RESTORE_VIEW, PhaseId.APPLY_REQUEST_VALUES, + PhaseId.APPLY_REQUEST_VALUES, PhaseId.UPDATE_MODEL_VALUES, PhaseId.INVOKE_APPLICATION, PhaseId.RENDER_RESPONSE); + } + + @Test + public void testPhaseViewId() { + ViewConfigDescriptor viewConfigDescriptor = store.getRuntimeViewConfigDescriptor("/explicit-phase/done.xhtml"); + callPhaseAndVerifyZeroInteractions(viewConfigDescriptor, PhaseId.RESTORE_VIEW, PhaseId.APPLY_REQUEST_VALUES, + PhaseId.APPLY_REQUEST_VALUES, PhaseId.UPDATE_MODEL_VALUES, PhaseId.RENDER_RESPONSE); + viewConfigDescriptor.executeBeforePhase(PhaseId.INVOKE_APPLICATION); + verify(mock).viewAction(); + reset(mock); + viewConfigDescriptor.executeAfterPhase(PhaseId.INVOKE_APPLICATION); + verifyZeroInteractions(mock); + } + + private void callPhaseAndVerifyZeroInteractions(ViewConfigDescriptor viewConfigDescriptor, PhaseId... phases) { + for (PhaseId phase : phases) { + viewConfigDescriptor.executeBeforePhase(phase); + viewConfigDescriptor.executeAfterPhase(phase); + verifyZeroInteractions(mock); + } + } +} From d3b2da3bd33b94189685f8e936111a7c1a5d7b30 Mon Sep 17 00:00:00 2001 From: gonzalad Date: Wed, 23 Nov 2011 20:07:28 +0100 Subject: [PATCH 9/9] Sample removed --- .../view/action/el/BeginConversation.java | 24 ----------------- .../el/BeginConversationHandlerProvider.java | 27 ------------------- 2 files changed, 51 deletions(-) delete mode 100644 api/src/main/java/org/jboss/seam/faces/view/action/el/BeginConversation.java delete mode 100644 api/src/main/java/org/jboss/seam/faces/view/action/el/BeginConversationHandlerProvider.java diff --git a/api/src/main/java/org/jboss/seam/faces/view/action/el/BeginConversation.java b/api/src/main/java/org/jboss/seam/faces/view/action/el/BeginConversation.java deleted file mode 100644 index 1ec5388..0000000 --- a/api/src/main/java/org/jboss/seam/faces/view/action/el/BeginConversation.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.jboss.seam.faces.view.action.el; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.jboss.seam.faces.event.qualifier.ApplyRequestValues; -import org.jboss.seam.faces.event.qualifier.Before; -import org.jboss.seam.faces.view.action.Order; -import org.jboss.seam.faces.view.action.OrderDefault; -import org.jboss.seam.faces.view.action.ViewAction; - -@ViewAction(BeginConversationHandlerProvider.class) -@Before -@ApplyRequestValues -@Order(OrderDefault.DEFAULT-10) -@Target(ElementType.FIELD) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface BeginConversation { - boolean join() default false; -} diff --git a/api/src/main/java/org/jboss/seam/faces/view/action/el/BeginConversationHandlerProvider.java b/api/src/main/java/org/jboss/seam/faces/view/action/el/BeginConversationHandlerProvider.java deleted file mode 100644 index 47651c5..0000000 --- a/api/src/main/java/org/jboss/seam/faces/view/action/el/BeginConversationHandlerProvider.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.jboss.seam.faces.view.action.el; - -import javax.enterprise.context.Conversation; -import javax.inject.Inject; - -import org.jboss.seam.faces.view.action.SimpleViewActionHandlerProvider; - -public class BeginConversationHandlerProvider extends SimpleViewActionHandlerProvider { - private boolean join; - - @Inject - private Conversation conversation; - - @Override - public void doInitialize(BeginConversation annotation) { - join = annotation.join(); - } - - @Override - public Object execute() { - if (join && !conversation.isTransient()) { - return null; - } - conversation.begin(); - return null; - } -}