diff --git a/build-spring-webflow/ivysettings.xml b/build-spring-webflow/ivysettings.xml index 41938115..e61499df 100644 --- a/build-spring-webflow/ivysettings.xml +++ b/build-spring-webflow/ivysettings.xml @@ -10,6 +10,9 @@ + + + diff --git a/spring-faces/.classpath b/spring-faces/.classpath index 72a3bc81..35488b00 100644 --- a/spring-faces/.classpath +++ b/spring-faces/.classpath @@ -8,7 +8,6 @@ - diff --git a/spring-faces/ivy.xml b/spring-faces/ivy.xml index b59f71f5..7bd85e15 100644 --- a/spring-faces/ivy.xml +++ b/spring-faces/ivy.xml @@ -11,9 +11,12 @@ - - + + + + + @@ -38,18 +41,35 @@ - - - + + + + - - - + + + + + + + + + + + - + + + + + - + + + + + diff --git a/spring-faces/src/main/java/org/springframework/faces/mvc/JsfView.java b/spring-faces/src/main/java/org/springframework/faces/mvc/JsfView.java index cbe52e26..e88c055e 100644 --- a/spring-faces/src/main/java/org/springframework/faces/mvc/JsfView.java +++ b/spring-faces/src/main/java/org/springframework/faces/mvc/JsfView.java @@ -15,11 +15,12 @@ */ package org.springframework.faces.mvc; -import java.io.IOException; +import static org.springframework.faces.webflow.JsfRuntimeInformation.isAtLeastJsf12; +import static org.springframework.faces.webflow.JsfRuntimeInformation.isPortletRequest; + import java.util.Iterator; import java.util.Map; -import javax.faces.FacesException; import javax.faces.FactoryFinder; import javax.faces.application.ViewHandler; import javax.faces.component.UIViewRoot; @@ -62,7 +63,7 @@ public class JsfView extends AbstractUrlBasedView { ViewHandler viewHandler = facesContext.getApplication().getViewHandler(); - if (JsfUtils.isAtLeastJsf12() && !JsfUtils.isPortlet(facesContext)) { + if (isAtLeastJsf12() && (!isPortletRequest(facesContext))) { viewHandler.initView(facesContext); } @@ -78,12 +79,8 @@ public class JsfView extends AbstractUrlBasedView { facesContext.setViewRoot(viewRoot); facesContext.renderResponse(); try { - JsfUtils.notifyBeforeListeners(PhaseId.RENDER_RESPONSE, facesLifecycle, facesContext); - logger.debug("Asking view handler to render view"); - facesContext.getApplication().getViewHandler().renderView(facesContext, viewRoot); - JsfUtils.notifyAfterListeners(PhaseId.RENDER_RESPONSE, facesLifecycle, facesContext); - } catch (IOException e) { - throw new FacesException("An I/O error occurred during view rendering", e); + logger.debug("Asking faces lifecycle to render"); + facesLifecycle.render(facesContext); } finally { logger.debug("View rendering complete"); facesContext.responseComplete(); diff --git a/spring-faces/src/main/java/org/springframework/faces/ui/AjaxViewRoot.java b/spring-faces/src/main/java/org/springframework/faces/ui/AjaxViewRoot.java index 9be0777d..53b57c55 100644 --- a/spring-faces/src/main/java/org/springframework/faces/ui/AjaxViewRoot.java +++ b/spring-faces/src/main/java/org/springframework/faces/ui/AjaxViewRoot.java @@ -194,9 +194,18 @@ public class AjaxViewRoot extends DelegatingViewRoot { private void swapChildren(UIViewRoot source, UIViewRoot target) { target.getChildren().addAll(source.getChildren()); - Iterator i = target.getChildren().iterator(); - while (i.hasNext()) { - UIComponent child = (UIComponent) i.next(); + // Create a new list because the children of ViewRoot can change while we're iterating. + // For example: + // 1. child is an outputScript component with target="head" + // 2. child.setParent() fires PostAddToViewEvent + // 3. MyFaces HtmlScriptRenderer processes the event + // 3.1. creates javax.faces.Panel for "head" + // 3.2. adds outputScript to it + // 3.3. the parent of outputScript is changed from ViewRoot to "head" Panel component + // 4. outputScript is therefore no longer a child of ViewRoot + List children = new ArrayList(target.getChildren()); + for (int i = 0; i < children.size(); i++) { + UIComponent child = (UIComponent) children.get(i); child.setParent(target); } } diff --git a/spring-faces/src/main/java/org/springframework/faces/ui/AjaxJsf2ViewRoot.java b/spring-faces/src/main/java/org/springframework/faces/ui/Jsf2AjaxViewRoot.java similarity index 96% rename from spring-faces/src/main/java/org/springframework/faces/ui/AjaxJsf2ViewRoot.java rename to spring-faces/src/main/java/org/springframework/faces/ui/Jsf2AjaxViewRoot.java index db5bab97..43531136 100644 --- a/spring-faces/src/main/java/org/springframework/faces/ui/AjaxJsf2ViewRoot.java +++ b/spring-faces/src/main/java/org/springframework/faces/ui/Jsf2AjaxViewRoot.java @@ -31,8 +31,6 @@ import javax.faces.event.ComponentSystemEventListener; import javax.faces.event.PhaseId; import javax.faces.event.SystemEventListener; -import org.springframework.faces.webflow.JsfVersion; - /** *

* A subclass of AjaxViewRoot for use with JSF 2.0. @@ -46,13 +44,10 @@ import org.springframework.faces.webflow.JsfVersion; * * @author Phil Webb */ -public class AjaxJsf2ViewRoot extends AjaxViewRoot { +public class Jsf2AjaxViewRoot extends AjaxViewRoot { - public AjaxJsf2ViewRoot(UIViewRoot original) { + public Jsf2AjaxViewRoot(UIViewRoot original) { super(original); - if (JsfVersion.isAtLeastJsf20()) { - setId(createUniqueId()); - } } public void addClientBehavior(String eventName, ClientBehavior behavior) { diff --git a/spring-faces/src/main/java/org/springframework/faces/webflow/FlowApplication.java b/spring-faces/src/main/java/org/springframework/faces/webflow/FlowApplication.java new file mode 100644 index 00000000..c05a4967 --- /dev/null +++ b/spring-faces/src/main/java/org/springframework/faces/webflow/FlowApplication.java @@ -0,0 +1,264 @@ +/* + * Copyright 2004-2010 the original author or authors. + * + * 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.springframework.faces.webflow; + +import static org.springframework.faces.webflow.JsfRuntimeInformation.isAtLeastJsf20; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Locale; + +import javax.faces.FacesException; +import javax.faces.application.Application; +import javax.faces.application.NavigationHandler; +import javax.faces.application.StateManager; +import javax.faces.application.ViewHandler; +import javax.faces.component.UIComponent; +import javax.faces.context.FacesContext; +import javax.faces.convert.Converter; +import javax.faces.el.MethodBinding; +import javax.faces.el.PropertyResolver; +import javax.faces.el.ReferenceSyntaxException; +import javax.faces.el.ValueBinding; +import javax.faces.el.VariableResolver; +import javax.faces.event.ActionListener; +import javax.faces.validator.Validator; + +import org.springframework.util.Assert; + +/** + * Wraps an {@link Application} instance in order to ensure Web Flow specific implementations of {@link ViewHandler} and + * {@link StateManager} are inserted at the front of the processing chain in JSF 1.2 and JSF 2.0 environments. This is + * done by intercepting the corresponding setters. All other methods are simple delegation methods. + * + * @author Rossen Stoyanchev + * + * @see Jsf2FlowApplication + */ +public class FlowApplication extends Application { + + private Application delegate; + + /** + * Class constructor that accepts a delegate Application instance. If the delegate has default instantiation logic + * for its StateManager and ViewHandler instances, those will be wrapped with {@link FlowViewStateManager} and a + * {@link FlowViewHandler} instance. + * + * @param delegate the Application instance to delegate to. + */ + public FlowApplication(Application delegate) { + Assert.notNull(delegate, "The delegate Application instance must not be null!"); + this.delegate = delegate; + + ViewHandler handler = this.delegate.getViewHandler(); + if (shouldWrap(handler)) { + wrapAndSetViewHandler(handler); + } + + StateManager manager = this.delegate.getStateManager(); + if (shouldWrap(manager)) { + wrapAndSetStateManager(manager); + } + } + + /** + * @return the wrapped Application instance + */ + public Application getDelegate() { + return delegate; + } + + /** + * Inserts {@link FlowViewStateManager} in front of the given StateManager (if not already done). + */ + public void setStateManager(StateManager manager) { + if (shouldWrap(manager)) { + wrapAndSetStateManager(manager); + } else { + delegate.setStateManager(manager); + } + } + + /** + * Inserts a {@link FlowViewHandler} in front of the given ViewHandler (if not already done). + */ + public void setViewHandler(ViewHandler handler) { + if (shouldWrap(handler)) { + wrapAndSetViewHandler(handler); + } else { + delegate.setViewHandler(handler); + } + } + + // ------------------- JSF 1.2 pass-through delegate methods ------------------// + + public void addComponent(String componentType, String componentClass) { + delegate.addComponent(componentType, componentClass); + } + + public void addConverter(String converterId, String converterClass) { + delegate.addConverter(converterId, converterClass); + } + + public void addConverter(Class targetClass, String converterClass) { + delegate.addConverter(targetClass, converterClass); + } + + public void addValidator(String validatorId, String validatorClass) { + delegate.addValidator(validatorId, validatorClass); + } + + public UIComponent createComponent(String componentType) throws FacesException { + return delegate.createComponent(componentType); + } + + public UIComponent createComponent(ValueBinding componentBinding, FacesContext context, String componentType) + throws FacesException { + return delegate.createComponent(componentBinding, context, componentType); + } + + public Converter createConverter(String converterId) { + return delegate.createConverter(converterId); + } + + public Converter createConverter(Class targetClass) { + return delegate.createConverter(targetClass); + } + + public MethodBinding createMethodBinding(String ref, Class[] params) throws ReferenceSyntaxException { + return delegate.createMethodBinding(ref, params); + } + + public Validator createValidator(String validatorId) throws FacesException { + return delegate.createValidator(validatorId); + } + + public ValueBinding createValueBinding(String ref) throws ReferenceSyntaxException { + return delegate.createValueBinding(ref); + } + + public ActionListener getActionListener() { + return delegate.getActionListener(); + } + + public Iterator getComponentTypes() { + return delegate.getComponentTypes(); + } + + public Iterator getConverterIds() { + return delegate.getConverterIds(); + } + + public Iterator> getConverterTypes() { + return delegate.getConverterTypes(); + } + + public Locale getDefaultLocale() { + return delegate.getDefaultLocale(); + } + + public String getDefaultRenderKitId() { + return delegate.getDefaultRenderKitId(); + } + + public String getMessageBundle() { + return delegate.getMessageBundle(); + } + + public NavigationHandler getNavigationHandler() { + return delegate.getNavigationHandler(); + } + + public PropertyResolver getPropertyResolver() { + return delegate.getPropertyResolver(); + } + + public StateManager getStateManager() { + return delegate.getStateManager(); + } + + public Iterator getSupportedLocales() { + return delegate.getSupportedLocales(); + } + + public Iterator getValidatorIds() { + return delegate.getValidatorIds(); + } + + public VariableResolver getVariableResolver() { + return delegate.getVariableResolver(); + } + + public ViewHandler getViewHandler() { + return delegate.getViewHandler(); + } + + public void setActionListener(ActionListener listener) { + delegate.setActionListener(listener); + } + + public void setDefaultLocale(Locale locale) { + delegate.setDefaultLocale(locale); + } + + public void setDefaultRenderKitId(String renderKitId) { + delegate.setDefaultRenderKitId(renderKitId); + } + + public void setMessageBundle(String bundle) { + delegate.setMessageBundle(bundle); + } + + public void setNavigationHandler(NavigationHandler handler) { + delegate.setNavigationHandler(handler); + } + + public void setPropertyResolver(PropertyResolver resolver) { + delegate.setPropertyResolver(resolver); + } + + public void setSupportedLocales(Collection locales) { + delegate.setSupportedLocales(locales); + } + + public void setVariableResolver(VariableResolver resolver) { + delegate.setVariableResolver(resolver); + } + + // ------------------- Private helper methods ------------------// + + private boolean shouldWrap(ViewHandler delegateViewHandler) { + return (delegateViewHandler != null) && (!(delegateViewHandler instanceof FlowViewHandler)); + } + + private boolean wrapAndSetViewHandler(ViewHandler target) { + if ((target != null) && (!(target instanceof FlowViewHandler))) { + ViewHandler handler = (isAtLeastJsf20()) ? new Jsf2FlowViewHandler(target) : new FlowViewHandler(target); + delegate.setViewHandler(handler); + return true; + } + return false; + } + + private boolean shouldWrap(StateManager manager) { + return (manager != null) && (!(manager instanceof FlowViewStateManager)); + } + + private void wrapAndSetStateManager(StateManager target) { + delegate.setStateManager(new FlowViewStateManager(target)); + } + +} diff --git a/spring-faces/src/main/java/org/springframework/faces/webflow/FlowApplicationFactory.java b/spring-faces/src/main/java/org/springframework/faces/webflow/FlowApplicationFactory.java index 3d99f735..a03db806 100644 --- a/spring-faces/src/main/java/org/springframework/faces/webflow/FlowApplicationFactory.java +++ b/spring-faces/src/main/java/org/springframework/faces/webflow/FlowApplicationFactory.java @@ -15,33 +15,38 @@ */ package org.springframework.faces.webflow; +import static org.springframework.faces.webflow.JsfRuntimeInformation.isAtLeastJsf20; + import javax.faces.application.Application; import javax.faces.application.ApplicationFactory; -import javax.faces.application.StateManager; + +import org.springframework.util.Assert; /** - * Custom {@link ApplicationFactory} that ensures the FlowViewStateManager is the first {@link StateManager} in the - * chain so that Web Flow may manage JSF component state when a flow is active. + * Custom {@link ApplicationFactory} that ensures the FlowApplication is the first {@link Application} in the chain, + * which in turn guarantees the install order for other JSF components. + * + * @see FlowApplication * * @author Jeremy Grelle - * */ public class FlowApplicationFactory extends ApplicationFactory { private ApplicationFactory delegate; public FlowApplicationFactory(ApplicationFactory delegate) { + Assert.notNull(delegate, "The delegate ApplicationFactory instance must not be null!"); this.delegate = delegate; } public Application getApplication() { - Application app = delegate.getApplication(); - // Ensure that FlowViewStateManager is first in the chain - if (app.getStateManager() != null && !(app.getStateManager() instanceof FlowViewStateManager)) { - FlowViewStateManager sm = new FlowViewStateManager(app.getStateManager()); - app.setStateManager(sm); + Application delegateApplication = delegate.getApplication(); + if (delegateApplication != null && (!(delegateApplication instanceof FlowApplication))) { + Application flowApplication = (isAtLeastJsf20()) ? new Jsf2FlowApplication(delegateApplication) + : new FlowApplication(delegateApplication); + setApplication(flowApplication); } - return app; + return delegate.getApplication(); } public void setApplication(Application application) { diff --git a/spring-faces/src/main/java/org/springframework/faces/webflow/FlowLifecycle.java b/spring-faces/src/main/java/org/springframework/faces/webflow/FlowLifecycle.java index f11825b3..2f652307 100644 --- a/spring-faces/src/main/java/org/springframework/faces/webflow/FlowLifecycle.java +++ b/spring-faces/src/main/java/org/springframework/faces/webflow/FlowLifecycle.java @@ -15,6 +15,8 @@ */ package org.springframework.faces.webflow; +import static org.springframework.faces.webflow.JsfRuntimeInformation.isAtLeastJsf20; + import javax.faces.FacesException; import javax.faces.FactoryFinder; import javax.faces.context.FacesContext; @@ -62,6 +64,9 @@ public class FlowLifecycle extends Lifecycle { for (int p = PhaseId.APPLY_REQUEST_VALUES.getOrdinal(); p <= PhaseId.INVOKE_APPLICATION.getOrdinal(); p++) { PhaseId phaseId = (PhaseId) PhaseId.VALUES.get(p); if (!skipPhase(context, phaseId)) { + if (isAtLeastJsf20()) { + context.setCurrentPhaseId(phaseId); + } invokePhase(context, phaseId); } } diff --git a/spring-faces/src/main/java/org/springframework/faces/webflow/FlowResourceResolver.java b/spring-faces/src/main/java/org/springframework/faces/webflow/FlowResourceResolver.java index e610a309..c949fd01 100644 --- a/spring-faces/src/main/java/org/springframework/faces/webflow/FlowResourceResolver.java +++ b/spring-faces/src/main/java/org/springframework/faces/webflow/FlowResourceResolver.java @@ -1,19 +1,34 @@ +/* + * Copyright 2004-2010 the original author or authors. + * + * 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.springframework.faces.webflow; import java.io.IOException; import java.net.URL; import javax.faces.FacesException; +import javax.faces.view.facelets.ResourceResolver; import org.springframework.context.ApplicationContext; import org.springframework.core.io.Resource; import org.springframework.webflow.execution.RequestContext; import org.springframework.webflow.execution.RequestContextHolder; -import com.sun.facelets.impl.DefaultResourceResolver; -import com.sun.facelets.impl.ResourceResolver; +import com.sun.faces.facelets.impl.DefaultResourceResolver; -public class FlowResourceResolver implements ResourceResolver { +public class FlowResourceResolver extends ResourceResolver { ResourceResolver delegateResolver = new DefaultResourceResolver(); diff --git a/spring-faces/src/main/java/org/springframework/faces/webflow/FlowViewHandler.java b/spring-faces/src/main/java/org/springframework/faces/webflow/FlowViewHandler.java index e93637f1..8cbbff81 100644 --- a/spring-faces/src/main/java/org/springframework/faces/webflow/FlowViewHandler.java +++ b/spring-faces/src/main/java/org/springframework/faces/webflow/FlowViewHandler.java @@ -26,6 +26,7 @@ import javax.faces.context.FacesContext; import org.springframework.context.ApplicationContext; import org.springframework.core.io.ContextResource; import org.springframework.core.io.Resource; +import org.springframework.util.Assert; import org.springframework.webflow.execution.RequestContext; import org.springframework.webflow.execution.RequestContextHolder; import org.springframework.webflow.execution.View; @@ -35,15 +36,22 @@ import org.springframework.webflow.execution.View; * including the current FlowExecutionKey, so that postbacks may be properly intercepted and handled by Web Flow. * * @author Jeremy Grelle + * + * @see Jsf2FlowViewHandler */ public class FlowViewHandler extends ViewHandler { private ViewHandler delegate; public FlowViewHandler(ViewHandler delegate) { + Assert.notNull(delegate, "The delegate ViewHandler instance must not be null!"); this.delegate = delegate; } + protected ViewHandler getDelegate() { + return delegate; + } + public String getActionURL(FacesContext context, String viewId) { if (JsfUtils.isFlowRequest()) { return RequestContextHolder.getRequestContext().getFlowExecutionUrl(); @@ -95,6 +103,14 @@ public class FlowViewHandler extends ViewHandler { delegate.writeState(context); } + public String deriveViewId(FacesContext context, String rawViewId) { + if (JsfUtils.isFlowRequest()) { + return resolveResourcePath(RequestContextHolder.getRequestContext(), rawViewId); + } else { + return getDelegate().deriveViewId(context, rawViewId); + } + } + // --------------------- Private Helpers ------------------------------// private String resolveResourcePath(RequestContext context, String viewId) { diff --git a/spring-faces/src/main/java/org/springframework/faces/webflow/FlowViewStateManager.java b/spring-faces/src/main/java/org/springframework/faces/webflow/FlowViewStateManager.java index bc7b7875..d7326b8b 100644 --- a/spring-faces/src/main/java/org/springframework/faces/webflow/FlowViewStateManager.java +++ b/spring-faces/src/main/java/org/springframework/faces/webflow/FlowViewStateManager.java @@ -103,10 +103,21 @@ public class FlowViewStateManager extends StateManager { public void writeState(FacesContext context, javax.faces.application.StateManager.SerializedView state) throws IOException { // Ensures that javax.faces.ViewState hidden field always gets written - needed for third-party component - // compatability + // compatibility delegate.writeState(context, state); } + public void writeState(FacesContext context, Object state) throws IOException { + if (state instanceof Object[]) { + delegate.writeState(context, state); // MyFaces + } else if (state instanceof FlowSerializedView) { // Mojarra + FlowSerializedView view = (FlowSerializedView) state; + delegate.writeState(context, new Object[] { view.getTreeStructure(), view.getComponentState() }); + } else { + super.writeState(context, state); + } + } + public boolean isSavingStateInClient(FacesContext context) { if (!JsfUtils.isFlowRequest()) { return delegate.isSavingStateInClient(context); @@ -161,4 +172,5 @@ public class FlowViewStateManager extends StateManager { } return viewRoot; } + } diff --git a/spring-faces/src/main/java/org/springframework/faces/webflow/Jsf2FlowApplication.java b/spring-faces/src/main/java/org/springframework/faces/webflow/Jsf2FlowApplication.java new file mode 100644 index 00000000..115cf4e7 --- /dev/null +++ b/spring-faces/src/main/java/org/springframework/faces/webflow/Jsf2FlowApplication.java @@ -0,0 +1,162 @@ +/* + * Copyright 2004-2010 the original author or authors. + * + * 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.springframework.faces.webflow; + +import java.util.Iterator; +import java.util.Map; +import java.util.ResourceBundle; + +import javax.el.ELContextListener; +import javax.el.ELException; +import javax.el.ELResolver; +import javax.el.ExpressionFactory; +import javax.el.ValueExpression; +import javax.faces.FacesException; +import javax.faces.application.Application; +import javax.faces.application.ProjectStage; +import javax.faces.application.Resource; +import javax.faces.application.ResourceHandler; +import javax.faces.component.UIComponent; +import javax.faces.component.behavior.Behavior; +import javax.faces.context.FacesContext; +import javax.faces.event.SystemEvent; +import javax.faces.event.SystemEventListener; + +/** + * Extends FlowApplication in order to provide JSF 2 delegation method. This is necessary because some of the methods + * use JSF 2 specific types as input or output parameters. + * + * @author Rossen Stoyanchev + */ +public class Jsf2FlowApplication extends FlowApplication { + + public Jsf2FlowApplication(Application delegate) { + super(delegate); + } + + // ------------------- JSF 2 pass-through delegate methods ------------------// + + public void addBehavior(String behaviorId, String behaviorClass) { + getDelegate().addBehavior(behaviorId, behaviorClass); + } + + public void addDefaultValidatorId(String validatorId) { + getDelegate().addDefaultValidatorId(validatorId); + } + + public void addELContextListener(ELContextListener listener) { + getDelegate().addELContextListener(listener); + } + + public void addELResolver(ELResolver resolver) { + getDelegate().addELResolver(resolver); + } + + public Behavior createBehavior(String behaviorId) throws FacesException { + return getDelegate().createBehavior(behaviorId); + } + + public UIComponent createComponent(FacesContext context, Resource componentResource) { + return getDelegate().createComponent(context, componentResource); + } + + public UIComponent createComponent(FacesContext context, String componentType, String rendererType) { + return getDelegate().createComponent(context, componentType, rendererType); + } + + public UIComponent createComponent(ValueExpression componentExpression, FacesContext context, String componentType, + String rendererType) { + return getDelegate().createComponent(componentExpression, context, componentType, rendererType); + } + + public UIComponent createComponent(ValueExpression componentExpression, FacesContext context, String componentType) + throws FacesException { + return getDelegate().createComponent(componentExpression, context, componentType); + } + + public T evaluateExpressionGet(FacesContext context, String expression, Class expectedType) + throws ELException { + return getDelegate().evaluateExpressionGet(context, expression, expectedType); + } + + public Iterator getBehaviorIds() { + return getDelegate().getBehaviorIds(); + } + + public Map getDefaultValidatorInfo() { + return getDelegate().getDefaultValidatorInfo(); + } + + public ELContextListener[] getELContextListeners() { + return getDelegate().getELContextListeners(); + } + + public ELResolver getELResolver() { + return getDelegate().getELResolver(); + } + + public ExpressionFactory getExpressionFactory() { + return getDelegate().getExpressionFactory(); + } + + public ProjectStage getProjectStage() { + return getDelegate().getProjectStage(); + } + + public ResourceBundle getResourceBundle(FacesContext ctx, String name) { + return getDelegate().getResourceBundle(ctx, name); + } + + public ResourceHandler getResourceHandler() { + return getDelegate().getResourceHandler(); + } + + public void publishEvent(FacesContext context, Class systemEventClass, + Class sourceBaseType, Object source) { + getDelegate().publishEvent(context, systemEventClass, sourceBaseType, source); + } + + public void publishEvent(FacesContext context, Class systemEventClass, Object source) { + getDelegate().publishEvent(context, systemEventClass, source); + } + + public void removeELContextListener(ELContextListener listener) { + getDelegate().removeELContextListener(listener); + } + + public void setResourceHandler(ResourceHandler resourceHandler) { + getDelegate().setResourceHandler(resourceHandler); + } + + public void subscribeToEvent(Class systemEventClass, Class sourceClass, + SystemEventListener listener) { + getDelegate().subscribeToEvent(systemEventClass, sourceClass, listener); + } + + public void subscribeToEvent(Class systemEventClass, SystemEventListener listener) { + getDelegate().subscribeToEvent(systemEventClass, listener); + } + + public void unsubscribeFromEvent(Class systemEventClass, Class sourceClass, + SystemEventListener listener) { + getDelegate().unsubscribeFromEvent(systemEventClass, sourceClass, listener); + } + + public void unsubscribeFromEvent(Class systemEventClass, SystemEventListener listener) { + getDelegate().unsubscribeFromEvent(systemEventClass, listener); + } + +} diff --git a/spring-faces/src/main/java/org/springframework/faces/webflow/Jsf2FlowViewHandler.java b/spring-faces/src/main/java/org/springframework/faces/webflow/Jsf2FlowViewHandler.java new file mode 100644 index 00000000..a0099921 --- /dev/null +++ b/spring-faces/src/main/java/org/springframework/faces/webflow/Jsf2FlowViewHandler.java @@ -0,0 +1,59 @@ +/* + * Copyright 2004-2010 the original author or authors. + * + * 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.springframework.faces.webflow; + +import java.util.Map; + +import javax.faces.FacesException; +import javax.faces.application.ViewHandler; +import javax.faces.context.FacesContext; +import javax.faces.view.ViewDeclarationLanguage; + +/** + * Extends FlowViewHandler in order to provide JSF 2 delegation method. This is necessary because some of the methods + * use JSF 2 specific types as input or output parameters. + * + * @author Rossen Stoyanchev + */ +public class Jsf2FlowViewHandler extends FlowViewHandler { + + public Jsf2FlowViewHandler(ViewHandler delegate) { + super(delegate); + } + + // --------------- JSF 2.0 Pass-through delegate methods ------------------// + + public String calculateCharacterEncoding(FacesContext context) { + return getDelegate().calculateCharacterEncoding(context); + } + + public String getBookmarkableURL(FacesContext context, String viewId, Map parameters, boolean includeViewParams) { + return getDelegate().getBookmarkableURL(context, viewId, parameters, includeViewParams); + } + + public String getRedirectURL(FacesContext context, String viewId, Map parameters, boolean includeViewParams) { + return getDelegate().getRedirectURL(context, viewId, parameters, includeViewParams); + } + + public ViewDeclarationLanguage getViewDeclarationLanguage(FacesContext context, String viewId) { + return getDelegate().getViewDeclarationLanguage(context, viewId); + } + + public void initView(FacesContext context) throws FacesException { + getDelegate().initView(context); + } + +} diff --git a/spring-faces/src/main/java/org/springframework/faces/webflow/JsfVersion.java b/spring-faces/src/main/java/org/springframework/faces/webflow/JsfRuntimeInformation.java similarity index 64% rename from spring-faces/src/main/java/org/springframework/faces/webflow/JsfVersion.java rename to spring-faces/src/main/java/org/springframework/faces/webflow/JsfRuntimeInformation.java index 85e7c65e..933ea91b 100644 --- a/spring-faces/src/main/java/org/springframework/faces/webflow/JsfVersion.java +++ b/spring-faces/src/main/java/org/springframework/faces/webflow/JsfRuntimeInformation.java @@ -17,46 +17,40 @@ package org.springframework.faces.webflow; import javax.faces.context.FacesContext; +import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; /** - * Internal helper class to find the version of JSF in use at runtime. + * Helper class to provide information about the JSF runtime environment such as JSF version and implementation. * * @author Phil Webb */ -public class JsfVersion { +public class JsfRuntimeInformation { - /** - * JSF Version 1.1 - */ + /** JSF Version 1.1 */ public static final int JSF_11 = 0; - /** - * JSF Version 1.2 - */ + /** JSF Version 1.2 */ public static final int JSF_12 = 1; - /** - * JSF Version 2.0 - */ + /** JSF Version 2.0 */ public static final int JSF_20 = 2; private static final int jsfVersion; + private static final boolean myFacesPresent = ClassUtils.isPresent("org.apache.myfaces.webapp.MyFacesServlet", + JsfUtils.class.getClassLoader()); + static { if (ReflectionUtils.findMethod(FacesContext.class, "isPostback") != null) { - jsfVersion = JsfVersion.JSF_20; + jsfVersion = JSF_20; } else if (ReflectionUtils.findMethod(FacesContext.class, "getELContext") != null) { - jsfVersion = JsfVersion.JSF_12; + jsfVersion = JSF_12; } else { - jsfVersion = JsfVersion.JSF_11; + jsfVersion = JSF_11; } } - public static int getJsfVersion() { - return jsfVersion; - } - public static boolean isAtLeastJsf20() { return jsfVersion >= JSF_20; } @@ -65,4 +59,12 @@ public class JsfVersion { return jsfVersion >= JSF_12; } + protected static boolean isMyFacesPresent() { + return myFacesPresent; + } + + public static boolean isPortletRequest(FacesContext context) { + return context.getExternalContext().getContext().getClass().getName().indexOf("Portlet") != -1; + } + } \ No newline at end of file diff --git a/spring-faces/src/main/java/org/springframework/faces/webflow/JsfView.java b/spring-faces/src/main/java/org/springframework/faces/webflow/JsfView.java index 0f92ea74..d9643d43 100644 --- a/spring-faces/src/main/java/org/springframework/faces/webflow/JsfView.java +++ b/spring-faces/src/main/java/org/springframework/faces/webflow/JsfView.java @@ -15,12 +15,13 @@ */ package org.springframework.faces.webflow; +import static org.springframework.faces.webflow.JsfRuntimeInformation.isAtLeastJsf12; + import java.io.IOException; import java.io.Serializable; import javax.faces.component.UIViewRoot; import javax.faces.context.FacesContext; -import javax.faces.event.PhaseId; import javax.faces.lifecycle.Lifecycle; import org.apache.commons.logging.Log; @@ -84,10 +85,8 @@ public class JsfView implements View { } facesContext.setViewRoot(viewRoot); try { - JsfUtils.notifyBeforeListeners(PhaseId.RENDER_RESPONSE, facesLifecycle, facesContext); - logger.debug("Asking view handler to render view"); - facesContext.getApplication().getViewHandler().renderView(facesContext, viewRoot); - JsfUtils.notifyAfterListeners(PhaseId.RENDER_RESPONSE, facesLifecycle, facesContext); + logger.debug("Asking faces lifecycle to render"); + facesLifecycle.render(facesContext); } finally { logger.debug("View rendering complete"); facesContext.responseComplete(); @@ -96,7 +95,7 @@ public class JsfView implements View { } public boolean userEventQueued() { - if (JsfUtils.isAtLeastJsf12()) { + if (isAtLeastJsf12()) { return requestContext.getRequestParameters().contains("javax.faces.ViewState"); } else { return requestContext.getRequestParameters().size() > 1; diff --git a/spring-faces/src/main/java/org/springframework/faces/webflow/JsfViewFactory.java b/spring-faces/src/main/java/org/springframework/faces/webflow/JsfViewFactory.java index 44e62be7..9801fb69 100644 --- a/spring-faces/src/main/java/org/springframework/faces/webflow/JsfViewFactory.java +++ b/spring-faces/src/main/java/org/springframework/faces/webflow/JsfViewFactory.java @@ -15,6 +15,10 @@ */ package org.springframework.faces.webflow; +import static org.springframework.faces.webflow.JsfRuntimeInformation.isAtLeastJsf12; +import static org.springframework.faces.webflow.JsfRuntimeInformation.isAtLeastJsf20; +import static org.springframework.faces.webflow.JsfRuntimeInformation.isPortletRequest; + import java.util.Iterator; import javax.faces.application.ViewHandler; @@ -32,8 +36,8 @@ import javax.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.binding.expression.Expression; -import org.springframework.faces.ui.AjaxJsf2ViewRoot; import org.springframework.faces.ui.AjaxViewRoot; +import org.springframework.faces.ui.Jsf2AjaxViewRoot; import org.springframework.js.ajax.SpringJavascriptAjaxHandler; import org.springframework.webflow.context.ExternalContext; import org.springframework.webflow.execution.RequestContext; @@ -70,13 +74,16 @@ public class JsfViewFactory implements ViewFactory { public View getView(RequestContext context) { FacesContext facesContext = FlowFacesContext.newInstance(context, lifecycle); try { + if (isAtLeastJsf20()) { + facesContext.setCurrentPhaseId(PhaseId.RESTORE_VIEW); + } if (!facesContext.getRenderResponse()) { // only publish a RESTORE_VIEW event if this is the first phase of the lifecycle // this won't be true when this method is called after a transition from one view-state to another JsfUtils.notifyBeforeListeners(PhaseId.RESTORE_VIEW, lifecycle, facesContext); } ViewHandler viewHandler = facesContext.getApplication().getViewHandler(); - if (JsfUtils.isAtLeastJsf12() && !JsfUtils.isPortlet(facesContext)) { + if (isAtLeastJsf12() && (!isPortletRequest(facesContext))) { viewHandler.initView(facesContext); } JsfView view; @@ -137,7 +144,7 @@ public class JsfViewFactory implements ViewFactory { private JsfView createJsfView(UIViewRoot root, Lifecycle lifecycle, RequestContext context) { if (isSpringJavascriptAjaxRequest(context.getExternalContext())) { - AjaxViewRoot viewRoot = (JsfVersion.isAtLeastJsf20()) ? new AjaxJsf2ViewRoot(root) : new AjaxViewRoot(root); + AjaxViewRoot viewRoot = (isAtLeastJsf20()) ? new Jsf2AjaxViewRoot(root) : new AjaxViewRoot(root); return new JsfView(viewRoot, lifecycle, context); } else { return new JsfView(root, lifecycle, context); diff --git a/spring-faces/src/main/resources/META-INF/faces-config.xml b/spring-faces/src/main/resources/META-INF/faces-config.xml index b08197b6..0ef11f11 100644 --- a/spring-faces/src/main/resources/META-INF/faces-config.xml +++ b/spring-faces/src/main/resources/META-INF/faces-config.xml @@ -11,9 +11,8 @@ org.springframework.faces.webflow.FlowVariableResolver org.springframework.faces.webflow.FlowPropertyResolver org.springframework.faces.webflow.SpringBeanWebFlowVariableResolver - org.springframework.faces.webflow.FlowViewHandler - + org.springframework.faces.webflow.FlowApplicationFactory diff --git a/spring-faces/src/test/java/org/springframework/faces/model/SelectionTrackingActionListenerTests.java b/spring-faces/src/test/java/org/springframework/faces/model/SelectionTrackingActionListenerTests.java index 07ff011f..1a677241 100644 --- a/spring-faces/src/test/java/org/springframework/faces/model/SelectionTrackingActionListenerTests.java +++ b/spring-faces/src/test/java/org/springframework/faces/model/SelectionTrackingActionListenerTests.java @@ -8,16 +8,18 @@ import javax.faces.component.UIColumn; import javax.faces.component.UICommand; import javax.faces.component.UIData; import javax.faces.component.UIViewRoot; +import javax.faces.context.FacesContext; import javax.faces.event.AbortProcessingException; import javax.faces.event.ActionEvent; import javax.faces.event.ActionListener; import junit.framework.TestCase; +import org.apache.myfaces.test.mock.MockFacesContext; import org.springframework.faces.webflow.JSFMockHelper; import org.springframework.util.ReflectionUtils; -import com.sun.facelets.component.UIRepeat; +import com.sun.faces.facelets.component.UIRepeat; public class SelectionTrackingActionListenerTests extends TestCase { @@ -92,10 +94,11 @@ public class SelectionTrackingActionListenerTests extends TestCase { uiRepeat.getChildren().add(commandButton); viewToTest.getChildren().add(uiRepeat); - Method indexMutator = ReflectionUtils.findMethod(UIRepeat.class, "setIndex", new Class[] { int.class }); + Method indexMutator = ReflectionUtils.findMethod(UIRepeat.class, "setIndex", new Class[] { FacesContext.class, + int.class }); indexMutator.setAccessible(true); - ReflectionUtils.invokeMethod(indexMutator, uiRepeat, new Object[] { new Integer(1) }); + ReflectionUtils.invokeMethod(indexMutator, uiRepeat, new Object[] { new MockFacesContext(), new Integer(1) }); ActionEvent event = new ActionEvent(commandButton); @@ -105,7 +108,7 @@ public class SelectionTrackingActionListenerTests extends TestCase { assertSame(dataModel.getSelectedRow(), dataModel.getRowData()); assertTrue(delegateListener.processedEvent); - ReflectionUtils.invokeMethod(indexMutator, uiRepeat, new Object[] { new Integer(2) }); + ReflectionUtils.invokeMethod(indexMutator, uiRepeat, new Object[] { new MockFacesContext(), new Integer(2) }); assertFalse(dataModel.isCurrentRowSelected()); assertTrue(dataModel.getSelectedRow() != dataModel.getRowData()); } diff --git a/spring-faces/template.mf b/spring-faces/template.mf index 76ac0df9..0ba14caa 100644 --- a/spring-faces/template.mf +++ b/spring-faces/template.mf @@ -15,5 +15,6 @@ Import-Template: javax.servlet.http;version="[2.4.0, 3.0.0)", javax.faces.*;version="[1.2.0, 3.0.0)", org.ajax4jsf.*;version="[1.1.1, 2.0.0)";resolution:=optional, + com.sun.faces.*;version="[2.0.0, 3.0.0)";resolution:=optional, com.sun.facelets.*;version="[1.1.0, 2.0.0)";resolution:=optional, org.w3c.dom;version="0" diff --git a/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/snapshot/SerializedFlowExecutionSnapshot.java b/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/snapshot/SerializedFlowExecutionSnapshot.java index f797f1d5..0b5114e6 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/snapshot/SerializedFlowExecutionSnapshot.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/snapshot/SerializedFlowExecutionSnapshot.java @@ -29,6 +29,7 @@ import java.io.ObjectStreamClass; import java.lang.reflect.Modifier; import java.lang.reflect.Proxy; import java.util.Arrays; +import java.util.HashMap; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; @@ -229,6 +230,20 @@ public class SerializedFlowExecutionSnapshot extends FlowExecutionSnapshot imple private static class ConfigurableObjectInputStream extends ObjectInputStream { + /* Temporary workaround for SPR-???? */ + private static final HashMap PRIMITIVE_CLASSES = new HashMap(8, 1.0F); + static { + PRIMITIVE_CLASSES.put("boolean", boolean.class); + PRIMITIVE_CLASSES.put("byte", byte.class); + PRIMITIVE_CLASSES.put("char", char.class); + PRIMITIVE_CLASSES.put("short", short.class); + PRIMITIVE_CLASSES.put("int", int.class); + PRIMITIVE_CLASSES.put("long", long.class); + PRIMITIVE_CLASSES.put("float", float.class); + PRIMITIVE_CLASSES.put("double", double.class); + PRIMITIVE_CLASSES.put("void", void.class); + } + private final ClassLoader classLoader; public ConfigurableObjectInputStream(InputStream in, ClassLoader classLoader) throws IOException { @@ -237,7 +252,16 @@ public class SerializedFlowExecutionSnapshot extends FlowExecutionSnapshot imple } protected Class resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { - return ClassUtils.forName(desc.getName(), classLoader); + String name = desc.getName(); + try { + return ClassUtils.forName(desc.getName(), classLoader); + } catch (ClassNotFoundException ex) { + Class rtn = (Class) PRIMITIVE_CLASSES.get(name); + if (rtn == null) { + throw ex; + } + return rtn; + } } protected Class resolveProxyClass(String[] interfaces) throws IOException, ClassNotFoundException {