From 5ba34a8321b690502e19a1550dc67fb891a6bc1b Mon Sep 17 00:00:00 2001 From: Scott Andrews Date: Tue, 25 Mar 2008 22:42:27 +0000 Subject: [PATCH] SWF-183 first pass at portlet integration --- .../webflow/context/ExternalContext.java | 6 + .../portlet/DefaultFlowUrlHandler.java | 43 +++ .../context/portlet/FlowUrlHandler.java | 46 +++ .../context/portlet/PortletContextMap.java | 65 ++++ .../portlet/PortletExternalContext.java | 325 ++++++++++++++++++ .../context/portlet/PortletRequestMap.java | 60 ++++ .../portlet/PortletRequestParameterMap.java | 87 +++++ .../context/portlet/PortletSessionMap.java | 104 ++++++ .../context/servlet/HttpSessionMap.java | 1 + .../servlet/ServletExternalContext.java | 4 + .../HttpSessionMapBindingListener.java | 2 +- .../webflow/engine/EndState.java | 2 +- .../webflow/engine/ViewState.java | 22 +- .../webflow/portlet/FlowHandler.java | 57 +++ .../webflow/portlet/FlowHandlerAdapter.java | 77 +++++ .../webflow/test/MockExternalContext.java | 4 + .../portlet/DefaultFlowUrlHandlerTests.java | 29 ++ .../portlet/PortletContextMapTests.java | 125 +++++++ .../portlet/PortletExternalContextTests.java | 133 +++++++ .../portlet/PortletRequestMapTests.java | 78 +++++ .../PortletRequestParameterMapTests.java | 85 +++++ .../portlet/PortletSessionMapTests.java | 112 ++++++ .../servlet/ServletExternalContextTests.java | 4 + .../HttpSessionMapBindingListenerTests.java | 3 +- 24 files changed, 1462 insertions(+), 12 deletions(-) create mode 100644 spring-webflow/src/main/java/org/springframework/webflow/context/portlet/DefaultFlowUrlHandler.java create mode 100644 spring-webflow/src/main/java/org/springframework/webflow/context/portlet/FlowUrlHandler.java create mode 100644 spring-webflow/src/main/java/org/springframework/webflow/context/portlet/PortletContextMap.java create mode 100644 spring-webflow/src/main/java/org/springframework/webflow/context/portlet/PortletExternalContext.java create mode 100644 spring-webflow/src/main/java/org/springframework/webflow/context/portlet/PortletRequestMap.java create mode 100644 spring-webflow/src/main/java/org/springframework/webflow/context/portlet/PortletRequestParameterMap.java create mode 100644 spring-webflow/src/main/java/org/springframework/webflow/context/portlet/PortletSessionMap.java rename spring-webflow/src/main/java/org/springframework/webflow/context/{servlet => web}/HttpSessionMapBindingListener.java (94%) create mode 100644 spring-webflow/src/main/java/org/springframework/webflow/portlet/FlowHandler.java create mode 100644 spring-webflow/src/main/java/org/springframework/webflow/portlet/FlowHandlerAdapter.java create mode 100644 spring-webflow/src/test/java/org/springframework/webflow/context/portlet/DefaultFlowUrlHandlerTests.java create mode 100644 spring-webflow/src/test/java/org/springframework/webflow/context/portlet/PortletContextMapTests.java create mode 100644 spring-webflow/src/test/java/org/springframework/webflow/context/portlet/PortletExternalContextTests.java create mode 100644 spring-webflow/src/test/java/org/springframework/webflow/context/portlet/PortletRequestMapTests.java create mode 100644 spring-webflow/src/test/java/org/springframework/webflow/context/portlet/PortletRequestParameterMapTests.java create mode 100644 spring-webflow/src/test/java/org/springframework/webflow/context/portlet/PortletSessionMapTests.java rename spring-webflow/src/test/java/org/springframework/webflow/context/{servlet => web}/HttpSessionMapBindingListenerTests.java (92%) diff --git a/spring-webflow/src/main/java/org/springframework/webflow/context/ExternalContext.java b/spring-webflow/src/main/java/org/springframework/webflow/context/ExternalContext.java index e60f7e13..4fdba354 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/context/ExternalContext.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/context/ExternalContext.java @@ -172,4 +172,10 @@ public interface ExternalContext { */ public boolean isResponseCommitted(); + /** + * Is a response allowed for this request + * @return true if yes, false otherwise + */ + public boolean isResponseAllowed(); + } \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/context/portlet/DefaultFlowUrlHandler.java b/spring-webflow/src/main/java/org/springframework/webflow/context/portlet/DefaultFlowUrlHandler.java new file mode 100644 index 00000000..ba2f7055 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/context/portlet/DefaultFlowUrlHandler.java @@ -0,0 +1,43 @@ +/* + * Copyright 2004-2008 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.webflow.context.portlet; + +import javax.portlet.ActionResponse; +import javax.portlet.PortletRequest; +import javax.portlet.PortletURL; +import javax.portlet.RenderResponse; + +/** + * Default flow URL handler for SWF 2. + * + * @author Scott Andrews + */ +public class DefaultFlowUrlHandler implements FlowUrlHandler { + + public String getFlowExecutionKey(PortletRequest request) { + return request.getParameter("execution"); + } + + public void setFlowExecutionRenderParameter(String flowExecutionKey, ActionResponse response) { + response.setRenderParameter("execution", flowExecutionKey); + } + + public String createFlowExecutionUrl(String flowId, String flowExecutionKey, RenderResponse response) { + PortletURL url = response.createActionURL(); + url.setParameter("execution", flowExecutionKey); + return url.toString(); + } +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/context/portlet/FlowUrlHandler.java b/spring-webflow/src/main/java/org/springframework/webflow/context/portlet/FlowUrlHandler.java new file mode 100644 index 00000000..273dd1a8 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/context/portlet/FlowUrlHandler.java @@ -0,0 +1,46 @@ +/* + * Copyright 2004-2008 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.webflow.context.portlet; + +import javax.portlet.ActionResponse; +import javax.portlet.PortletRequest; +import javax.portlet.RenderResponse; + +/** + * A interface for parsing and generating flow URLs. Encapsulates a specific flow URL format. + * + * @author Keith Donald + * @author Scott Andrews + */ +public interface FlowUrlHandler { + + /** + * Extract the flow execution from the request. + * @param request the request + * @return the flow execution key, or null if no flow execution key is present + */ + public String getFlowExecutionKey(PortletRequest request); + + /** + * Set the flow execution key render parameter. + * @param flowExecutionKey the key + * @param response the action response + */ + public void setFlowExecutionRenderParameter(String flowExecutionKey, ActionResponse response); + + public String createFlowExecutionUrl(String flowId, String flowExecutionKey, RenderResponse response); + +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/context/portlet/PortletContextMap.java b/spring-webflow/src/main/java/org/springframework/webflow/context/portlet/PortletContextMap.java new file mode 100644 index 00000000..c2c4414f --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/context/portlet/PortletContextMap.java @@ -0,0 +1,65 @@ +/* + * Copyright 2004-2007 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.webflow.context.portlet; + +import java.util.Iterator; + +import javax.portlet.PortletContext; + +import org.springframework.binding.collection.SharedMap; +import org.springframework.binding.collection.StringKeyedMapAdapter; +import org.springframework.webflow.core.collection.CollectionUtils; + +/** + * Map backed by the Portlet context for accessing application scoped attributes. + * + * @author Keith Donald + * @author Scott Andrews + */ +public class PortletContextMap extends StringKeyedMapAdapter implements SharedMap { + + /** + * The wrapped portlet context. + */ + private PortletContext context; + + /** + * Create a map wrapping given portlet context. + */ + public PortletContextMap(PortletContext context) { + this.context = context; + } + + protected Object getAttribute(String key) { + return context.getAttribute(key); + } + + protected void setAttribute(String key, Object value) { + context.setAttribute(key, value); + } + + protected void removeAttribute(String key) { + context.removeAttribute(key); + } + + protected Iterator getAttributeNames() { + return CollectionUtils.toIterator(context.getAttributeNames()); + } + + public Object getMutex() { + return context; + } +} \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/context/portlet/PortletExternalContext.java b/spring-webflow/src/main/java/org/springframework/webflow/context/portlet/PortletExternalContext.java new file mode 100644 index 00000000..f2915ba3 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/context/portlet/PortletExternalContext.java @@ -0,0 +1,325 @@ +package org.springframework.webflow.context.portlet; + +import java.io.IOException; +import java.io.Writer; +import java.security.Principal; + +import javax.portlet.ActionRequest; +import javax.portlet.ActionResponse; +import javax.portlet.PortletContext; +import javax.portlet.PortletRequest; +import javax.portlet.PortletResponse; +import javax.portlet.RenderRequest; +import javax.portlet.RenderResponse; + +import org.springframework.webflow.context.ExternalContext; +import org.springframework.webflow.core.collection.AttributeMap; +import org.springframework.webflow.core.collection.LocalAttributeMap; +import org.springframework.webflow.core.collection.LocalParameterMap; +import org.springframework.webflow.core.collection.LocalSharedAttributeMap; +import org.springframework.webflow.core.collection.MutableAttributeMap; +import org.springframework.webflow.core.collection.ParameterMap; +import org.springframework.webflow.core.collection.SharedAttributeMap; + +/** + * Provides contextual information about an portlet environment that has interacted with Spring Web Flow. + * + * @author Keith Donald + * @author Erwin Vervaet + * @author Jeremy Grelle + * @author Scott Andrews + */ +public class PortletExternalContext implements ExternalContext { + + protected static final short ACTION_PHASE = 1; + protected static final short RENDER_PHASE = 2; + + /** + * The context. + */ + private PortletContext context; + + /** + * The request. + */ + private PortletRequest request; + + /** + * The response. + */ + private PortletResponse response; + + /** + * The portlet request phase: render or action + */ + private short requestPhase; + + /** + * An accessor for the HTTP request parameter map. + */ + private ParameterMap requestParameterMap; + + /** + * An accessor for the HTTP request attribute map. + */ + private MutableAttributeMap requestMap; + + /** + * An accessor for the HTTP session map. + */ + private SharedAttributeMap sessionMap; + + /** + * An accessor for the servlet context application map. + */ + private SharedAttributeMap applicationMap; + + /** + * A flag indicating if a flow execution redirect has been requested. + */ + private boolean flowExecutionRedirectRequested; + + /** + * A string specifying the id of the flow to redirect to after request processing. If null, no flow definition + * redirect has been requested. + */ + private String flowDefinitionRedirectFlowId; + + /** + * Input to pass the flow definition upon redirecting. May be null. Never set unless + * {@link #flowDefinitionRedirectFlowId} has been set. + */ + private AttributeMap flowDefinitionRedirectFlowInput; + + /** + * A string specifying an arbitrary + */ + private String externalRedirectUrl; + + /** + * The strategy for generating flow execution urls. + */ + private FlowUrlHandler flowUrlHandler; + + /** + * Whether this external request context originated from an Ajax request or not. + */ + private boolean ajaxRequest; + + /** + * In the case where a redirect response is requested, this flag indicates if the redirect should be issued from a + * popup dialog. + */ + private boolean redirectInPopup; + + /** + * Create a new external context wrapping given portlet action request and response and given portlet context. + * @param context the portal context + * @param request the portlet request + * @param response the portlet response + */ + public PortletExternalContext(PortletContext context, PortletRequest request, PortletResponse response) { + init(context, request, response, new DefaultFlowUrlHandler()); + } + + /** + * Create a new external context wrapping given portlet action request and response and given portlet context. + * @param context the portal context + * @param request the portlet request + * @param response the portlet response + * @param flowUrlHandler the flow url handler + */ + public PortletExternalContext(PortletContext context, PortletRequest request, PortletResponse response, + FlowUrlHandler flowUrlHandler) { + init(context, request, response, flowUrlHandler); + } + + /** + * Indicates if the current request from this client is an ajax request. This flag may effect the handling of + * response writing within Spring Web Flow. + * @param ajaxRequest the ajax request flag + */ + public void setAjaxRequest(boolean ajaxRequest) { + this.ajaxRequest = ajaxRequest; + } + + // implementing external context + + public String getContextPath() { + return request.getContextPath(); + } + + public ParameterMap getRequestParameterMap() { + return requestParameterMap; + } + + public MutableAttributeMap getRequestMap() { + return requestMap; + } + + public SharedAttributeMap getSessionMap() { + return sessionMap; + } + + public SharedAttributeMap getGlobalSessionMap() { + return getSessionMap(); + } + + public SharedAttributeMap getApplicationMap() { + return applicationMap; + } + + public Principal getCurrentUser() { + return request.getUserPrincipal(); + } + + public Object getNativeContext() { + return context; + } + + public Object getNativeRequest() { + return request; + } + + public Object getNativeResponse() { + return response; + } + + public boolean isAjaxRequest() { + return ajaxRequest; + } + + public String getFlowExecutionUri(String flowId, String flowExecutionKey) { + if (this.isRenderPhase()) { + return flowUrlHandler.createFlowExecutionUrl(flowId, flowExecutionKey, (RenderResponse) response); + } else { + throw new IllegalStateException("Only a render request can obtain an flow execution uri"); + } + } + + public Writer getResponseWriter() { + try { + if (isRenderPhase()) { + return ((RenderResponse) response).getWriter(); + } else { + throw new IllegalStateException("Only render requests can obtain response writer"); + } + } catch (IOException e) { + throw new IllegalStateException("Unable to obtain response writer", e); + } + } + + public boolean isResponseCommitted() { + return flowExecutionRedirectRequested() || flowDefinitionRedirectRequested() || externalRedirectRequested(); + } + + public boolean isResponseAllowed() { + return isRenderPhase(); + } + + public void requestFlowExecutionRedirect() { + flowExecutionRedirectRequested = true; + } + + public void requestExternalRedirect(String uri) { + externalRedirectUrl = uri; + } + + public void requestFlowDefinitionRedirect(String flowId, AttributeMap input) { + flowDefinitionRedirectFlowId = flowId; + flowDefinitionRedirectFlowInput = input; + } + + public void requestRedirectInPopup() { + redirectInPopup = true; + } + + // implementation specific methods + + /** + * Returns the flag indicating if a flow execution redirect response has been requested by the flow. + */ + public boolean flowExecutionRedirectRequested() { + return flowExecutionRedirectRequested; + } + + /** + * Returns the flag indicating if a flow definition redirect response has been requested by the flow. + */ + public boolean flowDefinitionRedirectRequested() { + return flowDefinitionRedirectFlowId != null; + } + + /** + * Returns the id of the flow definition to redirect to. Only set when {@link #flowDefinitionRedirectRequested()} + * returns true. + */ + public String getFlowRedirectFlowId() { + return flowDefinitionRedirectFlowId; + } + + /** + * Returns the input to pass the flow definition through the redirect. Only set when + * {@link #flowDefinitionRedirectRequested()} returns true. + */ + public AttributeMap getFlowRedirectFlowInput() { + return flowDefinitionRedirectFlowInput; + } + + /** + * Returns the flag indicating if an external redirect response has been requested by the flow. + */ + public boolean externalRedirectRequested() { + return externalRedirectUrl != null; + } + + /** + * Returns the URL to redirect to. Only set if {@link #externalRedirectRequested()} returns true. + */ + public String getExternalRedirectUrl() { + return externalRedirectUrl; + } + + /** + * If a redirect response has been requested, indicates if the redirect should be issued from a popup dialog. + */ + public boolean redirectInPopup() { + return redirectInPopup; + } + + /** + * Returns true if the current request phase is the action phase + */ + public boolean isActionPhase() { + return requestPhase == ACTION_PHASE; + } + + /** + * Returns true if the current request phase is the render phase + */ + public boolean isRenderPhase() { + return requestPhase == RENDER_PHASE; + } + + // private helpers + + private void init(PortletContext context, PortletRequest request, PortletResponse response, + FlowUrlHandler flowUrlHandler) { + this.context = context; + this.request = request; + this.response = response; + this.requestParameterMap = new LocalParameterMap(new PortletRequestParameterMap(request)); + this.requestMap = new LocalAttributeMap(new PortletRequestMap(request)); + this.sessionMap = new LocalSharedAttributeMap(new PortletSessionMap(request)); + this.applicationMap = new LocalSharedAttributeMap(new PortletContextMap(context)); + this.flowUrlHandler = flowUrlHandler; + if (request instanceof ActionRequest && response instanceof ActionResponse) { + requestPhase = ACTION_PHASE; + } else if (request instanceof RenderRequest && response instanceof RenderResponse) { + requestPhase = RENDER_PHASE; + } else { + throw new IllegalArgumentException("Unknown portlet phase, expected: action or render"); + } + } + +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/context/portlet/PortletRequestMap.java b/spring-webflow/src/main/java/org/springframework/webflow/context/portlet/PortletRequestMap.java new file mode 100644 index 00000000..4cad852d --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/context/portlet/PortletRequestMap.java @@ -0,0 +1,60 @@ +/* + * Copyright 2004-2007 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.webflow.context.portlet; + +import java.util.Iterator; + +import javax.portlet.PortletRequest; + +import org.springframework.binding.collection.StringKeyedMapAdapter; +import org.springframework.webflow.core.collection.CollectionUtils; + +/** + * Map backed by the Portlet request attribute map for accessing request local attributes. + * + * @author Keith Donald + * @author Scott Andrews + */ +public class PortletRequestMap extends StringKeyedMapAdapter { + + /** + * The wrapped portlet request. + */ + private PortletRequest request; + + /** + * Create a new map wrapping the attributes of given request. + */ + public PortletRequestMap(PortletRequest request) { + this.request = request; + } + + protected Object getAttribute(String key) { + return request.getAttribute(key); + } + + protected void setAttribute(String key, Object value) { + request.setAttribute(key, value); + } + + protected void removeAttribute(String key) { + request.removeAttribute(key); + } + + protected Iterator getAttributeNames() { + return CollectionUtils.toIterator(request.getAttributeNames()); + } +} \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/context/portlet/PortletRequestParameterMap.java b/spring-webflow/src/main/java/org/springframework/webflow/context/portlet/PortletRequestParameterMap.java new file mode 100644 index 00000000..9bdd6dc8 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/context/portlet/PortletRequestParameterMap.java @@ -0,0 +1,87 @@ +/* + * Copyright 2004-2007 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.webflow.context.portlet; + +import java.util.Iterator; + +import javax.portlet.PortletRequest; + +import org.springframework.binding.collection.CompositeIterator; +import org.springframework.binding.collection.StringKeyedMapAdapter; +import org.springframework.util.Assert; +import org.springframework.web.portlet.multipart.MultipartActionRequest; +import org.springframework.webflow.core.collection.CollectionUtils; + +/** + * Map backed by the Portlet request parameter map for accessing request parameters. Also provides support for + * multi-part requests, providing transparent access to the request "fileMap" as a request parameter entry. + * + * @author Keith Donald + * @author Scott Andrews + */ +public class PortletRequestParameterMap extends StringKeyedMapAdapter { + + /** + * The wrapped Portlet request. + */ + private PortletRequest request; + + /** + * Create a new map wrapping the parameters of given request. + */ + public PortletRequestParameterMap(PortletRequest request) { + Assert.notNull(request, "The portlet request is required"); + this.request = request; + } + + protected Object getAttribute(String key) { + if (request instanceof MultipartActionRequest) { + MultipartActionRequest multipartRequest = (MultipartActionRequest) request; + Object data = multipartRequest.getFileMap().get(key); + if (data != null) { + return data; + } + } + String[] parameters = request.getParameterValues(key); + if (parameters == null) { + return null; + } else if (parameters.length == 1) { + return parameters[0]; + } else { + return parameters; + } + } + + protected void setAttribute(String key, Object value) { + throw new UnsupportedOperationException("PortletRequest parameter maps are immutable"); + } + + protected void removeAttribute(String key) { + throw new UnsupportedOperationException("PortletRequest parameter maps are immutable"); + } + + protected Iterator getAttributeNames() { + if (request instanceof MultipartActionRequest) { + MultipartActionRequest multipartRequest = (MultipartActionRequest) request; + CompositeIterator iterator = new CompositeIterator(); + iterator.add(multipartRequest.getFileMap().keySet().iterator()); + iterator.add(CollectionUtils.toIterator(request.getParameterNames())); + return iterator; + } else { + return CollectionUtils.toIterator(request.getParameterNames()); + } + } +} \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/context/portlet/PortletSessionMap.java b/spring-webflow/src/main/java/org/springframework/webflow/context/portlet/PortletSessionMap.java new file mode 100644 index 00000000..28ae0b2b --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/context/portlet/PortletSessionMap.java @@ -0,0 +1,104 @@ +/* + * Copyright 2004-2007 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.webflow.context.portlet; + +import java.util.Iterator; + +import javax.portlet.PortletRequest; +import javax.portlet.PortletSession; + +import org.springframework.binding.collection.SharedMap; +import org.springframework.binding.collection.StringKeyedMapAdapter; +import org.springframework.web.util.WebUtils; +import org.springframework.webflow.context.web.HttpSessionMapBindingListener; +import org.springframework.webflow.core.collection.AttributeMapBindingListener; +import org.springframework.webflow.core.collection.CollectionUtils; + +/** + * A Shared Map backed by the Portlet session, for accessing session scoped attributes. + * + * @author Keith Donald + * @author Scott Andrews + */ +public class PortletSessionMap extends StringKeyedMapAdapter implements SharedMap { + + /** + * The wrapped portlet request, providing access to the session. + */ + private PortletRequest request; + + /** + * Create a map wrapping the session of given request. + */ + public PortletSessionMap(PortletRequest request) { + this.request = request; + } + + /** + * Internal helper to get the portlet session associated with the wrapped request, or null if there is no such + * session. + *

+ * Note that this method will not force session creation. + */ + private PortletSession getSession() { + return request.getPortletSession(false); + } + + protected Object getAttribute(String key) { + PortletSession session = getSession(); + if (session == null) { + return null; + } + Object value = session.getAttribute(key); + if (value instanceof HttpSessionMapBindingListener) { + // unwrap + return ((HttpSessionMapBindingListener) value).getListener(); + } else { + return value; + } + } + + protected void setAttribute(String key, Object value) { + // force session creation + PortletSession session = request.getPortletSession(true); + if (value instanceof AttributeMapBindingListener) { + // wrap + session.setAttribute(key, new HttpSessionMapBindingListener((AttributeMapBindingListener) value, this)); + } else { + session.setAttribute(key, value); + } + } + + protected void removeAttribute(String key) { + PortletSession session = getSession(); + if (session != null) { + session.removeAttribute(key); + } + } + + protected Iterator getAttributeNames() { + PortletSession session = getSession(); + return session == null ? CollectionUtils.EMPTY_ITERATOR : CollectionUtils.toIterator(session + .getAttributeNames()); + } + + public Object getMutex() { + // force session creation + PortletSession session = request.getPortletSession(true); + Object mutex = session.getAttribute(WebUtils.SESSION_MUTEX_ATTRIBUTE); + return mutex != null ? mutex : session; + } +} \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/context/servlet/HttpSessionMap.java b/spring-webflow/src/main/java/org/springframework/webflow/context/servlet/HttpSessionMap.java index 0a61bc4f..9233906e 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/context/servlet/HttpSessionMap.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/context/servlet/HttpSessionMap.java @@ -23,6 +23,7 @@ import javax.servlet.http.HttpSession; import org.springframework.binding.collection.SharedMap; import org.springframework.binding.collection.StringKeyedMapAdapter; import org.springframework.web.util.WebUtils; +import org.springframework.webflow.context.web.HttpSessionMapBindingListener; import org.springframework.webflow.core.collection.AttributeMapBindingListener; import org.springframework.webflow.core.collection.CollectionUtils; diff --git a/spring-webflow/src/main/java/org/springframework/webflow/context/servlet/ServletExternalContext.java b/spring-webflow/src/main/java/org/springframework/webflow/context/servlet/ServletExternalContext.java index 90b53d02..d84e8ade 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/context/servlet/ServletExternalContext.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/context/servlet/ServletExternalContext.java @@ -207,6 +207,10 @@ public class ServletExternalContext implements ExternalContext { return flowExecutionRedirectRequested() || flowDefinitionRedirectRequested() || externalRedirectRequested(); } + public boolean isResponseAllowed() { + return true; + } + public void requestFlowExecutionRedirect() { flowExecutionRedirectRequested = true; } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/context/servlet/HttpSessionMapBindingListener.java b/spring-webflow/src/main/java/org/springframework/webflow/context/web/HttpSessionMapBindingListener.java similarity index 94% rename from spring-webflow/src/main/java/org/springframework/webflow/context/servlet/HttpSessionMapBindingListener.java rename to spring-webflow/src/main/java/org/springframework/webflow/context/web/HttpSessionMapBindingListener.java index a873915b..f7aa5587 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/context/servlet/HttpSessionMapBindingListener.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/context/web/HttpSessionMapBindingListener.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. */ -package org.springframework.webflow.context.servlet; +package org.springframework.webflow.context.web; import java.util.Map; diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/EndState.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/EndState.java index f8305b52..4bf22702 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/EndState.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/EndState.java @@ -96,7 +96,7 @@ public class EndState extends State { FlowSession activeSession = context.getFlowExecutionContext().getActiveSession(); if (activeSession.isRoot()) { // entire flow execution is ending; issue the final response - if (finalResponseAction != null) { + if (finalResponseAction != null && context.getExternalContext().isResponseAllowed()) { ActionExecutor.execute(finalResponseAction, context); } context.endActiveFlowSession(createSessionOutput(context)); diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/ViewState.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/ViewState.java index ec474a53..c38654b4 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/ViewState.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/ViewState.java @@ -161,14 +161,16 @@ public class ViewState extends TransitionableState { protected void doEnter(RequestControlContext context) throws FlowExecutionException { context.assignFlowExecutionKey(); - if (shouldRedirect(context)) { - context.getExternalContext().requestFlowExecutionRedirect(); - if (popup) { - context.getExternalContext().requestRedirectInPopup(); + if (context.getExternalContext().isResponseAllowed()) { + if (shouldRedirect(context)) { + context.getExternalContext().requestFlowExecutionRedirect(); + if (popup) { + context.getExternalContext().requestRedirectInPopup(); + } + } else { + View view = viewFactory.getView(context); + render(context, view); } - } else { - View view = viewFactory.getView(context); - render(context, view); } } @@ -182,11 +184,13 @@ public class ViewState extends TransitionableState { logger.debug("Event '" + event.getId() + "' signaled on view " + view); } boolean stateExited = context.handleEvent(event); - if (!stateExited) { + if (!stateExited && context.getExternalContext().isResponseAllowed()) { render(context, view); } } else { - render(context, view); + if (context.getExternalContext().isResponseAllowed()) { + render(context, view); + } } } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/portlet/FlowHandler.java b/spring-webflow/src/main/java/org/springframework/webflow/portlet/FlowHandler.java new file mode 100644 index 00000000..5fded1da --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/portlet/FlowHandler.java @@ -0,0 +1,57 @@ +/* + * Copyright 2004-2008 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.webflow.portlet; + +import javax.portlet.PortletRequest; +import javax.portlet.RenderRequest; +import javax.portlet.RenderResponse; + +import org.springframework.web.portlet.ModelAndView; +import org.springframework.webflow.core.collection.AttributeMap; +import org.springframework.webflow.core.collection.MutableAttributeMap; + +/** + * A controller helper used for customizing access to a single flow definition. This helper is used to: + *

    + *
  1. Launch executions of that flow with data in the execution input map + *
  2. Handle outcomes reached by that flow in a custom manner. + *
  3. Handle un-handled exceptions dealing with that flow in a custom manner. + *
+ * Such a handler can be visually thought of as a "flow reference" on a Garrett IA diagram. It holds a reference to the + * flow id to launch, how to provision its input, how to process its outcomes, and how to handle uncaught exceptions. + * + * @author Keith Donald + */ +public interface FlowHandler { + + /** + * Returns the id of the flow handled by this handler. Used by a Controller to load the flow definition. + * @return the flow id + */ + public String getFlowId(); + + /** + * Creates the flow execution input map to pass to a new instance of the flow being started. Used by a Controller to + * launch the flow execution with the correct input. + * @param request the current request + * @return the input map + */ + public MutableAttributeMap createExecutionInputMap(PortletRequest request); + + public ModelAndView handleFlowOutcome(String endedOutcome, AttributeMap endedOutput, RenderRequest request, + RenderResponse response); + +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/portlet/FlowHandlerAdapter.java b/spring-webflow/src/main/java/org/springframework/webflow/portlet/FlowHandlerAdapter.java new file mode 100644 index 00000000..d7bbf9c9 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/portlet/FlowHandlerAdapter.java @@ -0,0 +1,77 @@ +package org.springframework.webflow.portlet; + +import javax.portlet.ActionRequest; +import javax.portlet.ActionResponse; +import javax.portlet.PortletRequest; +import javax.portlet.PortletResponse; +import javax.portlet.PortletSession; +import javax.portlet.RenderRequest; +import javax.portlet.RenderResponse; + +import org.springframework.web.portlet.HandlerAdapter; +import org.springframework.web.portlet.ModelAndView; +import org.springframework.web.portlet.context.PortletApplicationObjectSupport; +import org.springframework.webflow.context.portlet.FlowUrlHandler; +import org.springframework.webflow.context.portlet.PortletExternalContext; +import org.springframework.webflow.core.collection.MutableAttributeMap; +import org.springframework.webflow.executor.FlowExecutionResult; +import org.springframework.webflow.executor.FlowExecutor; + +public class FlowHandlerAdapter extends PortletApplicationObjectSupport implements HandlerAdapter { + + private static final String FLOW_EXECUTION_RESULT = "flowExecutionResult"; + + private FlowExecutor flowExecutor; + + private FlowUrlHandler urlHandler; + + public FlowHandlerAdapter(FlowExecutor flowExecutor) { + this.flowExecutor = flowExecutor; + } + + public ModelAndView handleRender(RenderRequest request, RenderResponse response, Object handler) throws Exception { + FlowHandler flowHandler = (FlowHandler) handler; + String flowExecutionKey = urlHandler.getFlowExecutionKey(request); + if (flowExecutionKey != null) { + PortletExternalContext context = createPortletExternalContext(request, response); + flowExecutor.resumeExecution(flowExecutionKey, context); + return null; + } else { + PortletSession session = request.getPortletSession(true); + FlowExecutionResult result = (FlowExecutionResult) session.getAttribute(FLOW_EXECUTION_RESULT); + if (result != null) { + session.removeAttribute(FLOW_EXECUTION_RESULT); + return flowHandler.handleFlowOutcome(result.getEndedOutcome(), result.getEndedOutput(), request, + response); + } else { + MutableAttributeMap input = flowHandler.createExecutionInputMap(request); + PortletExternalContext context = createPortletExternalContext(request, response); + flowExecutor.launchExecution(flowHandler.getFlowId(), input, context); + return null; + } + } + } + + public void handleAction(ActionRequest request, ActionResponse response, Object handler) throws Exception { + String flowExecutionKey = urlHandler.getFlowExecutionKey(request); + PortletExternalContext context = createPortletExternalContext(request, response); + FlowExecutionResult result = flowExecutor.resumeExecution(flowExecutionKey, context); + if (result.paused()) { + urlHandler.setFlowExecutionRenderParameter(flowExecutionKey, response); + } else { + setEndResult(result, request); + } + } + + private void setEndResult(FlowExecutionResult result, ActionRequest request) { + request.getPortletSession().setAttribute(FLOW_EXECUTION_RESULT, result); + } + + public boolean supports(Object handler) { + return handler instanceof FlowHandler; + } + + private PortletExternalContext createPortletExternalContext(PortletRequest request, PortletResponse response) { + return new PortletExternalContext(getPortletContext(), request, response); + } +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/test/MockExternalContext.java b/spring-webflow/src/main/java/org/springframework/webflow/test/MockExternalContext.java index 30a5d56d..fbeba2cf 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/test/MockExternalContext.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/test/MockExternalContext.java @@ -145,6 +145,10 @@ public class MockExternalContext implements ExternalContext { return flowExecutionRedirectRequested() || flowDefinitionRedirectRequested() || externalRedirectRequested(); } + public boolean isResponseAllowed() { + return true; + } + public void requestFlowExecutionRedirect() { flowExecutionRedirectRequested = true; } diff --git a/spring-webflow/src/test/java/org/springframework/webflow/context/portlet/DefaultFlowUrlHandlerTests.java b/spring-webflow/src/test/java/org/springframework/webflow/context/portlet/DefaultFlowUrlHandlerTests.java new file mode 100644 index 00000000..f07e4183 --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/context/portlet/DefaultFlowUrlHandlerTests.java @@ -0,0 +1,29 @@ +package org.springframework.webflow.context.portlet; + +import junit.framework.TestCase; + +import org.springframework.mock.web.portlet.MockActionResponse; +import org.springframework.mock.web.portlet.MockPortletRequest; +import org.springframework.mock.web.portlet.MockRenderResponse; + +public class DefaultFlowUrlHandlerTests extends TestCase { + private DefaultFlowUrlHandler urlHandler = new DefaultFlowUrlHandler(); + private MockPortletRequest request = new MockPortletRequest(); + private MockActionResponse actionResponse = new MockActionResponse(); + private MockRenderResponse renderResponse = new MockRenderResponse(); + + public void testGetFlowExecutionKey() { + request.addParameter("execution", "12345"); + assertEquals("12345", urlHandler.getFlowExecutionKey(request)); + } + + public void testSetFlowExecutionRenderParameter() { + urlHandler.setFlowExecutionRenderParameter("12345", actionResponse); + assertEquals("12345", actionResponse.getRenderParameter("execution")); + } + + public void testCreateFlowExecutionUrl() { + String url = urlHandler.createFlowExecutionUrl("foo", "12345", renderResponse); + assertEquals("http://localhost/mockportlet?urlType=action;param_execution=12345", url); + } +} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/context/portlet/PortletContextMapTests.java b/spring-webflow/src/test/java/org/springframework/webflow/context/portlet/PortletContextMapTests.java new file mode 100644 index 00000000..6c8ef4bf --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/context/portlet/PortletContextMapTests.java @@ -0,0 +1,125 @@ +/* + * Copyright 2004-2007 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.webflow.context.portlet; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import junit.framework.TestCase; + +import org.springframework.mock.web.portlet.MockPortletContext; + +/** + * Test case for the {@link PortletContextMap} class. + * + * @author Ulrik Sandberg + * @author Erwin Vervaet + * @author Scott Andrews + */ +public class PortletContextMapTests extends TestCase { + + private PortletContextMap tested; + + private MockPortletContext context; + + protected void setUp() throws Exception { + super.setUp(); + context = new MockPortletContext(); + // a fresh MockPortletContext seems to already contain an element; + // that's confusing, so we remove it + context.removeAttribute("javax.servlet.context.tempdir"); + tested = new PortletContextMap(context); + tested.put("SomeKey", "SomeValue"); + } + + protected void tearDown() throws Exception { + super.tearDown(); + context = null; + tested = null; + } + + public void testIsEmpty() { + tested.remove("SomeKey"); + assertEquals("size,", 0, tested.size()); + assertEquals("isEmpty,", true, tested.isEmpty()); + } + + public void testSizeAddOne() { + assertEquals("size,", 1, tested.size()); + } + + public void testSizeAddTwo() { + tested.put("SomeOtherKey", "SomeOtherValue"); + assertEquals("size,", 2, tested.size()); + } + + public void testContainsKey() { + assertEquals("containsKey,", true, tested.containsKey("SomeKey")); + } + + public void testContainsValue() { + assertTrue(tested.containsValue("SomeValue")); + } + + public void testGet() { + assertEquals("get,", "SomeValue", tested.get("SomeKey")); + } + + public void testPut() { + Object old = tested.put("SomeKey", "SomeNewValue"); + + assertEquals("old value,", "SomeValue", old); + assertEquals("new value,", "SomeNewValue", tested.get("SomeKey")); + } + + public void testRemove() { + Object old = tested.remove("SomeKey"); + + assertEquals("old value,", "SomeValue", old); + assertNull("should be gone", tested.get("SomeKey")); + } + + public void testPutAll() { + Map otherMap = new HashMap(); + otherMap.put("SomeOtherKey", "SomeOtherValue"); + otherMap.put("SomeKey", "SomeUpdatedValue"); + tested.putAll(otherMap); + assertEquals("SomeOtherValue", tested.get("SomeOtherKey")); + assertEquals("SomeUpdatedValue", tested.get("SomeKey")); + } + + public void testClear() { + tested.clear(); + assertTrue(tested.isEmpty()); + } + + public void testKeySet() { + assertEquals(1, tested.keySet().size()); + assertTrue(tested.keySet().contains("SomeKey")); + } + + public void testValues() { + assertEquals(1, tested.values().size()); + assertTrue(tested.values().contains("SomeValue")); + } + + public void testEntrySet() { + assertEquals(1, tested.entrySet().size()); + assertEquals("SomeKey", ((Entry) tested.entrySet().iterator().next()).getKey()); + assertEquals("SomeValue", ((Entry) tested.entrySet().iterator().next()).getValue()); + } +} \ No newline at end of file diff --git a/spring-webflow/src/test/java/org/springframework/webflow/context/portlet/PortletExternalContextTests.java b/spring-webflow/src/test/java/org/springframework/webflow/context/portlet/PortletExternalContextTests.java new file mode 100644 index 00000000..3ca9b88b --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/context/portlet/PortletExternalContextTests.java @@ -0,0 +1,133 @@ +/* + * Copyright 2004-2007 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.webflow.context.portlet; + +import junit.framework.TestCase; + +import org.springframework.mock.web.portlet.MockActionRequest; +import org.springframework.mock.web.portlet.MockActionResponse; +import org.springframework.mock.web.portlet.MockPortletContext; +import org.springframework.mock.web.portlet.MockPortletRequest; +import org.springframework.mock.web.portlet.MockPortletResponse; +import org.springframework.webflow.context.servlet.ServletExternalContext; + +/** + * Unit tests for {@link ServletExternalContext}. + */ +public class PortletExternalContextTests extends TestCase { + + private MockPortletContext portletContext; + + private MockPortletRequest request; + + private MockPortletResponse response; + + private PortletExternalContext context; + + protected void setUp() { + portletContext = new MockPortletContext(); + request = new MockActionRequest(); + response = new MockActionResponse(); + context = new PortletExternalContext(portletContext, request, response); + } + + public void testGetContextPath() { + request.setContextPath("/foo"); + assertEquals("/foo", request.getContextPath()); + } + + public void testRequestParameters() { + assertTrue(context.getRequestParameterMap().isEmpty()); + } + + public void testGetNativeObjects() { + assertEquals(portletContext, context.getNativeContext()); + assertEquals(request, context.getNativeRequest()); + assertEquals(response, context.getNativeResponse()); + } + + public void testNotAnAjaxRequest() { + assertFalse(context.isAjaxRequest()); + } + + public void testAjaxRequestAcceptHeader() { + context.setAjaxRequest(true); + assertTrue(context.isAjaxRequest()); + } + + public void testNotResponseCommitted() { + assertFalse(context.isResponseCommitted()); + } + + public void testCommitExecutionRedirect() { + context.requestFlowExecutionRedirect(); + assertTrue(context.isResponseCommitted()); + assertTrue(context.flowExecutionRedirectRequested()); + } + + public void testCommitFlowRedirect() { + context.requestFlowDefinitionRedirect("foo", null); + assertTrue(context.isResponseCommitted()); + assertTrue(context.flowDefinitionRedirectRequested()); + assertEquals("foo", context.getFlowRedirectFlowId()); + } + + public void testCommitExternalRedirect() { + context.requestExternalRedirect("foo"); + assertTrue(context.isResponseCommitted()); + assertTrue(context.externalRedirectRequested()); + assertEquals("foo", context.getExternalRedirectUrl()); + } + + public void testCommitExecutionRedirectPopup() { + context.requestFlowExecutionRedirect(); + context.requestRedirectInPopup(); + assertTrue(context.isResponseCommitted()); + assertTrue(context.flowExecutionRedirectRequested()); + assertTrue(context.redirectInPopup()); + } + + public void testCommitFlowRedirectPopup() { + context.requestFlowDefinitionRedirect("foo", null); + context.requestRedirectInPopup(); + assertTrue(context.isResponseCommitted()); + assertTrue(context.flowDefinitionRedirectRequested()); + assertEquals("foo", context.getFlowRedirectFlowId()); + assertTrue(context.redirectInPopup()); + } + + public void testCommitExternalRedirectPopup() { + context.requestExternalRedirect("foo"); + context.requestRedirectInPopup(); + assertTrue(context.isResponseCommitted()); + assertTrue(context.externalRedirectRequested()); + assertEquals("foo", context.getExternalRedirectUrl()); + assertTrue(context.redirectInPopup()); + } + + public void testResponseAllowed() { + assertFalse(context.isResponseAllowed()); + } + + public void testIsActionPhase() { + assertTrue(context.isActionPhase()); + } + + public void testIsRenderPhase() { + assertFalse(context.isRenderPhase()); + } + +} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/context/portlet/PortletRequestMapTests.java b/spring-webflow/src/test/java/org/springframework/webflow/context/portlet/PortletRequestMapTests.java new file mode 100644 index 00000000..b380666e --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/context/portlet/PortletRequestMapTests.java @@ -0,0 +1,78 @@ +/* + * Copyright 2004-2007 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.webflow.context.portlet; + +import java.util.Iterator; + +import junit.framework.TestCase; + +import org.springframework.mock.web.portlet.MockPortletRequest; + +/** + * Unit test for the {@link PortletRequestMap} class. + * + * @author Ulrik Sandberg + * @author Scott Andrews + */ +public class PortletRequestMapTests extends TestCase { + + private PortletRequestMap tested; + + private MockPortletRequest request; + + protected void setUp() throws Exception { + super.setUp(); + request = new MockPortletRequest(); + tested = new PortletRequestMap(request); + } + + protected void tearDown() throws Exception { + super.tearDown(); + request = null; + tested = null; + } + + public void testGetAttribute() { + request.setAttribute("Some key", "Some value"); + // perform test + Object result = tested.getAttribute("Some key"); + assertEquals("Some value", result); + } + + public void testSetAttribute() { + // perform test + tested.setAttribute("Some key", "Some value"); + assertEquals("Some value", request.getAttribute("Some key")); + } + + public void testRemoveAttribute() { + request.setAttribute("Some key", "Some value"); + // perform test + tested.removeAttribute("Some key"); + assertNull(request.getAttribute("Some key")); + } + + public void testGetAttributeNames() { + request.setAttribute("Some key", "Some value"); + request.removeAttribute("javax.servlet.context.tempdir"); + // perform test + Iterator names = tested.getAttributeNames(); + assertNotNull("Null result unexpected", names); + assertTrue("More elements", names.hasNext()); + String name = (String) names.next(); + assertEquals("Some key", name); + } +} \ No newline at end of file diff --git a/spring-webflow/src/test/java/org/springframework/webflow/context/portlet/PortletRequestParameterMapTests.java b/spring-webflow/src/test/java/org/springframework/webflow/context/portlet/PortletRequestParameterMapTests.java new file mode 100644 index 00000000..888d77fc --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/context/portlet/PortletRequestParameterMapTests.java @@ -0,0 +1,85 @@ +/* + * Copyright 2004-2007 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.webflow.context.portlet; + +import java.util.Iterator; + +import junit.framework.TestCase; + +import org.springframework.mock.web.portlet.MockPortletRequest; + +/** + * Unit test for the {@link PortletRequestParameterMap} class. + * + * @author Ulrik Sandberg + * @author Scott Andrews + */ +public class PortletRequestParameterMapTests extends TestCase { + + private PortletRequestParameterMap tested; + + private MockPortletRequest request; + + protected void setUp() throws Exception { + super.setUp(); + request = new MockPortletRequest(); + tested = new PortletRequestParameterMap(request); + } + + protected void tearDown() throws Exception { + super.tearDown(); + request = null; + tested = null; + } + + public void testGetAttribute() { + request.setParameter("Some param", "Some value"); + // perform test + Object result = tested.getAttribute("Some param"); + assertEquals("Some value", result); + } + + public void testSetAttribute() { + // perform test + try { + tested.setAttribute("Some key", "Some value"); + fail("UnsupportedOperationException expected"); + } catch (UnsupportedOperationException expected) { + // expected + } + } + + public void testRemoveAttribute() { + request.setParameter("Some param", "Some value"); + // perform test + try { + tested.removeAttribute("Some param"); + fail("UnsupportedOperationException expected"); + } catch (UnsupportedOperationException expected) { + // expected + } + } + + public void testGetAttributeNames() { + request.setParameter("Some param", "Some value"); + // perform test + Iterator names = tested.getAttributeNames(); + assertNotNull("Null result unexpected", names); + assertTrue("More elements", names.hasNext()); + String name = (String) names.next(); + assertEquals("Some param", name); + } +} \ No newline at end of file diff --git a/spring-webflow/src/test/java/org/springframework/webflow/context/portlet/PortletSessionMapTests.java b/spring-webflow/src/test/java/org/springframework/webflow/context/portlet/PortletSessionMapTests.java new file mode 100644 index 00000000..cf449827 --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/context/portlet/PortletSessionMapTests.java @@ -0,0 +1,112 @@ +/* + * Copyright 2004-2007 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.webflow.context.portlet; + +import java.util.Iterator; + +import junit.framework.TestCase; + +import org.springframework.mock.web.portlet.MockPortletRequest; +import org.springframework.web.util.WebUtils; + +/** + * Unit test for the {@link PortletSessionMap} class. + * + * @author Ulrik Sandberg + * @author Scott Andrews + */ +public class PortletSessionMapTests extends TestCase { + + private PortletSessionMap tested; + + private MockPortletRequest request; + + protected void setUp() throws Exception { + super.setUp(); + request = new MockPortletRequest(); + tested = new PortletSessionMap(request); + } + + protected void tearDown() throws Exception { + super.tearDown(); + request = null; + tested = null; + } + + public void testGetAttribute() { + request.getPortletSession().setAttribute("Some key", "Some value"); + // perform test + Object result = tested.getAttribute("Some key"); + assertEquals("Some value", result); + } + + public void testGetAttributeNullSession() { + request.setSession(null); + // perform test + Object result = tested.getAttribute("Some key"); + assertNull("No value expected", result); + } + + public void testSetAttribute() { + // perform test + tested.setAttribute("Some key", "Some value"); + assertEquals("Some value", request.getPortletSession().getAttribute("Some key")); + } + + public void testRemoveAttribute() { + request.getPortletSession().setAttribute("Some key", "Some value"); + // perform test + tested.removeAttribute("Some key"); + assertNull(request.getPortletSession().getAttribute("Some key")); + } + + public void testRemoveAttributeNullSession() { + request.setSession(null); + // perform test + tested.removeAttribute("Some key"); + assertNull(request.getPortletSession().getAttribute("Some key")); + } + + public void testGetAttributeNames() { + request.getPortletSession().setAttribute("Some key", "Some value"); + // perform test + Iterator names = tested.getAttributeNames(); + assertNotNull("Null result unexpected", names); + assertTrue("More elements", names.hasNext()); + String name = (String) names.next(); + assertEquals("Some key", name); + } + + public void testGetAttributeNamesNullSession() { + request.setSession(null); + // perform test + Iterator names = tested.getAttributeNames(); + assertNotNull("Null result unexpected", names); + assertFalse("No elements expected", names.hasNext()); + } + + public void testGetSessionAsMutex() { + Object mutex = tested.getMutex(); + assertSame(mutex, request.getPortletSession()); + } + + public void testGetSessionMutex() { + Object object = new Object(); + request.getPortletSession().setAttribute(WebUtils.SESSION_MUTEX_ATTRIBUTE, object); + Object mutex = tested.getMutex(); + assertSame(mutex, object); + } +} \ No newline at end of file diff --git a/spring-webflow/src/test/java/org/springframework/webflow/context/servlet/ServletExternalContextTests.java b/spring-webflow/src/test/java/org/springframework/webflow/context/servlet/ServletExternalContextTests.java index c74171ed..f7a21dac 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/context/servlet/ServletExternalContextTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/context/servlet/ServletExternalContextTests.java @@ -115,4 +115,8 @@ public class ServletExternalContextTests extends TestCase { assertTrue(context.redirectInPopup()); } + public void testResponseAllowed() { + assertTrue(context.isResponseAllowed()); + } + } diff --git a/spring-webflow/src/test/java/org/springframework/webflow/context/servlet/HttpSessionMapBindingListenerTests.java b/spring-webflow/src/test/java/org/springframework/webflow/context/web/HttpSessionMapBindingListenerTests.java similarity index 92% rename from spring-webflow/src/test/java/org/springframework/webflow/context/servlet/HttpSessionMapBindingListenerTests.java rename to spring-webflow/src/test/java/org/springframework/webflow/context/web/HttpSessionMapBindingListenerTests.java index a38bd762..949e542c 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/context/servlet/HttpSessionMapBindingListenerTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/context/web/HttpSessionMapBindingListenerTests.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.webflow.context.servlet; +package org.springframework.webflow.context.web; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; @@ -21,6 +21,7 @@ import javax.servlet.http.HttpSession; import junit.framework.TestCase; import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.webflow.context.servlet.HttpSessionMap; import org.springframework.webflow.core.collection.AttributeMapBindingEvent; import org.springframework.webflow.core.collection.AttributeMapBindingListener;