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