001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.protocol.http;
018
019import java.io.IOException;
020import java.io.InputStream;
021import java.util.Enumeration;
022
023import javax.servlet.FilterConfig;
024import javax.servlet.ServletContext;
025import javax.servlet.ServletException;
026import javax.servlet.http.HttpServlet;
027import javax.servlet.http.HttpServletRequest;
028import javax.servlet.http.HttpServletResponse;
029
030import org.apache.wicket.util.io.Streams;
031import org.apache.wicket.util.string.Strings;
032import org.slf4j.Logger;
033import org.slf4j.LoggerFactory;
034
035/**
036 * 
037 * Please use {@link WicketFilter} if you require advanced chaining of resources.
038 * 
039 * <p>
040 * Servlet class for all wicket applications. The specific application class to instantiate should
041 * be specified to the application server via an init-params argument named "applicationClassName"
042 * in the servlet declaration, which is typically in a <i>web.xml </i> file. The servlet declaration
043 * may vary from one application server to another, but should look something like this:
044 * 
045 * <pre>
046 * &lt;servlet&gt;
047 *   &lt;servlet-name&gt;MyApplication&lt;/servlet-name&gt;
048 *   &lt;servlet-class&gt;org.apache.wicket.protocol.http.WicketServlet&lt;/servlet-class&gt;
049 *   &lt;init-param&gt;
050 *     &lt;param-name&gt;applicationClassName&lt;/param-name&gt;
051 *     &lt;param-value&gt;com.whoever.MyApplication&lt;/param-value&gt;
052 *   &lt;/init-param&gt;
053 *   &lt;load-on-startup&gt;1&lt;/load-on-startup&gt;
054 * &lt;/servlet&gt;
055 * </pre>
056 * 
057 * Note that the applicationClassName parameter you specify must be the fully qualified name of a
058 * class that extends WebApplication. If your class cannot be found, does not extend WebApplication
059 * or cannot be instantiated, a runtime exception of type WicketRuntimeException will be thrown.
060 * <p>
061 * As an alternative, you can configure an application factory instead. This looks like:
062 * 
063 * <pre>
064 * &lt;init-param&gt;
065 *   &lt;param-name&gt;applicationFactoryClassName&lt;/param-name&gt;
066 *   &lt;param-value&gt;teachscape.platform.web.wicket.SpringApplicationFactory&lt;/param-value&gt;
067 * &lt;/init-param&gt;
068 * </pre>
069 * 
070 * and it has to satisfy interface {@link org.apache.wicket.protocol.http.IWebApplicationFactory}.
071 * 
072 * <p>
073 * The servlet can also be configured to skip certain paths, this is especially useful when the
074 * servlet is mapped to <code>/*</code> mapping:
075 * 
076 * <pre>
077 * &lt;init-param&gt;
078 *   &lt;param-name&gt;ignorePaths&lt;/param-name&gt;
079 *   &lt;param-value&gt;/images/products/,/documents/pdf/&lt;/param-value&gt;
080 * &lt;/init-param&gt;
081 * </pre>
082 * 
083 * <p>
084 * When GET/POST requests are made via HTTP, a WebRequestCycle object is created from the request,
085 * response and session objects (after wrapping them in the appropriate wicket wrappers). The
086 * RequestCycle's render() method is then called to produce a response to the HTTP request.
087 * <p>
088 * If you want to use servlet specific configuration, e.g. using init parameters from the
089 * {@link javax.servlet.ServletConfig}object, you should override the init() method of
090 * {@link javax.servlet.GenericServlet}. For example:
091 * 
092 * <pre>
093 * public void init() throws ServletException
094 * {
095 *     ServletConfig config = getServletConfig();
096 *     String webXMLParameter = config.getInitParameter(&quot;myWebXMLParameter&quot;);
097 *     ...
098 * </pre>
099 * 
100 * <p>
101 * In order to support frameworks like Spring, the class is non-final and the variable
102 * webApplication is protected instead of private. Thus subclasses may provide their own means of
103 * providing the application object.
104 * 
105 * @see org.apache.wicket.request.cycle.RequestCycle
106 * @author Jonathan Locke
107 * @author Timur Mehrvarz
108 * @author Juergen Donnerstag
109 * @author Igor Vaynberg (ivaynberg)
110 * @author Al Maw
111 */
112public class WicketServlet extends HttpServlet
113{
114        private static final long serialVersionUID = 1L;
115
116        /** Log. */
117        private static final Logger log = LoggerFactory.getLogger(WicketServlet.class);
118
119        /** The WicketFilter where all the handling is done */
120        protected transient WicketFilter wicketFilter;
121
122        /**
123         * Handles servlet page requests.
124         * 
125         * @param servletRequest
126         *            Servlet request object
127         * @param servletResponse
128         *            Servlet response object
129         * @throws ServletException
130         *             Thrown if something goes wrong during request handling
131         * @throws IOException
132         */
133        @Override
134        public final void doGet(final HttpServletRequest servletRequest,
135                final HttpServletResponse servletResponse) throws ServletException, IOException
136        {
137                if (wicketFilter.processRequest(servletRequest, servletResponse, null) == false)
138                {
139                        fallback(servletRequest, servletResponse);
140                }
141        }
142
143        /**
144         * Calls doGet with arguments.
145         * 
146         * @param servletRequest
147         *            Servlet request object
148         * @param servletResponse
149         *            Servlet response object
150         * @see WicketServlet#doGet(HttpServletRequest, HttpServletResponse)
151         * @throws ServletException
152         *             Thrown if something goes wrong during request handling
153         * @throws IOException
154         */
155        @Override
156        public final void doPost(final HttpServletRequest servletRequest,
157                final HttpServletResponse servletResponse) throws ServletException, IOException
158        {
159                if (wicketFilter.processRequest(servletRequest, servletResponse, null) == false)
160                {
161                        fallback(servletRequest, servletResponse);
162                }
163        }
164
165        /**
166         * 
167         * @param httpServletRequest
168         * @return URL
169         */
170        private static String getURL(final HttpServletRequest httpServletRequest)
171        {
172                /*
173                 * Servlet 2.3 specification :
174                 * 
175                 * Servlet Path: The path section that directly corresponds to the mapping which activated
176                 * this request. This path starts with a "/" character except in the case where the request
177                 * is matched with the "/*" pattern, in which case it is the empty string.
178                 * 
179                 * PathInfo: The part of the request path that is not part of the Context Path or the
180                 * Servlet Path. It is either null if there is no extra path, or is a string with a leading
181                 * "/".
182                 */
183                String url = httpServletRequest.getServletPath();
184                final String pathInfo = httpServletRequest.getPathInfo();
185
186                if (pathInfo != null)
187                {
188                        url += pathInfo;
189                }
190
191                String queryString = httpServletRequest.getQueryString();
192                if (queryString != null)
193                {
194                        url += ("?" + queryString);
195                }
196
197                // If url is non-empty it will start with '/', which we should lose
198                if (url.length() > 0 && url.charAt(0) == '/')
199                {
200                        // Remove leading '/'
201                        url = url.substring(1);
202                }
203                return url;
204        }
205
206        /**
207         * 
208         * @param request
209         * @param response
210         * @throws IOException
211         */
212        private void fallback(final HttpServletRequest request, final HttpServletResponse response)
213                throws IOException
214        {
215                // The ServletWebRequest is created here to avoid code duplication. The getURL call doesn't
216                // depend on anything wicket specific
217                String url = getURL(request);
218
219                // WICKET-2185: strip of query string
220                if (url.indexOf('?') != -1)
221                {
222                        url = Strings.beforeFirst(url, '?');
223                }
224
225                // Get the relative URL we need for loading the resource from the servlet context
226                // NOTE: we NEED to put the '/' in front as otherwise some versions of application servers
227                // (e.g. Jetty 5.1.x) will fail for requests like '/mysubdir/myfile.css'
228                if ((url.length() > 0 && url.charAt(0) != '/') || url.length() == 0)
229                {
230                        url = '/' + url;
231                }
232
233                InputStream stream = getServletContext().getResourceAsStream(url);
234                String mimeType = getServletContext().getMimeType(url);
235
236                if (stream == null)
237                {
238                        if (response.isCommitted())
239                        {
240                                response.setStatus(HttpServletResponse.SC_NOT_FOUND);
241                        }
242                        else
243                        {
244                                response.sendError(HttpServletResponse.SC_NOT_FOUND);
245                        }
246                }
247                else
248                {
249                        if (mimeType != null)
250                        {
251                                response.setContentType(mimeType);
252                        }
253                        try
254                        {
255                                Streams.copy(stream, response.getOutputStream());
256                        }
257                        finally
258                        {
259                                stream.close();
260                        }
261                }
262        }
263
264        /**
265         * Servlet initialization
266         */
267        @Override
268        public void init() throws ServletException
269        {
270                wicketFilter = newWicketFilter();
271                wicketFilter.init(true, new FilterConfig()
272                {
273                        /**
274                         * @see javax.servlet.FilterConfig#getServletContext()
275                         */
276                        @Override
277                        public ServletContext getServletContext()
278                        {
279                                return WicketServlet.this.getServletContext();
280                        }
281
282                        /**
283                         * @see javax.servlet.FilterConfig#getInitParameterNames()
284                         */
285                        @Override
286                        @SuppressWarnings("unchecked")
287                        public Enumeration<String> getInitParameterNames()
288                        {
289                                return WicketServlet.this.getInitParameterNames();
290                        }
291
292                        /**
293                         * @see javax.servlet.FilterConfig#getInitParameter(java.lang.String)
294                         */
295                        @Override
296                        public String getInitParameter(final String name)
297                        {
298                                return WicketServlet.this.getInitParameter(name);
299                        }
300
301                        /**
302                         * @see javax.servlet.FilterConfig#getFilterName()
303                         */
304                        @Override
305                        public String getFilterName()
306                        {
307                                return WicketServlet.this.getServletName();
308                        }
309                });
310        }
311
312        /**
313         * @return The wicket filter
314         */
315        protected WicketFilter newWicketFilter()
316        {
317                return new WicketFilter();
318        }
319
320        /**
321         * Servlet cleanup.
322         */
323        @Override
324        public void destroy()
325        {
326                wicketFilter.destroy();
327                wicketFilter = null;
328        }
329}