diff --git a/core/src/main/java/org/apache/struts/action2/jsf/ApplyRequestValuesInterceptor.java b/core/src/main/java/org/apache/struts/action2/jsf/ApplyRequestValuesInterceptor.java
new file mode 100644
index 000000000..13a936e05
--- /dev/null
+++ b/core/src/main/java/org/apache/struts/action2/jsf/ApplyRequestValuesInterceptor.java
@@ -0,0 +1,70 @@
+/*
+ * $Id: TokenInterceptor.java 394468 2006-04-16 12:16:03Z tmjee $
+ *
+ * Copyright 2006 The Apache Software Foundation.
+ *
+ * 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.apache.struts.action2.jsf;
+
+import javax.faces.FacesException;
+import javax.faces.context.FacesContext;
+import javax.faces.event.PhaseId;
+
+/**
+ * Applies the request values to the component tree
+ */
+public class ApplyRequestValuesInterceptor extends FacesInterceptor {
+
+ private static final long serialVersionUID = -1471180154211835323L;
+
+ /**
+ * Apply Request Values (JSF.2.2.2)
+ *
+ * @param viewId The view id
+ * @param facesContext The faces context
+ * @return true, if response is complete
+ */
+ protected boolean executePhase(String viewId, FacesContext facesContext)
+ throws FacesException {
+ boolean skipFurtherProcessing = false;
+ if (log.isTraceEnabled())
+ log.trace("entering applyRequestValues");
+
+ informPhaseListenersBefore(facesContext, PhaseId.APPLY_REQUEST_VALUES);
+
+ if (isResponseComplete(facesContext, "applyRequestValues", true)) {
+ // have to return right away
+ return true;
+ }
+ if (shouldRenderResponse(facesContext, "applyRequestValues", true)) {
+ skipFurtherProcessing = true;
+ }
+
+ facesContext.getViewRoot().processDecodes(facesContext);
+
+ informPhaseListenersAfter(facesContext, PhaseId.APPLY_REQUEST_VALUES);
+
+ if (isResponseComplete(facesContext, "applyRequestValues", false)
+ || shouldRenderResponse(facesContext, "applyRequestValues",
+ false)) {
+ // since this phase is completed we don't need to return right away
+ // even if the response is completed
+ skipFurtherProcessing = true;
+ }
+
+ if (!skipFurtherProcessing && log.isTraceEnabled())
+ log.trace("exiting applyRequestValues");
+ return skipFurtherProcessing;
+ }
+}
diff --git a/core/src/main/java/org/apache/struts/action2/jsf/FacesInterceptor.java b/core/src/main/java/org/apache/struts/action2/jsf/FacesInterceptor.java
new file mode 100644
index 000000000..c9f1863c6
--- /dev/null
+++ b/core/src/main/java/org/apache/struts/action2/jsf/FacesInterceptor.java
@@ -0,0 +1,90 @@
+/*
+ * $Id: TokenInterceptor.java 394468 2006-04-16 12:16:03Z tmjee $
+ *
+ * Copyright 2006 The Apache Software Foundation.
+ *
+ * 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.apache.struts.action2.jsf;
+
+import javax.faces.context.FacesContext;
+
+import com.opensymphony.xwork.ActionInvocation;
+import com.opensymphony.xwork.interceptor.Interceptor;
+
+/**
+ * Translates JSF phases into individual interceptors, and adapts their expected
+ * workflow to Action 2
+ */
+public class FacesInterceptor extends FacesSupport implements Interceptor {
+
+ private static final long serialVersionUID = -5418255964277566516L;
+
+ /**
+ * Not used
+ */
+ public void init() {
+ }
+
+ /**
+ * Adapts the phase workflow to Action 2
+ *
+ * @param invocation
+ * The action invocation
+ * @return The string result code
+ */
+ public String intercept(ActionInvocation invocation) throws Exception {
+
+ FacesContext context = FacesContext.getCurrentInstance();
+
+ if (context.getRenderResponse()) {
+ return invocation.invoke();
+ } else {
+ String viewId = invocation.getProxy().getNamespace() + '/'
+ + invocation.getProxy().getActionName();
+ executePhase(viewId, context);
+
+ if (context.getResponseComplete()) {
+ // Abort the chain as the result is done
+ return null;
+ } else {
+ if (invocation.getResultCode() != null) {
+ return invocation.getResultCode();
+ } else {
+ return invocation.invoke();
+ }
+ }
+ }
+ }
+
+ /**
+ * Executes the specific phase. The phase id is constructed as a composite
+ * of the namespace and action name.
+ *
+ * @param viewId
+ * The view id
+ * @param facesContext
+ * The current faces context
+ * @return True if the next phases should be skipped
+ */
+ protected boolean executePhase(String viewId, FacesContext facesContext) {
+ return false;
+ }
+
+ /**
+ * Not used
+ */
+ public void destroy() {
+ }
+
+}
\ No newline at end of file
diff --git a/core/src/main/java/org/apache/struts/action2/jsf/FacesResult.java b/core/src/main/java/org/apache/struts/action2/jsf/FacesResult.java
new file mode 100644
index 000000000..41ff99a35
--- /dev/null
+++ b/core/src/main/java/org/apache/struts/action2/jsf/FacesResult.java
@@ -0,0 +1,85 @@
+/*
+ * $Id: PlainTextResult.java 394468 2006-04-16 12:16:03Z tmjee $
+ *
+ * Copyright 2006 The Apache Software Foundation.
+ *
+ * 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.apache.struts.action2.jsf;
+
+import java.io.IOException;
+
+import javax.faces.FacesException;
+import javax.faces.application.Application;
+import javax.faces.application.ViewHandler;
+import javax.faces.context.FacesContext;
+import javax.faces.event.PhaseId;
+
+import com.opensymphony.xwork.ActionInvocation;
+import com.opensymphony.xwork.Result;
+
+/**
+ * Executes the JSF render phase
+ */
+public class FacesResult extends FacesSupport implements Result {
+
+ private static final long serialVersionUID = -3548970638740937804L;
+
+ /**
+ * Executes the result
+ */
+ public void execute(ActionInvocation invocation) throws Exception {
+ render(FacesContext.getCurrentInstance());
+ }
+
+ /**
+ * Executes the render phase, borrowed from MyFaces
+ *
+ * @param facesContext
+ * The faces context
+ * @throws FacesException
+ * If anything goes wrong
+ */
+ public void render(FacesContext facesContext) throws FacesException {
+ // if the response is complete we should not be invoking the phase
+ // listeners
+ if (isResponseComplete(facesContext, "render", true)) {
+ return;
+ }
+ if (log.isTraceEnabled())
+ log.trace("entering renderResponse");
+
+ informPhaseListenersBefore(facesContext, PhaseId.RENDER_RESPONSE);
+ // also possible that one of the listeners completed the response
+ if (isResponseComplete(facesContext, "render", true)) {
+ return;
+ }
+ Application application = facesContext.getApplication();
+ ViewHandler viewHandler = application.getViewHandler();
+ try {
+ viewHandler.renderView(facesContext, facesContext.getViewRoot());
+ } catch (IOException e) {
+ throw new FacesException(e.getMessage(), e);
+ }
+
+ informPhaseListenersAfter(facesContext, PhaseId.RENDER_RESPONSE);
+ if (log.isTraceEnabled()) {
+ // Note: DebugUtils Logger must also be in trace level
+ // DebugUtils.traceView("View after rendering");
+ }
+
+ if (log.isTraceEnabled())
+ log.trace("exiting renderResponse");
+ }
+
+}
diff --git a/core/src/main/java/org/apache/struts/action2/jsf/FacesSetupInterceptor.java b/core/src/main/java/org/apache/struts/action2/jsf/FacesSetupInterceptor.java
new file mode 100644
index 000000000..f5771191b
--- /dev/null
+++ b/core/src/main/java/org/apache/struts/action2/jsf/FacesSetupInterceptor.java
@@ -0,0 +1,110 @@
+/*
+ * $Id: TokenInterceptor.java 394468 2006-04-16 12:16:03Z tmjee $
+ *
+ * Copyright 2006 The Apache Software Foundation.
+ *
+ * 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.apache.struts.action2.jsf;
+
+import javax.faces.FactoryFinder;
+import javax.faces.application.Application;
+import javax.faces.application.ApplicationFactory;
+import javax.faces.context.FacesContext;
+import javax.faces.context.FacesContextFactory;
+import javax.faces.lifecycle.Lifecycle;
+import javax.faces.lifecycle.LifecycleFactory;
+
+import org.apache.struts.action2.ServletActionContext;
+
+import com.opensymphony.xwork.ActionInvocation;
+import com.opensymphony.xwork.interceptor.Interceptor;
+
+/**
+ * Initializes the JSF context for this request
+ */
+public class FacesSetupInterceptor extends FacesSupport implements Interceptor {
+
+ private static final long serialVersionUID = -621512342655103941L;
+
+ private String lifecycleId = LifecycleFactory.DEFAULT_LIFECYCLE;
+
+ private FacesContextFactory facesContextFactory;
+
+ private Lifecycle lifecycle;
+
+ /**
+ * Sets the lifecycle id
+ *
+ * @param id
+ * The id
+ */
+ public void setLifecycleId(String id) {
+ this.lifecycleId = id;
+ }
+
+ /**
+ * Initializes the lifecycle and factories
+ */
+ public void init() {
+ facesContextFactory = (FacesContextFactory) FactoryFinder
+ .getFactory(FactoryFinder.FACES_CONTEXT_FACTORY);
+ // Javadoc says: Lifecycle instance is shared across multiple
+ // simultaneous requests, it must be implemented in a thread-safe
+ // manner.
+ // So we can acquire it here once:
+ LifecycleFactory lifecycleFactory = (LifecycleFactory) FactoryFinder
+ .getFactory(FactoryFinder.LIFECYCLE_FACTORY);
+ lifecycle = lifecycleFactory.getLifecycle(lifecycleId);
+
+ Application application = ((ApplicationFactory) FactoryFinder
+ .getFactory(FactoryFinder.APPLICATION_FACTORY))
+ .getApplication();
+ if (!(application.getNavigationHandler() instanceof StrutsNavigationHandler)) {
+ application.setNavigationHandler(new StrutsNavigationHandler());
+ }
+ if (!(application.getVariableResolver() instanceof StrutsVariableResolver)) {
+ application.setVariableResolver(new StrutsVariableResolver(
+ application.getVariableResolver()));
+ }
+ }
+
+ /**
+ * Creates the faces context for other phases.
+ *
+ * @param invocation
+ * The action invocation
+ */
+ public String intercept(ActionInvocation invocation) throws Exception {
+ FacesContext facesContext = facesContextFactory.getFacesContext(
+ ServletActionContext.getServletContext(), ServletActionContext
+ .getRequest(), ServletActionContext.getResponse(),
+ lifecycle);
+
+ setLifecycle(lifecycle);
+
+ try {
+ return invocation.invoke();
+ } finally {
+ facesContext.release();
+ }
+ }
+
+ /**
+ * Cleans up the lifecycle and factories
+ */
+ public void destroy() {
+ facesContextFactory = null;
+ lifecycle = null;
+ }
+}
diff --git a/core/src/main/java/org/apache/struts/action2/jsf/FacesSupport.java b/core/src/main/java/org/apache/struts/action2/jsf/FacesSupport.java
new file mode 100644
index 000000000..842b88617
--- /dev/null
+++ b/core/src/main/java/org/apache/struts/action2/jsf/FacesSupport.java
@@ -0,0 +1,164 @@
+/*
+ * $Id: TokenInterceptor.java 394468 2006-04-16 12:16:03Z tmjee $
+ *
+ * Copyright 2006 The Apache Software Foundation.
+ *
+ * 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.apache.struts.action2.jsf;
+
+import javax.faces.context.FacesContext;
+import javax.faces.event.PhaseEvent;
+import javax.faces.event.PhaseId;
+import javax.faces.event.PhaseListener;
+import javax.faces.lifecycle.Lifecycle;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import com.opensymphony.xwork.ActionContext;
+
+/**
+ * Provides common lifecycle phase methods needed by interceptors and results.
+ */
+public class FacesSupport {
+
+ private static final String LIFECYCLE_KEY = "lifecycle";
+
+ /** Log instance for each class */
+ protected Log log;
+
+ /** Initializes log instance for the instance object */
+ protected FacesSupport() {
+ log = LogFactory.getLog(getClass());
+ }
+
+ /**
+ * Gets the shared lifecycle for this request
+ *
+ * @return The lifecycle
+ */
+ private Lifecycle getLifecycle() {
+ return (Lifecycle) ActionContext.getContext().get(LIFECYCLE_KEY);
+ }
+
+ /**
+ * Sets the lifecycle for this request
+ *
+ * @param lifecycle
+ * The lifecycle
+ */
+ protected void setLifecycle(Lifecycle lifecycle) {
+ ActionContext.getContext().put(LIFECYCLE_KEY, lifecycle);
+ }
+
+ /**
+ * Informs phase listeners before a phase is executed
+ *
+ * @param facesContext
+ * The current faces context
+ * @param phaseId
+ * The phase id about to be executed
+ */
+ protected void informPhaseListenersBefore(FacesContext facesContext,
+ PhaseId phaseId) {
+ Lifecycle lifecycle = getLifecycle();
+ PhaseListener[] phaseListeners = lifecycle.getPhaseListeners();
+ for (int i = 0; i < phaseListeners.length; i++) {
+ PhaseListener phaseListener = phaseListeners[i];
+ int listenerPhaseId = phaseListener.getPhaseId().getOrdinal();
+ if (listenerPhaseId == PhaseId.ANY_PHASE.getOrdinal()
+ || listenerPhaseId == phaseId.getOrdinal()) {
+ phaseListener.beforePhase(new PhaseEvent(FacesContext
+ .getCurrentInstance(), phaseId, lifecycle));
+ }
+ }
+
+ }
+
+ /**
+ * Informs phase listeners after a phase is executed
+ *
+ * @param facesContext
+ * The current faces context
+ * @param phaseId
+ * The phase id that was executed
+ */
+ protected void informPhaseListenersAfter(FacesContext facesContext,
+ PhaseId phaseId) {
+ Lifecycle lifecycle = getLifecycle();
+ PhaseListener[] phaseListeners = lifecycle.getPhaseListeners();
+ for (int i = 0; i < phaseListeners.length; i++) {
+ PhaseListener phaseListener = phaseListeners[i];
+ int listenerPhaseId = phaseListener.getPhaseId().getOrdinal();
+ if (listenerPhaseId == PhaseId.ANY_PHASE.getOrdinal()
+ || listenerPhaseId == phaseId.getOrdinal()) {
+ phaseListener.afterPhase(new PhaseEvent(FacesContext
+ .getCurrentInstance(), phaseId, lifecycle));
+ }
+ }
+
+ }
+
+ /**
+ * Checks to see if the response has been completed. Mainly used for better
+ * debugging messages.
+ *
+ * @param facesContext
+ * The current faces context
+ * @param phase
+ * The phase id in execution
+ * @param before
+ * Whether the phase has been executed or not
+ * @return True if the response is complete
+ */
+ protected boolean isResponseComplete(FacesContext facesContext,
+ String phase, boolean before) {
+ boolean flag = false;
+ if (facesContext.getResponseComplete()) {
+ if (log.isDebugEnabled())
+ log
+ .debug("exiting from lifecycle.execute in "
+ + phase
+ + " because getResponseComplete is true from one of the "
+ + (before ? "before" : "after") + " listeners");
+ flag = true;
+ }
+ return flag;
+ }
+
+ /**
+ * Checks to see the render phase should be executed next. Mainly used for
+ * better debugging messages.
+ *
+ * @param facesContext
+ * The current faces context
+ * @param phase
+ * The phase id in execution
+ * @param before
+ * Whether the phase has been executed or not
+ * @return True if the response is complete
+ */
+ protected boolean shouldRenderResponse(FacesContext facesContext,
+ String phase, boolean before) {
+ boolean flag = false;
+ if (facesContext.getRenderResponse()) {
+ if (log.isDebugEnabled())
+ log.debug("exiting from lifecycle.execute in " + phase
+ + " because getRenderResponse is true from one of the "
+ + (before ? "before" : "after") + " listeners");
+ flag = true;
+ }
+ return flag;
+ }
+}
diff --git a/core/src/main/java/org/apache/struts/action2/jsf/InvokeApplicationInterceptor.java b/core/src/main/java/org/apache/struts/action2/jsf/InvokeApplicationInterceptor.java
new file mode 100644
index 000000000..732742bf0
--- /dev/null
+++ b/core/src/main/java/org/apache/struts/action2/jsf/InvokeApplicationInterceptor.java
@@ -0,0 +1,73 @@
+/*
+ * $Id: TokenInterceptor.java 394468 2006-04-16 12:16:03Z tmjee $
+ *
+ * Copyright 2006 The Apache Software Foundation.
+ *
+ * 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.apache.struts.action2.jsf;
+
+import javax.faces.FacesException;
+import javax.faces.context.FacesContext;
+import javax.faces.event.PhaseId;
+
+/**
+ * Invokes the application
+ */
+public class InvokeApplicationInterceptor extends FacesInterceptor {
+
+ private static final long serialVersionUID = -7388153356410171208L;
+
+ /**
+ * Invoke Application (JSF.2.2.5)
+ *
+ * @param viewId
+ * The view id
+ * @param facesContext
+ * The faces context
+ * @return true, if response is complete
+ */
+ protected boolean executePhase(String viewId, FacesContext facesContext)
+ throws FacesException {
+ boolean skipFurtherProcessing = false;
+ if (log.isTraceEnabled())
+ log.trace("entering invokeApplication");
+
+ informPhaseListenersBefore(facesContext, PhaseId.INVOKE_APPLICATION);
+
+ if (isResponseComplete(facesContext, "invokeApplication", true)) {
+ // have to return right away
+ return true;
+ }
+ if (shouldRenderResponse(facesContext, "invokeApplication", true)) {
+ skipFurtherProcessing = true;
+ }
+
+ facesContext.getViewRoot().processApplication(facesContext);
+
+ informPhaseListenersAfter(facesContext, PhaseId.INVOKE_APPLICATION);
+
+ if (isResponseComplete(facesContext, "invokeApplication", false)
+ || shouldRenderResponse(facesContext, "invokeApplication",
+ false)) {
+ // since this phase is completed we don't need to return right away
+ // even if the response is completed
+ skipFurtherProcessing = true;
+ }
+
+ if (!skipFurtherProcessing && log.isTraceEnabled())
+ log.trace("exiting invokeApplication ");
+
+ return skipFurtherProcessing;
+ }
+}
diff --git a/core/src/main/java/org/apache/struts/action2/jsf/ProcessValidationsInterceptor.java b/core/src/main/java/org/apache/struts/action2/jsf/ProcessValidationsInterceptor.java
new file mode 100644
index 000000000..fb5066242
--- /dev/null
+++ b/core/src/main/java/org/apache/struts/action2/jsf/ProcessValidationsInterceptor.java
@@ -0,0 +1,72 @@
+/*
+ * $Id: TokenInterceptor.java 394468 2006-04-16 12:16:03Z tmjee $
+ *
+ * Copyright 2006 The Apache Software Foundation.
+ *
+ * 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.apache.struts.action2.jsf;
+
+import javax.faces.FacesException;
+import javax.faces.context.FacesContext;
+import javax.faces.event.PhaseId;
+
+/**
+ * Processes validations on the component tree
+ */
+public class ProcessValidationsInterceptor extends FacesInterceptor {
+
+ private static final long serialVersionUID = 8785236570688278147L;
+
+ /**
+ * Process Validations (JSF.2.2.3)
+ *
+ * @param viewId
+ * The view id
+ * @param facesContext
+ * The faces context
+ * @return true, if response is complete
+ */
+ protected boolean executePhase(String viewId, FacesContext facesContext)
+ throws FacesException {
+ boolean skipFurtherProcessing = false;
+ if (log.isTraceEnabled())
+ log.trace("entering processValidations");
+
+ informPhaseListenersBefore(facesContext, PhaseId.PROCESS_VALIDATIONS);
+
+ if (isResponseComplete(facesContext, "processValidations", true)) {
+ // have to return right away
+ return true;
+ }
+ if (shouldRenderResponse(facesContext, "processValidations", true)) {
+ skipFurtherProcessing = true;
+ }
+
+ facesContext.getViewRoot().processValidators(facesContext);
+
+ informPhaseListenersAfter(facesContext, PhaseId.PROCESS_VALIDATIONS);
+
+ if (isResponseComplete(facesContext, "processValidations", false)
+ || shouldRenderResponse(facesContext, "processValidations",
+ false)) {
+ // since this phase is completed we don't need to return right away
+ // even if the response is completed
+ skipFurtherProcessing = true;
+ }
+
+ if (!skipFurtherProcessing && log.isTraceEnabled())
+ log.trace("exiting processValidations");
+ return skipFurtherProcessing;
+ }
+}
diff --git a/core/src/main/java/org/apache/struts/action2/jsf/RestoreViewInterceptor.java b/core/src/main/java/org/apache/struts/action2/jsf/RestoreViewInterceptor.java
new file mode 100644
index 000000000..e965438d5
--- /dev/null
+++ b/core/src/main/java/org/apache/struts/action2/jsf/RestoreViewInterceptor.java
@@ -0,0 +1,221 @@
+/*
+ * $Id: TokenInterceptor.java 394468 2006-04-16 12:16:03Z tmjee $
+ *
+ * Copyright 2006 The Apache Software Foundation.
+ *
+ * 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.apache.struts.action2.jsf;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.util.Iterator;
+
+import javax.faces.FacesException;
+import javax.faces.application.Application;
+import javax.faces.application.ViewHandler;
+import javax.faces.component.UIComponent;
+import javax.faces.component.UIInput;
+import javax.faces.component.UIViewRoot;
+import javax.faces.context.ExternalContext;
+import javax.faces.context.FacesContext;
+import javax.faces.el.ValueBinding;
+import javax.faces.event.PhaseId;
+
+/**
+ * Restores the view or component tree
+ */
+public class RestoreViewInterceptor extends FacesInterceptor {
+
+ private static final long serialVersionUID = -1500785113037140668L;
+
+ /**
+ * Restore View (JSF.2.2.1)
+ *
+ * @param viewId
+ * The view id
+ * @param facesContext
+ * The faces context
+ * @return true, if immediate rendering should occur
+ */
+ protected boolean executePhase(String viewId, FacesContext facesContext) {
+ boolean skipFurtherProcessing = false;
+ if (log.isTraceEnabled())
+ log.trace("entering restoreView");
+
+ informPhaseListenersBefore(facesContext, PhaseId.RESTORE_VIEW);
+
+ if (isResponseComplete(facesContext, "restoreView", true)) {
+ // have to skips this phase
+ return true;
+ }
+ if (shouldRenderResponse(facesContext, "restoreView", true)) {
+ skipFurtherProcessing = true;
+ }
+
+ ExternalContext externalContext = facesContext.getExternalContext();
+ String defaultSuffix = externalContext
+ .getInitParameter(ViewHandler.DEFAULT_SUFFIX_PARAM_NAME);
+ String suffix = defaultSuffix != null ? defaultSuffix
+ : ViewHandler.DEFAULT_SUFFIX;
+ if (viewId != null) {
+ viewId += suffix;
+ }
+
+ if (viewId == null) {
+ if (!externalContext.getRequestServletPath().endsWith("/")) {
+ try {
+ externalContext.redirect(externalContext
+ .getRequestServletPath()
+ + "/");
+ facesContext.responseComplete();
+ return true;
+ } catch (IOException e) {
+ throw new FacesException("redirect failed", e);
+ }
+ }
+ }
+
+ Application application = facesContext.getApplication();
+ ViewHandler viewHandler = application.getViewHandler();
+
+ // boolean viewCreated = false;
+ UIViewRoot viewRoot = viewHandler.restoreView(facesContext, viewId);
+ if (viewRoot == null) {
+ viewRoot = viewHandler.createView(facesContext, viewId);
+ viewRoot.setViewId(viewId);
+ facesContext.renderResponse();
+ // viewCreated = true;
+ }
+
+ facesContext.setViewRoot(viewRoot);
+
+ /*
+ * This section has been disabled because it causes some bug. Be careful
+ * if you need to re-enable it. Furthermore, for an unknown reason, it
+ * seems that by default it is executed (i.e. log.isTraceEnabled() is
+ * true). Bug example : This traceView causes DebugUtil.printComponent
+ * to print all the attributes of the view components. And if you have a
+ * data table within an aliasBean, this causes the data table to
+ * initialize it's value attribute while the alias isn't set. So, the
+ * value initializes with an UIData.EMPTY_DATA_MODEL, and not with the
+ * aliased one. But as it's initialized, it will not try to get the
+ * value from the ValueBinding next time it needs to. I expect this to
+ * cause more similar bugs. TODO : Completely remove or be SURE by
+ * default it's not executed, and it has no more side-effects.
+ *
+ * if (log.isTraceEnabled()) { //Note: DebugUtils Logger must also be in
+ * trace level DebugUtils.traceView(viewCreated ? "Newly created view" :
+ * "Restored view"); }
+ */
+
+ if (facesContext.getExternalContext().getRequestParameterMap()
+ .isEmpty()) {
+ // no POST or query parameters --> set render response flag
+ facesContext.renderResponse();
+ }
+
+ recursivelyHandleComponentReferencesAndSetValid(facesContext, viewRoot);
+
+ informPhaseListenersAfter(facesContext, PhaseId.RESTORE_VIEW);
+
+ if (isResponseComplete(facesContext, "restoreView", false)
+ || shouldRenderResponse(facesContext, "restoreView", false)) {
+ // since this phase is completed we don't need to return right away
+ // even if the response is completed
+ skipFurtherProcessing = true;
+ }
+
+ if (!skipFurtherProcessing && log.isTraceEnabled())
+ log.trace("exiting restoreView ");
+ return skipFurtherProcessing;
+ }
+
+ /**
+ * Walk the component tree, executing any component-bindings to reattach
+ * components to their backing beans. Also, any UIInput component is marked
+ * as Valid.
+ *
+ * Note that this method effectively breaks encapsulation; instead of asking
+ * each component to update itself and its children, this method just
+ * reaches into each component. That makes it impossible for any component
+ * to customise its behaviour at this point.
+ *
+ * This has been filed as an issue against the spec. Until this issue is
+ * resolved, we'll add a new marker-interface for components to allow them
+ * to define their interest in handling children bindings themselves.
+ */
+ protected void recursivelyHandleComponentReferencesAndSetValid(
+ FacesContext facesContext, UIComponent parent) {
+ recursivelyHandleComponentReferencesAndSetValid(facesContext, parent,
+ false);
+ }
+
+ protected void recursivelyHandleComponentReferencesAndSetValid(
+ FacesContext facesContext, UIComponent parent, boolean forceHandle) {
+ Method handleBindingsMethod = getBindingMethod(parent);
+
+ if (handleBindingsMethod != null && !forceHandle) {
+ try {
+ handleBindingsMethod.invoke(parent, new Object[] {});
+ } catch (Throwable th) {
+ log.error(
+ "Exception while invoking handleBindings on component with client-id:"
+ + parent.getClientId(facesContext), th);
+ }
+ } else {
+ for (Iterator it = parent.getFacetsAndChildren(); it.hasNext();) {
+ UIComponent component = (UIComponent) it.next();
+
+ ValueBinding binding = component.getValueBinding("binding"); // TODO:
+ // constant
+ if (binding != null && !binding.isReadOnly(facesContext)) {
+ binding.setValue(facesContext, component);
+ }
+
+ if (component instanceof UIInput) {
+ ((UIInput) component).setValid(true);
+ }
+
+ recursivelyHandleComponentReferencesAndSetValid(facesContext,
+ component);
+ }
+ }
+ }
+
+ /**
+ * This is all a hack to work around a spec-bug which will be fixed in
+ * JSF2.0
+ *
+ * @param parent
+ * @return true if this component is bindingAware (e.g. aliasBean)
+ */
+ private static Method getBindingMethod(UIComponent parent) {
+ Class[] clazzes = parent.getClass().getInterfaces();
+
+ for (int i = 0; i < clazzes.length; i++) {
+ Class clazz = clazzes[i];
+
+ if (clazz.getName().indexOf("BindingAware") != -1) {
+ try {
+ return parent.getClass().getMethod("handleBindings",
+ new Class[] {});
+ } catch (NoSuchMethodException e) {
+ // return
+ }
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/core/src/main/java/org/apache/struts/action2/jsf/StrutsNavigationHandler.java b/core/src/main/java/org/apache/struts/action2/jsf/StrutsNavigationHandler.java
new file mode 100644
index 000000000..6a8196619
--- /dev/null
+++ b/core/src/main/java/org/apache/struts/action2/jsf/StrutsNavigationHandler.java
@@ -0,0 +1,46 @@
+/*
+ * $Id: TokenInterceptor.java 394468 2006-04-16 12:16:03Z tmjee $
+ *
+ * Copyright 2006 The Apache Software Foundation.
+ *
+ * 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.apache.struts.action2.jsf;
+
+import javax.faces.application.NavigationHandler;
+import javax.faces.context.FacesContext;
+
+import com.opensymphony.xwork.ActionContext;
+
+/**
+ * Overrides the JFS navigation by delegating the result to handling by the core
+ * result code lookup and execution.
+ */
+public class StrutsNavigationHandler extends NavigationHandler {
+
+ /**
+ * Stores any outcomes as the result code
+ *
+ * @param facesContext The faces context
+ * @param fromAction The action we are coming from
+ * @param outcome The String return code
+ */
+ @Override
+ public void handleNavigation(FacesContext facesContext, String fromAction, String outcome) {
+ ActionContext ctx = ActionContext.getContext();
+ if (outcome != null) {
+ ctx.getActionInvocation().setResultCode(outcome);
+ }
+ }
+
+}
diff --git a/core/src/main/java/org/apache/struts/action2/jsf/StrutsVariableResolver.java b/core/src/main/java/org/apache/struts/action2/jsf/StrutsVariableResolver.java
new file mode 100644
index 000000000..7b9dcf7bd
--- /dev/null
+++ b/core/src/main/java/org/apache/struts/action2/jsf/StrutsVariableResolver.java
@@ -0,0 +1,70 @@
+/*
+ * $Id: TokenInterceptor.java 394468 2006-04-16 12:16:03Z tmjee $
+ *
+ * Copyright 2006 The Apache Software Foundation.
+ *
+ * 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.apache.struts.action2.jsf;
+
+import javax.faces.context.FacesContext;
+import javax.faces.el.EvaluationException;
+import javax.faces.el.VariableResolver;
+
+import com.opensymphony.xwork.ActionContext;
+
+/**
+ * Adds the current Action instance to the variable lookups. All other requests
+ * delegate to underlying resolver.
+ */
+public class StrutsVariableResolver extends VariableResolver {
+
+ /** The original VariableResolver passed to our constructor. */
+ private VariableResolver original = null;
+
+ /** The variable name of our Struts action */
+ private static final String STRUTS_VARIABLE_NAME = "action";
+
+ /**
+ * Constructor
+ *
+ * @param original
+ * Original resolver to delegate to.
+ */
+ public StrutsVariableResolver(VariableResolver original) {
+
+ this.original = original;
+
+ }
+
+ /**
+ *
+ * Resolve variable names known to this resolver; otherwise, delegate to the
+ * original resolver passed to our constructor.
+ *
+ *
+ * @param name
+ * Variable name to be resolved
+ */
+ public Object resolveVariable(FacesContext context, String name)
+ throws EvaluationException {
+
+ if (STRUTS_VARIABLE_NAME.equals(name)) {
+ return ActionContext.getContext().getActionInvocation().getAction();
+ } else {
+ return original.resolveVariable(context, name);
+ }
+
+ }
+
+}
diff --git a/core/src/main/java/org/apache/struts/action2/jsf/UpdateModelValuesInterceptor.java b/core/src/main/java/org/apache/struts/action2/jsf/UpdateModelValuesInterceptor.java
new file mode 100644
index 000000000..537cb580f
--- /dev/null
+++ b/core/src/main/java/org/apache/struts/action2/jsf/UpdateModelValuesInterceptor.java
@@ -0,0 +1,70 @@
+/*
+ * $Id: TokenInterceptor.java 394468 2006-04-16 12:16:03Z tmjee $
+ *
+ * Copyright 2006 The Apache Software Foundation.
+ *
+ * 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.apache.struts.action2.jsf;
+
+import javax.faces.FacesException;
+import javax.faces.context.FacesContext;
+import javax.faces.event.PhaseId;
+
+/**
+ * Updates the model values from the component tree
+ */
+public class UpdateModelValuesInterceptor extends FacesInterceptor {
+
+ private static final long serialVersionUID = 4011504235094251077L;
+
+ /**
+ * Update Model Values (JSF.2.2.4)
+ *
+ * @param viewId The view id
+ * @param facesContext The faces context
+ * @return true, if response is complete
+ */
+ protected boolean executePhase(String viewId, FacesContext facesContext) throws FacesException
+ {
+ boolean skipFurtherProcessing = false;
+ if (log.isTraceEnabled()) log.trace("entering updateModelValues");
+
+ informPhaseListenersBefore(facesContext, PhaseId.UPDATE_MODEL_VALUES);
+
+ if(isResponseComplete(facesContext, "updateModelValues", true))
+ {
+ // have to return right away
+ return true;
+ }
+ if(shouldRenderResponse(facesContext, "updateModelValues", true))
+ {
+ skipFurtherProcessing = true;
+ }
+
+ facesContext.getViewRoot().processUpdates(facesContext);
+
+ informPhaseListenersAfter(facesContext, PhaseId.UPDATE_MODEL_VALUES);
+
+ if (isResponseComplete(facesContext, "updateModelValues", false)
+ || shouldRenderResponse(facesContext, "updateModelValues", false))
+ {
+ // since this phase is completed we don't need to return right away even if the response is completed
+ skipFurtherProcessing = true;
+ }
+
+ if (!skipFurtherProcessing && log.isTraceEnabled()) log.trace("exiting updateModelValues");
+
+ return skipFurtherProcessing;
+ }
+}
diff --git a/core/src/main/resources/org/apache/struts/action2/jsf/package.html b/core/src/main/resources/org/apache/struts/action2/jsf/package.html
new file mode 100644
index 000000000..7576b8635
--- /dev/null
+++ b/core/src/main/resources/org/apache/struts/action2/jsf/package.html
@@ -0,0 +1,9 @@
+
+Integrates the JSF library into the Struts Action Framework. The overall approach
+ splits the JSF lifecycle into an Interceptor stack and Result, delegating
+ navigation and general controller functions to Struts.
+
+ Most of this code was borrowed from MyFaces, and as such, will need to be synchronized
+ occassionally with the original code.
+
+
diff --git a/core/src/main/resources/struts-default.xml b/core/src/main/resources/struts-default.xml
index 609a9b388..02385ccbf 100644
--- a/core/src/main/resources/struts-default.xml
+++ b/core/src/main/resources/struts-default.xml
@@ -13,6 +13,7 @@
+
@@ -44,6 +45,14 @@
+
+
+
+
+
+
+
+
@@ -176,6 +185,16 @@
input,back,cancel
+
+
+
+
+
+
+
+
+
+