001: /*
002: * $Id: WicketServlet.java 5151 2006-03-28 03:50:28 -0800 (Tue, 28 Mar 2006)
003: * joco01 $ $Revision: 464641 $ $Date: 2006-03-28 03:50:28 -0800 (Tue, 28 Mar
004: * 2006) $
005: *
006: * ==============================================================================
007: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
008: * use this file except in compliance with the License. You may obtain a copy of
009: * the License at
010: *
011: * http://www.apache.org/licenses/LICENSE-2.0
012: *
013: * Unless required by applicable law or agreed to in writing, software
014: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
015: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
016: * License for the specific language governing permissions and limitations under
017: * the License.
018: */
019: package wicket.protocol.http;
020:
021: import java.io.IOException;
022: import java.io.UnsupportedEncodingException;
023:
024: import javax.servlet.ServletException;
025: import javax.servlet.http.HttpServlet;
026: import javax.servlet.http.HttpServletRequest;
027: import javax.servlet.http.HttpServletResponse;
028:
029: import org.apache.commons.logging.Log;
030: import org.apache.commons.logging.LogFactory;
031:
032: import wicket.AbortException;
033: import wicket.Application;
034: import wicket.RequestCycle;
035: import wicket.Resource;
036: import wicket.Session;
037: import wicket.WicketRuntimeException;
038: import wicket.session.ISessionStore;
039: import wicket.settings.IRequestCycleSettings;
040: import wicket.util.resource.IResourceStream;
041: import wicket.util.time.Time;
042:
043: /**
044: * Servlet class for all wicket applications. The specific application class to
045: * instantiate should be specified to the application server via an init-params
046: * argument named "applicationClassName" in the servlet declaration, which is
047: * typically in a <i>web.xml </i> file. The servlet declaration may vary from
048: * one application server to another, but should look something like this:
049: *
050: * <pre>
051: * <servlet>
052: * <servlet-name>MyApplication</servlet-name>
053: * <servlet-class>wicket.protocol.http.WicketServlet</servlet-class>
054: * <init-param>
055: * <param-name>applicationClassName</param-name>
056: * <param-value>com.whoever.MyApplication</param-value>
057: * </init-param>
058: * <load-on-startup>1</load-on-startup>
059: * </servlet>
060: * </pre>
061: *
062: * Note that the applicationClassName parameter you specify must be the fully
063: * qualified name of a class that extends WebApplication. If your class cannot
064: * be found, does not extend WebApplication or cannot be instantiated, a runtime
065: * exception of type WicketRuntimeException will be thrown.
066: * </p>
067: * As an alternative, you can configure an application factory instead. This
068: * looks like:
069: *
070: * <pre>
071: * <init-param>
072: * <param-name>applicationFactoryClassName</param-name>
073: * <param-value>teachscape.platform.web.wicket.SpringApplicationFactory</param-value>
074: * </init-param>
075: * </pre>
076: *
077: * and it has to satisfy interface
078: * {@link wicket.protocol.http.IWebApplicationFactory}.
079: *
080: * <p>
081: * When GET/POST requests are made via HTTP, an WebRequestCycle object is
082: * created from the request, response and session objects (after wrapping them
083: * in the appropriate wicket wrappers). The RequestCycle's render() method is
084: * then called to produce a response to the HTTP request.
085: * <p>
086: * If you want to use servlet specific configuration, e.g. using init parameters
087: * from the {@link javax.servlet.ServletConfig}object, you should override the
088: * init() method of {@link javax.servlet.GenericServlet}. For example:
089: *
090: * <pre>
091: * public void init() throws ServletException
092: * {
093: * ServletConfig config = getServletConfig();
094: * String webXMLParameter = config.getInitParameter("myWebXMLParameter");
095: * ...
096: * </pre>
097: *
098: * </p>
099: * In order to support frameworks like Spring, the class is non-final and the
100: * variable webApplication is protected instead of private. Thus subclasses may
101: * provide there own means of providing the application object.
102: *
103: * @see wicket.RequestCycle
104: * @author Jonathan Locke
105: * @author Timur Mehrvarz
106: * @author Juergen Donnerstag
107: * @author Igor Vaynberg (ivaynberg)
108: */
109: public class WicketServlet extends HttpServlet {
110: private static final long serialVersionUID = 1L;
111:
112: /** The URL path prefix expected for (so called) resources (not html pages). */
113: private static final String RESOURCES_PATH_PREFIX = "/resources/";
114:
115: /**
116: * The name of the context parameter that specifies application factory
117: * class
118: */
119: public static final String APP_FACT_PARAM = "applicationFactoryClassName";
120:
121: /** Log. */
122: private static final Log log = LogFactory
123: .getLog(WicketServlet.class);
124:
125: /** The application this servlet is serving */
126: protected WebApplication webApplication;
127:
128: /**
129: * Handles servlet page requests.
130: *
131: * @param servletRequest
132: * Servlet request object
133: * @param servletResponse
134: * Servlet response object
135: * @throws ServletException
136: * Thrown if something goes wrong during request handling
137: * @throws IOException
138: */
139: public final void doGet(final HttpServletRequest servletRequest,
140: final HttpServletResponse servletResponse)
141: throws ServletException, IOException {
142: long time = System.currentTimeMillis();
143:
144: // If the request does not provide information about the encoding of its
145: // body (which includes POST parameters), than assume the default
146: // encoding as defined by the wicket application. Bear in mind that the
147: // encoding of the request usually is equal to the previous response.
148: // However it is a known bug of IE that it does not provide this
149: // information. Please see the wiki for more details and why all other
150: // browser deliberately copied that bug.
151: if (servletRequest.getCharacterEncoding() == null) {
152: try {
153: // The encoding defined by the wicket settings is used to encode
154: // the responses. Thus, it is reasonable to assume the request
155: // has the same encoding. This is especially important for
156: // forms and form parameters.
157: servletRequest.setCharacterEncoding(webApplication
158: .getRequestCycleSettings()
159: .getResponseRequestEncoding());
160: } catch (UnsupportedEncodingException ex) {
161: throw new WicketRuntimeException(ex.getMessage());
162: }
163: }
164:
165: // Create a new webrequest
166: final WebRequest request = webApplication
167: .newWebRequest(servletRequest);
168:
169: if (webApplication.getRequestCycleSettings()
170: .getRenderStrategy() == IRequestCycleSettings.REDIRECT_TO_BUFFER) {
171: String queryString = servletRequest.getQueryString();
172: if (queryString != null) {
173: // Try to see if there is a redirect stored
174: ISessionStore sessionStore = webApplication
175: .getSessionStore();
176: String sessionId = sessionStore.getSessionId(request);
177: BufferedHttpServletResponse bufferedResponse = webApplication
178: .popBufferedResponse(sessionId, queryString);
179:
180: if (bufferedResponse != null) {
181: bufferedResponse.writeTo(servletResponse);
182: // redirect responses are ignored for the request
183: // logger...
184: return;
185: }
186: }
187: }
188:
189: // First, set the webapplication for this thread
190: Application.set(webApplication);
191:
192: // Get session for request
193: final WebSession session = webApplication.getSession(request);
194:
195: // Create a response object and set the output encoding according to
196: // wicket's application setttings.
197: final WebResponse response = webApplication
198: .newWebResponse(servletResponse);
199: response.setAjax(request.isAjax());
200: response
201: .setCharacterEncoding(webApplication
202: .getRequestCycleSettings()
203: .getResponseRequestEncoding());
204:
205: try {
206: // Create a new request cycle
207: // FIXME post 1.2 Instead of doing this, we should get a request
208: // cycle factory
209: // from the application settings and use that. That way we are a
210: // step
211: // closer to a session-less operation of Wicket.
212: RequestCycle cycle = session.newRequestCycle(request,
213: response);
214:
215: try {
216: // Process request
217: cycle.request();
218: } catch (AbortException e) {
219: // noop
220: }
221: } finally {
222: // Close response
223: response.close();
224:
225: RequestLogger requestLogger = webApplication
226: .getRequestLogger();
227:
228: if (requestLogger != null) {
229: requestLogger
230: .requestTime((System.currentTimeMillis() - time));
231: }
232:
233: // Clean up thread local session
234: Session.unset();
235:
236: // Clean up thread local application
237: Application.unset();
238:
239: }
240: }
241:
242: /**
243: * Calls doGet with arguments.
244: *
245: * @param servletRequest
246: * Servlet request object
247: * @param servletResponse
248: * Servlet response object
249: * @see WicketServlet#doGet(HttpServletRequest, HttpServletResponse)
250: * @throws ServletException
251: * Thrown if something goes wrong during request handling
252: * @throws IOException
253: */
254: public final void doPost(final HttpServletRequest servletRequest,
255: final HttpServletResponse servletResponse)
256: throws ServletException, IOException {
257: doGet(servletRequest, servletResponse);
258: }
259:
260: /**
261: * Servlet initialization
262: */
263: public void init() {
264: if (this .webApplication == null) {
265: IWebApplicationFactory factory = getApplicationFactory();
266:
267: // Construct WebApplication subclass
268: this .webApplication = factory.createApplication(this );
269:
270: // Finished
271: log.info("WicketServlet loaded application "
272: + this .webApplication.getName() + " via "
273: + factory.getClass().getName() + " factory");
274: }
275:
276: // Set this WicketServlet as the servlet for the web application
277: this .webApplication.setWicketServlet(this );
278:
279: // Store instance of this application object in servlet context to make
280: // integration with outside world easier
281: String contextKey = "wicket:"
282: + getServletConfig().getServletName();
283: getServletContext().setAttribute(contextKey,
284: this .webApplication);
285:
286: try {
287: Application.set(webApplication);
288:
289: // Call internal init method of web application for default
290: // initialisation
291: this .webApplication.internalInit();
292:
293: // Call init method of web application
294: this .webApplication.init();
295:
296: // We initialize components here rather than in the constructor or
297: // in the internal init, because in the init method class aliases
298: // can be added, that would be used in installing resources in the
299: // component.
300: this .webApplication.initializeComponents();
301:
302: // Give the application the option to log that it is started
303: this .webApplication.logStarted();
304: } finally {
305: Application.unset();
306: }
307: }
308:
309: /**
310: * Servlet cleanup.
311: */
312: public void destroy() {
313: this .webApplication.internalDestroy();
314: this .webApplication = null;
315: }
316:
317: /**
318: * Creates the web application factory instance.
319: *
320: * If no APP_FACT_PARAM is specified in web.xml
321: * ContextParamWebApplicationFactory will be used by default.
322: *
323: * @see ContextParamWebApplicationFactory
324: *
325: * @return application factory instance
326: */
327: protected IWebApplicationFactory getApplicationFactory() {
328: final String appFactoryClassName = getInitParameter(APP_FACT_PARAM);
329:
330: if (appFactoryClassName == null) {
331: // If no context param was specified we return the default factory
332: return new ContextParamWebApplicationFactory();
333: } else {
334: try {
335: // Try to find the specified factory class
336: final Class factoryClass = getClass().getClassLoader()
337: .loadClass(appFactoryClassName);
338:
339: // Instantiate the factory
340: return (IWebApplicationFactory) factoryClass
341: .newInstance();
342: } catch (ClassCastException e) {
343: throw new WicketRuntimeException(
344: "Application factory class "
345: + appFactoryClassName
346: + " must implement IWebApplicationFactory");
347: } catch (ClassNotFoundException e) {
348: throw new WebApplicationFactoryCreationException(
349: appFactoryClassName, e);
350: } catch (InstantiationException e) {
351: throw new WebApplicationFactoryCreationException(
352: appFactoryClassName, e);
353: } catch (IllegalAccessException e) {
354: throw new WebApplicationFactoryCreationException(
355: appFactoryClassName, e);
356: } catch (SecurityException e) {
357: throw new WebApplicationFactoryCreationException(
358: appFactoryClassName, e);
359: }
360: }
361: }
362:
363: /**
364: * @see javax.servlet.http.HttpServlet#getLastModified(javax.servlet.http.HttpServletRequest)
365: */
366: protected long getLastModified(
367: final HttpServletRequest servletRequest) {
368: final String pathInfo = servletRequest.getPathInfo();
369: if ((pathInfo != null)
370: && pathInfo.startsWith(RESOURCES_PATH_PREFIX)) {
371: final String resourceReferenceKey = pathInfo
372: .substring(RESOURCES_PATH_PREFIX.length());
373:
374: // Try to find shared resource
375: Resource resource = webApplication.getSharedResources()
376: .get(resourceReferenceKey);
377:
378: // If resource found and it is cacheable
379: if ((resource != null) && resource.isCacheable()) {
380: try {
381: Application.set(webApplication);
382:
383: final WebRequest webRequest = webApplication
384: .newWebRequest(servletRequest);
385:
386: // Set parameters from servlet request
387: resource
388: .setParameters(webRequest.getParameterMap());
389:
390: // Get resource stream
391: IResourceStream stream = resource
392: .getResourceStream();
393:
394: // Get last modified time from stream
395: Time time = stream.lastModifiedTime();
396:
397: try {
398: stream.close();
399: } catch (IOException e) {
400: // ignore
401: }
402:
403: return time != null ? time.getMilliseconds() : -1;
404: } catch (AbortException e) {
405: return -1;
406: } finally {
407: resource.setParameters(null);
408: Application.unset();
409: }
410: }
411: }
412: return -1;
413: }
414:
415: }
|