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/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/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/api/src/main/java/org/jboss/seam/faces/view/action/PhaseInstant.java b/api/src/main/java/org/jboss/seam/faces/view/action/PhaseInstant.java new file mode 100644 index 0000000..41b0fcc --- /dev/null +++ b/api/src/main/java/org/jboss/seam/faces/view/action/PhaseInstant.java @@ -0,0 +1,59 @@ +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 String toString() { + return super.toString()+"{phase="+phaseId+",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/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 new file mode 100644 index 0000000..93fe0a4 --- /dev/null +++ b/api/src/main/java/org/jboss/seam/faces/view/action/ViewAction.java @@ -0,0 +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.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; + +/** + * 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. + *

+ * + * @author Adriàn Gonzalez + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface ViewAction { + /** 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/binding/ViewActionBindingType.java b/api/src/main/java/org/jboss/seam/faces/view/action/binding/ViewActionBindingType.java new file mode 100644 index 0000000..31f8e50 --- /dev/null +++ b/api/src/main/java/org/jboss/seam/faces/view/action/binding/ViewActionBindingType.java @@ -0,0 +1,22 @@ +package org.jboss.seam.faces.view.action.binding; + +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. + * + * 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 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface ViewActionBindingType { +} diff --git a/api/src/main/java/org/jboss/seam/faces/view/action/controller/ViewController.java b/api/src/main/java/org/jboss/seam/faces/view/action/controller/ViewController.java new file mode 100644 index 0000000..c3925c4 --- /dev/null +++ b/api/src/main/java/org/jboss/seam/faces/view/action/controller/ViewController.java @@ -0,0 +1,41 @@ +package org.jboss.seam.faces.view.action.controller; + +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; +import org.jboss.seam.faces.view.action.ViewAction; + +/** + * 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 + */ +@ViewAction(ViewControllerHandlerProvider.class) +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +@Documented +public @interface ViewController { + Class[] value(); +} 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/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 new file mode 100644 index 0000000..2e275b4 --- /dev/null +++ b/api/src/main/java/org/jboss/seam/faces/view/config/ViewConfigDescriptor.java @@ -0,0 +1,189 @@ +package org.jboss.seam.faces.view.config; + +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; + +/** + * 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>(); + private List viewActionHandlers = new ArrayList(); + private Map> viewActionHandlersPerPhaseInstant = new HashMap>(); + + /** + * 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); + } + } + + 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. + * + * 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; + } + + public String toString() { + 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 da7b462..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 @@ -56,4 +56,22 @@ public interface ViewConfigStore { */ public Map getAllAnnotationViewMap(Class type); + /** + * return the registered viewConfigs + */ + 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/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/MyAppViewConfig.java b/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/MyAppViewConfig.java index f74ada0..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,6 +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.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; @@ -39,9 +41,21 @@ static enum Pages { @UrlMapping(pattern = "/item/#{id}/") @ViewPattern("/item.xhtml") + @ViewController(PageController.class) @Owner 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") + @ElViewAction("#{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 new file mode 100644 index 0000000..9bceb39 --- /dev/null +++ b/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/MyViewAction.java @@ -0,0 +1,15 @@ +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.binding.ViewActionBindingType; + +@ViewActionBindingType +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE}) +public @interface MyViewAction { + MyAppViewConfig.Pages value(); +} 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..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,6 +25,9 @@ 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; @@ -53,6 +56,21 @@ public Item getItem() { return item; } + @Before + @RenderResponse + 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 viewActionBindingType(@Current Item 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/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..bb7bc22 --- /dev/null +++ b/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/ViewController.java @@ -0,0 +1,80 @@ +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; + +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"); + } + + @Before + @RenderResponse + 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/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/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 new file mode 100644 index 0000000..6604289 --- /dev/null +++ b/impl/src/main/java/org/jboss/seam/faces/view/action/ViewActionPhaseListener.java @@ -0,0 +1,47 @@ +package org.jboss.seam.faces.view.action; + +import javax.enterprise.event.Observes; +import javax.faces.event.PhaseEvent; +import javax.faces.event.PhaseId; +import javax.inject.Inject; + +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; + +/** + * 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; + + 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; + } + ViewConfigDescriptor viewDescriptor = viewConfigStore.getRuntimeViewConfigDescriptor(event.getFacesContext() + .getViewRoot().getViewId()); + viewDescriptor.executeBeforePhase(event.getPhaseId()); + } + + public void observerAfterPhase(@Observes @After PhaseEvent event) { + PhaseId phaseId = event.getPhaseId(); + log.debugf("After {1} event", phaseId); + ViewConfigDescriptor viewDescriptor = viewConfigStore.getRuntimeViewConfigDescriptor(event.getFacesContext() + .getViewRoot().getViewId()); + viewDescriptor.executeAfterPhase(event.getPhaseId()); + } +} 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/ViewConfigExtension.java b/impl/src/main/java/org/jboss/seam/faces/view/config/ViewConfigExtension.java index 8d9cc59..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 @@ -20,9 +20,7 @@ import java.lang.reflect.Field; 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.AnnotatedType; @@ -33,7 +31,7 @@ /** * Extension that scans enums for view specific configuration - * + * * @author stuart * @author Brian Leathem */ @@ -41,7 +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 data = new HashMap(); public void processAnnotatedType(@Observes ProcessAnnotatedType event) { AnnotatedType tp = event.getAnnotatedType(); @@ -59,15 +57,19 @@ 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(); - data.put(viewConfig.value(), viewPattern); + 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); } } } @@ -76,8 +78,22 @@ public void processAnnotatedType(@Observes ProcessAnnotatedType event) { } } - public Map> getData() { - return Collections.unmodifiableMap(data); + /** + * 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); + } } 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..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 @@ -22,31 +22,33 @@ 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; 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 runtimeViewConfigDescriptor = new ConcurrentHashMap(); + private final ConcurrentHashMap, ConcurrentHashMap> viewPatternDataByAnnotation = new ConcurrentHashMap, ConcurrentHashMap>(); private final ConcurrentHashMap, ConcurrentHashMap>> viewPatternDataByQualifier = new ConcurrentHashMap, ConcurrentHashMap>>(); @@ -58,15 +60,17 @@ 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); } } } @Override public synchronized void addAnnotationData(String viewId, Annotation annotation) { + runtimeViewConfigDescriptor.clear(); ConcurrentHashMap annotationMap = viewPatternDataByAnnotation.get(annotation.annotationType()); if (annotationMap == null) { annotationMap = new ConcurrentHashMap(); @@ -95,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); } } @@ -132,9 +137,50 @@ public List getAllQualifierData(String viewId, Class 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 f37ca0c..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,5 +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.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/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 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/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..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 @@ -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.getViewConfigDescriptors()) { + 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/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/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/binding/AfterInvokeApplicationViewAction.java b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/binding/AfterInvokeApplicationViewAction.java new file mode 100644 index 0000000..1f96feb --- /dev/null +++ b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/binding/AfterInvokeApplicationViewAction.java @@ -0,0 +1,19 @@ +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.binding.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/binding/BeforeRenderResponseViewAction.java b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/binding/BeforeRenderResponseViewAction.java new file mode 100644 index 0000000..74749ef --- /dev/null +++ b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/binding/BeforeRenderResponseViewAction.java @@ -0,0 +1,15 @@ +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.binding.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/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/controller/ViewConfigEnum.java b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/controller/ViewConfigEnum.java new file mode 100644 index 0000000..af04ed7 --- /dev/null +++ b/testsuite/src/test/java/org/jboss/seam/faces/test/weld/view/action/controller/ViewConfigEnum.java @@ -0,0 +1,45 @@ +/* + * 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.controller; + +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; + +@ViewConfig +public interface ViewConfigEnum { + static enum Pages { + @ViewPattern("/*") + DEFAULT, + + @ViewPattern("/client/*") + CLIENTS, + + @ViewPattern("/country/*") + @ViewController(CountryController.class) + COUNTRIES(), + + @ViewPattern("/client/done.xhtml") + @ViewController(ClientController.class) + CLIENT_CONFIRMED(), + + @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); + } + } +}