001/*
002 * Copyright 2002-2017 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.mock.web;
018
019import java.io.IOException;
020import java.io.UnsupportedEncodingException;
021import java.util.Collections;
022import java.util.Enumeration;
023import java.util.LinkedHashMap;
024import java.util.LinkedHashSet;
025import java.util.Map;
026import javax.el.ELContext;
027import javax.servlet.Servlet;
028import javax.servlet.ServletConfig;
029import javax.servlet.ServletContext;
030import javax.servlet.ServletException;
031import javax.servlet.ServletRequest;
032import javax.servlet.ServletResponse;
033import javax.servlet.http.HttpServletRequest;
034import javax.servlet.http.HttpServletResponse;
035import javax.servlet.http.HttpSession;
036import javax.servlet.jsp.JspWriter;
037import javax.servlet.jsp.PageContext;
038
039import org.springframework.util.Assert;
040
041/**
042 * Mock implementation of the {@link javax.servlet.jsp.PageContext} interface.
043 * Only necessary for testing applications when testing custom JSP tags.
044 *
045 * <p>Note: Expects initialization via the constructor rather than via the
046 * {@code PageContext.initialize} method. Does not support writing to a
047 * JspWriter, request dispatching, or {@code handlePageException} calls.
048 *
049 * @author Juergen Hoeller
050 * @since 1.0.2
051 */
052public class MockPageContext extends PageContext {
053
054        private final ServletContext servletContext;
055
056        private final HttpServletRequest request;
057
058        private final HttpServletResponse response;
059
060        private final ServletConfig servletConfig;
061
062        private final Map<String, Object> attributes = new LinkedHashMap<String, Object>();
063
064        private JspWriter out;
065
066
067        /**
068         * Create new MockPageContext with a default {@link MockServletContext},
069         * {@link MockHttpServletRequest}, {@link MockHttpServletResponse},
070         * {@link MockServletConfig}.
071         */
072        public MockPageContext() {
073                this(null, null, null, null);
074        }
075
076        /**
077         * Create new MockPageContext with a default {@link MockHttpServletRequest},
078         * {@link MockHttpServletResponse}, {@link MockServletConfig}.
079         * @param servletContext the ServletContext that the JSP page runs in
080         * (only necessary when actually accessing the ServletContext)
081         */
082        public MockPageContext(ServletContext servletContext) {
083                this(servletContext, null, null, null);
084        }
085
086        /**
087         * Create new MockPageContext with a MockHttpServletResponse,
088         * MockServletConfig.
089         * @param servletContext the ServletContext that the JSP page runs in
090         * @param request the current HttpServletRequest
091         * (only necessary when actually accessing the request)
092         */
093        public MockPageContext(ServletContext servletContext, HttpServletRequest request) {
094                this(servletContext, request, null, null);
095        }
096
097        /**
098         * Create new MockPageContext with a MockServletConfig.
099         * @param servletContext the ServletContext that the JSP page runs in
100         * @param request the current HttpServletRequest
101         * @param response the current HttpServletResponse
102         * (only necessary when actually writing to the response)
103         */
104        public MockPageContext(ServletContext servletContext, HttpServletRequest request, HttpServletResponse response) {
105                this(servletContext, request, response, null);
106        }
107
108        /**
109         * Create new MockServletConfig.
110         * @param servletContext the ServletContext that the JSP page runs in
111         * @param request the current HttpServletRequest
112         * @param response the current HttpServletResponse
113         * @param servletConfig the ServletConfig (hardly ever accessed from within a tag)
114         */
115        public MockPageContext(ServletContext servletContext, HttpServletRequest request,
116                        HttpServletResponse response, ServletConfig servletConfig) {
117
118                this.servletContext = (servletContext != null ? servletContext : new MockServletContext());
119                this.request = (request != null ? request : new MockHttpServletRequest(servletContext));
120                this.response = (response != null ? response : new MockHttpServletResponse());
121                this.servletConfig = (servletConfig != null ? servletConfig : new MockServletConfig(servletContext));
122        }
123
124
125        @Override
126        public void initialize(
127                        Servlet servlet, ServletRequest request, ServletResponse response,
128                        String errorPageURL, boolean needsSession, int bufferSize, boolean autoFlush) {
129
130                throw new UnsupportedOperationException("Use appropriate constructor");
131        }
132
133        @Override
134        public void release() {
135        }
136
137        @Override
138        public void setAttribute(String name, Object value) {
139                Assert.notNull(name, "Attribute name must not be null");
140                if (value != null) {
141                        this.attributes.put(name, value);
142                }
143                else {
144                        this.attributes.remove(name);
145                }
146        }
147
148        @Override
149        public void setAttribute(String name, Object value, int scope) {
150                Assert.notNull(name, "Attribute name must not be null");
151                switch (scope) {
152                        case PAGE_SCOPE:
153                                setAttribute(name, value);
154                                break;
155                        case REQUEST_SCOPE:
156                                this.request.setAttribute(name, value);
157                                break;
158                        case SESSION_SCOPE:
159                                this.request.getSession().setAttribute(name, value);
160                                break;
161                        case APPLICATION_SCOPE:
162                                this.servletContext.setAttribute(name, value);
163                                break;
164                        default:
165                                throw new IllegalArgumentException("Invalid scope: " + scope);
166                }
167        }
168
169        @Override
170        public Object getAttribute(String name) {
171                Assert.notNull(name, "Attribute name must not be null");
172                return this.attributes.get(name);
173        }
174
175        @Override
176        public Object getAttribute(String name, int scope) {
177                Assert.notNull(name, "Attribute name must not be null");
178                switch (scope) {
179                        case PAGE_SCOPE:
180                                return getAttribute(name);
181                        case REQUEST_SCOPE:
182                                return this.request.getAttribute(name);
183                        case SESSION_SCOPE:
184                                HttpSession session = this.request.getSession(false);
185                                return (session != null ? session.getAttribute(name) : null);
186                        case APPLICATION_SCOPE:
187                                return this.servletContext.getAttribute(name);
188                        default:
189                                throw new IllegalArgumentException("Invalid scope: " + scope);
190                }
191        }
192
193        @Override
194        public Object findAttribute(String name) {
195                Object value = getAttribute(name);
196                if (value == null) {
197                        value = getAttribute(name, REQUEST_SCOPE);
198                        if (value == null) {
199                                value = getAttribute(name, SESSION_SCOPE);
200                                if (value == null) {
201                                        value = getAttribute(name, APPLICATION_SCOPE);
202                                }
203                        }
204                }
205                return value;
206        }
207
208        @Override
209        public void removeAttribute(String name) {
210                Assert.notNull(name, "Attribute name must not be null");
211                this.removeAttribute(name, PageContext.PAGE_SCOPE);
212                this.removeAttribute(name, PageContext.REQUEST_SCOPE);
213                this.removeAttribute(name, PageContext.SESSION_SCOPE);
214                this.removeAttribute(name, PageContext.APPLICATION_SCOPE);
215        }
216
217        @Override
218        public void removeAttribute(String name, int scope) {
219                Assert.notNull(name, "Attribute name must not be null");
220                switch (scope) {
221                        case PAGE_SCOPE:
222                                this.attributes.remove(name);
223                                break;
224                        case REQUEST_SCOPE:
225                                this.request.removeAttribute(name);
226                                break;
227                        case SESSION_SCOPE:
228                                this.request.getSession().removeAttribute(name);
229                                break;
230                        case APPLICATION_SCOPE:
231                                this.servletContext.removeAttribute(name);
232                                break;
233                        default:
234                                throw new IllegalArgumentException("Invalid scope: " + scope);
235                }
236        }
237
238        @Override
239        public int getAttributesScope(String name) {
240                if (getAttribute(name) != null) {
241                        return PAGE_SCOPE;
242                }
243                else if (getAttribute(name, REQUEST_SCOPE) != null) {
244                        return REQUEST_SCOPE;
245                }
246                else if (getAttribute(name, SESSION_SCOPE) != null) {
247                        return SESSION_SCOPE;
248                }
249                else if (getAttribute(name, APPLICATION_SCOPE) != null) {
250                        return APPLICATION_SCOPE;
251                }
252                else {
253                        return 0;
254                }
255        }
256
257        public Enumeration<String> getAttributeNames() {
258                return Collections.enumeration(new LinkedHashSet<String>(this.attributes.keySet()));
259        }
260
261        @Override
262        public Enumeration<String> getAttributeNamesInScope(int scope) {
263                switch (scope) {
264                        case PAGE_SCOPE:
265                                return getAttributeNames();
266                        case REQUEST_SCOPE:
267                                return this.request.getAttributeNames();
268                        case SESSION_SCOPE:
269                                HttpSession session = this.request.getSession(false);
270                                return (session != null ? session.getAttributeNames() : null);
271                        case APPLICATION_SCOPE:
272                                return this.servletContext.getAttributeNames();
273                        default:
274                                throw new IllegalArgumentException("Invalid scope: " + scope);
275                }
276        }
277
278        @Override
279        public JspWriter getOut() {
280                if (this.out == null) {
281                        this.out = new MockJspWriter(this.response);
282                }
283                return this.out;
284        }
285
286        @Override
287        @Deprecated
288        public javax.servlet.jsp.el.ExpressionEvaluator getExpressionEvaluator() {
289                return new MockExpressionEvaluator(this);
290        }
291
292        @Override
293        public ELContext getELContext() {
294                return null;
295        }
296
297        @Override
298        @Deprecated
299        public javax.servlet.jsp.el.VariableResolver getVariableResolver() {
300                return null;
301        }
302
303        @Override
304        public HttpSession getSession() {
305                return this.request.getSession();
306        }
307
308        @Override
309        public Object getPage() {
310                return this;
311        }
312
313        @Override
314        public ServletRequest getRequest() {
315                return this.request;
316        }
317
318        @Override
319        public ServletResponse getResponse() {
320                return this.response;
321        }
322
323        @Override
324        public Exception getException() {
325                return null;
326        }
327
328        @Override
329        public ServletConfig getServletConfig() {
330                return this.servletConfig;
331        }
332
333        @Override
334        public ServletContext getServletContext() {
335                return this.servletContext;
336        }
337
338        @Override
339        public void forward(String path) throws ServletException, IOException {
340                this.request.getRequestDispatcher(path).forward(this.request, this.response);
341        }
342
343        @Override
344        public void include(String path) throws ServletException, IOException {
345                this.request.getRequestDispatcher(path).include(this.request, this.response);
346        }
347
348        @Override
349        public void include(String path, boolean flush) throws ServletException, IOException {
350                this.request.getRequestDispatcher(path).include(this.request, this.response);
351                if (flush) {
352                        this.response.flushBuffer();
353                }
354        }
355
356        public byte[] getContentAsByteArray() {
357                Assert.state(this.response instanceof MockHttpServletResponse, "MockHttpServletResponse required");
358                return ((MockHttpServletResponse) this.response).getContentAsByteArray();
359        }
360
361        public String getContentAsString() throws UnsupportedEncodingException {
362                Assert.state(this.response instanceof MockHttpServletResponse, "MockHttpServletResponse required");
363                return ((MockHttpServletResponse) this.response).getContentAsString();
364        }
365
366        @Override
367        public void handlePageException(Exception ex) throws ServletException, IOException {
368                throw new ServletException("Page exception", ex);
369        }
370
371        @Override
372        public void handlePageException(Throwable ex) throws ServletException, IOException {
373                throw new ServletException("Page exception", ex);
374        }
375
376}