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 extends ViewActionHandlerProvider extends Annotation>> 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) :
+ *