001: /*
002: * $Id: WebApplication.java 4771 2006-03-05 17:07:48 -0800 (Sun, 05 Mar 2006)
003: * joco01 $ $Revision: 516823 $ $Date: 2006-03-05 17:07:48 -0800 (Sun, 05 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.util.HashMap;
022: import java.util.Map;
023:
024: import javax.servlet.http.HttpServletRequest;
025: import javax.servlet.http.HttpServletResponse;
026: import javax.servlet.http.HttpSession;
027:
028: import org.apache.commons.logging.Log;
029: import org.apache.commons.logging.LogFactory;
030:
031: import wicket.Application;
032: import wicket.IRequestCycleFactory;
033: import wicket.IRequestTarget;
034: import wicket.ISessionFactory;
035: import wicket.PageMap;
036: import wicket.Request;
037: import wicket.RequestCycle;
038: import wicket.Response;
039: import wicket.Session;
040: import wicket.WicketRuntimeException;
041: import wicket.markup.html.pages.AccessDeniedPage;
042: import wicket.markup.html.pages.InternalErrorPage;
043: import wicket.markup.html.pages.PageExpiredErrorPage;
044: import wicket.markup.resolver.AutoLinkResolver;
045: import wicket.protocol.http.servlet.ServletWebRequest;
046: import wicket.request.IRequestCycleProcessor;
047: import wicket.request.target.coding.BookmarkablePageRequestTargetUrlCodingStrategy;
048: import wicket.request.target.coding.IRequestTargetUrlCodingStrategy;
049: import wicket.request.target.coding.PackageRequestTargetUrlCodingStrategy;
050: import wicket.request.target.coding.SharedResourceRequestTargetUrlCodingStrategy;
051: import wicket.session.ISessionStore;
052: import wicket.util.collections.MostRecentlyUsedMap;
053: import wicket.util.file.WebApplicationPath;
054: import wicket.util.lang.PackageName;
055: import wicket.util.watch.ModificationWatcher;
056:
057: /**
058: * A web application is a subclass of Application which associates with an
059: * instance of WicketServlet to serve pages over the HTTP protocol. This class
060: * is intended to be subclassed by framework clients to define a web
061: * application.
062: * <p>
063: * Application settings are given defaults by the WebApplication() constructor
064: * and internalInit method, such as error page classes appropriate for HTML.
065: * WebApplication subclasses can override these values and/or modify other
066: * application settings by overriding the init() method and then by calling
067: * getXXXSettings() to retrieve an interface to a mutable Settings object. Do
068: * not do this in the constructor itself because the defaults will then override
069: * your settings.
070: * <p>
071: * If you want to use servlet specific configuration, e.g. using init parameters
072: * from the {@link javax.servlet.ServletConfig}object, you should override the
073: * init() method. For example:
074: *
075: * <pre>
076: * public void init()
077: * {
078: * String webXMLParameter = getWicketServlet().getInitParameter("myWebXMLParameter");
079: * URL schedulersConfig = getWicketServlet().getServletContext().getResource("/WEB-INF/schedulers.xml");
080: * ...
081: * </pre>
082: *
083: * @see WicketServlet
084: * @see wicket.settings.IApplicationSettings
085: * @see wicket.settings.IDebugSettings
086: * @see wicket.settings.IExceptionSettings
087: * @see wicket.settings.IMarkupSettings
088: * @see wicket.settings.IPageSettings
089: * @see wicket.settings.IRequestCycleSettings
090: * @see wicket.settings.IResourceSettings
091: * @see wicket.settings.ISecuritySettings
092: * @see wicket.settings.ISessionSettings
093: *
094: * @author Jonathan Locke
095: * @author Chris Turner
096: * @author Johan Compagner
097: * @author Eelco Hillenius
098: * @author Juergen Donnerstag
099: */
100: public abstract class WebApplication extends Application implements
101: ISessionFactory {
102: /** Log. */
103: private static final Log log = LogFactory
104: .getLog(WebApplication.class);
105:
106: /**
107: * The cached application key. Will be set in
108: * {@link #setWicketServlet(WicketServlet)} based on the servlet context.
109: */
110: private String applicationKey;
111:
112: /**
113: * Map of buffered responses that are in progress per session. Buffered
114: * responses are temporarily stored
115: */
116: private final Map bufferedResponses = new HashMap();
117:
118: /** the default request cycle processor implementation. */
119: private IRequestCycleProcessor requestCycleProcessor;
120:
121: /** Request logger instance. */
122: private RequestLogger requestLogger;
123:
124: /**
125: * the prefix for storing variables in the actual session (typically
126: * {@link HttpSession} for this application instance.
127: */
128: private String sessionAttributePrefix;
129:
130: /** Session factory for this web application */
131: private ISessionFactory sessionFactory = this ;
132:
133: /** The WicketServlet that this application is attached to */
134: private WicketServlet wicketServlet;
135:
136: /**
137: * Constructor. <strong>Use {@link #init()} for any configuration of your
138: * application instead of overriding the constructor.</strong>
139: */
140: public WebApplication() {
141: }
142:
143: /**
144: * @see wicket.Application#getApplicationKey()
145: */
146: public final String getApplicationKey() {
147: if (applicationKey == null) {
148: throw new IllegalStateException(
149: "the application key does not seem to"
150: + " be set properly or this method is called before WicketServlet is"
151: + " set, which leads to the wrong behavior");
152: }
153: return applicationKey;
154: }
155:
156: /**
157: * Gets the default request cycle processor (with lazy initialization). This
158: * is the {@link IRequestCycleProcessor} that will be used by
159: * {@link RequestCycle}s when custom implementations of the request cycle
160: * do not provide their own customized versions.
161: *
162: * @return the default request cycle processor
163: */
164: public final IRequestCycleProcessor getRequestCycleProcessor() {
165: if (requestCycleProcessor == null) {
166: requestCycleProcessor = newRequestCycleProcessor();
167: }
168: return requestCycleProcessor;
169: }
170:
171: /**
172: * Gets the {@link RequestLogger}.
173: *
174: * @return The RequestLogger
175: */
176: public final RequestLogger getRequestLogger() {
177: return requestLogger;
178: }
179:
180: /**
181: * Gets the prefix for storing variables in the actual session (typically
182: * {@link HttpSession} for this application instance.
183: *
184: * @param request
185: * the request
186: *
187: * @return the prefix for storing variables in the actual session
188: */
189: public final String getSessionAttributePrefix(
190: final WebRequest request) {
191: if (sessionAttributePrefix == null) {
192: String servletPath = request.getServletPath();
193: if (servletPath == null) {
194: throw new WicketRuntimeException(
195: "unable to retrieve servlet path");
196: }
197: sessionAttributePrefix = "wicket:" + servletPath + ":";
198: }
199: // Namespacing for session attributes is provided by
200: // adding the servlet path
201: return sessionAttributePrefix;
202: }
203:
204: /**
205: * @return The Wicket servlet for this application
206: */
207: public final WicketServlet getWicketServlet() {
208: if (wicketServlet == null) {
209: throw new IllegalStateException(
210: "wicketServlet is not set yet. Any code in your"
211: + " Application object that uses the wicketServlet instance should be put"
212: + " in the init() method instead of your constructor");
213: }
214: return wicketServlet;
215: }
216:
217: /**
218: * @see wicket.Application#logEventTarget(wicket.IRequestTarget)
219: */
220: public void logEventTarget(IRequestTarget target) {
221: super .logEventTarget(target);
222: RequestLogger rl = getRequestLogger();
223: if (rl != null) {
224: rl.logEventTarget(target);
225: }
226: }
227:
228: /**
229: * @see wicket.Application#logResponseTarget(wicket.IRequestTarget)
230: */
231: public void logResponseTarget(IRequestTarget target) {
232: super .logResponseTarget(target);
233: RequestLogger rl = getRequestLogger();
234: if (rl != null) {
235: rl.logResponseTarget(target);
236: }
237: }
238:
239: /**
240: * Mounts an encoder at the given path.
241: *
242: * @param path
243: * the path to mount the encoder on
244: * @param encoder
245: * the encoder that will be used for this mount
246: */
247: public final void mount(String path,
248: IRequestTargetUrlCodingStrategy encoder) {
249: checkMountPath(path);
250:
251: if (encoder == null) {
252: throw new IllegalArgumentException(
253: "Encoder must be not null");
254: }
255:
256: getRequestCycleProcessor().getRequestCodingStrategy().mount(
257: path, encoder);
258: }
259:
260: /**
261: * Mounts all bookmarkable pages at the given path.
262: *
263: * @param path
264: * the path to mount the bookmarkable page class on
265: * @param packageName
266: * the name of the package for which all bookmarkable pages or
267: * sharedresources should be mounted
268: */
269: public final void mount(final String path,
270: final PackageName packageName) {
271: checkMountPath(path);
272: if (packageName == null) {
273: throw new IllegalArgumentException(
274: "PackageName cannot be null");
275: }
276: mount(path, new PackageRequestTargetUrlCodingStrategy(path,
277: packageName));
278: }
279:
280: /**
281: * Mounts a bookmarkable page class to the given path.
282: *
283: * @param path
284: * the path to mount the bookmarkable page class on
285: * @param bookmarkablePageClass
286: * the bookmarkable page class to mount
287: */
288: public final void mountBookmarkablePage(final String path,
289: final Class bookmarkablePageClass) {
290: checkMountPath(path);
291: mount(path, new BookmarkablePageRequestTargetUrlCodingStrategy(
292: path, bookmarkablePageClass, null));
293: }
294:
295: /**
296: * Mounts a bookmarkable page class to the given pagemap and path.
297: *
298: * @param path
299: * the path to mount the bookmarkable page class on
300: * @param pageMap
301: * pagemap this mount is for
302: * @param bookmarkablePageClass
303: * the bookmarkable page class to mount
304: * @deprecated You won't be able to use this mount. Use
305: * {@link #mountBookmarkablePage(String, String, Class)} instead
306: */
307: public final void mountBookmarkablePage(final String path,
308: final PageMap pageMap, final Class bookmarkablePageClass) {
309: checkMountPath(path);
310: mount(path, new BookmarkablePageRequestTargetUrlCodingStrategy(
311: path, bookmarkablePageClass, pageMap.getName()));
312: }
313:
314: /**
315: * Mounts a bookmarkable page class to the given pagemap and path.
316: *
317: * @param path
318: * the path to mount the bookmarkable page class on
319: * @param pageMapName
320: * name of the pagemap this mount is for
321: * @param bookmarkablePageClass
322: * the bookmarkable page class to mount
323: */
324: public final void mountBookmarkablePage(final String path,
325: final String pageMapName, final Class bookmarkablePageClass) {
326: checkMountPath(path);
327: mount(path, new BookmarkablePageRequestTargetUrlCodingStrategy(
328: path, bookmarkablePageClass, pageMapName));
329: }
330:
331: /**
332: * Mounts a shared resource class to the given path.
333: *
334: * @param path
335: * the path to mount the bookmarkable page class on
336: * @param resourceKey
337: * the shared key of the resource being mounted
338: */
339: public final void mountSharedResource(final String path,
340: final String resourceKey) {
341: checkMountPath(path);
342: mount(path, new SharedResourceRequestTargetUrlCodingStrategy(
343: path, resourceKey));
344: }
345:
346: /**
347: * Create new Wicket Session object. Note, this method is not called if you
348: * registered your own ISessionFactory with the Application.
349: *
350: * @see wicket.ISessionFactory#newSession()
351: */
352: public Session newSession() {
353: return new WebSession(WebApplication.this );
354: }
355:
356: /**
357: * @param sessionId
358: * The session id that was destroyed
359: */
360: public void sessionDestroyed(String sessionId) {
361: bufferedResponses.remove(sessionId);
362:
363: RequestLogger logger = getRequestLogger();
364: if (logger != null) {
365: logger.sessionDestroyed(sessionId);
366: }
367: }
368:
369: /**
370: * Sets the {@link RequestLogger}.
371: *
372: * @param logger
373: * The request logger
374: */
375: public final void setRequestLogger(RequestLogger logger) {
376: requestLogger = logger;
377: }
378:
379: /**
380: * @param sessionFactory
381: * The session factory to use
382: */
383: public final void setSessionFactory(
384: final ISessionFactory sessionFactory) {
385: this .sessionFactory = sessionFactory;
386: }
387:
388: /**
389: * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT.
390: *
391: * @param wicketServlet
392: * The wicket servlet instance for this application
393: * @throws IllegalStateException
394: * If an attempt is made to call this method once the wicket
395: * servlet has been set for the application.
396: */
397: public final void setWicketServlet(final WicketServlet wicketServlet) {
398: if (this .wicketServlet == null) {
399: this .wicketServlet = wicketServlet;
400: this .applicationKey = wicketServlet.getServletName();
401: } else {
402: throw new IllegalStateException(
403: "WicketServlet cannot be changed once it is set");
404: }
405: }
406:
407: /**
408: * Unmounts whatever encoder is mounted at a given path.
409: *
410: * @param path
411: * the path of the encoder to unmount
412: */
413: public final void unmount(String path) {
414: checkMountPath(path);
415: getRequestCycleProcessor().getRequestCodingStrategy().unmount(
416: path);
417: }
418:
419: /**
420: * Create a request cycle factory which is used by default by WebSession.
421: * You may provide your own default factory by subclassing WebApplication
422: * and overriding this method or your may subclass WebSession to create a
423: * session specific request cycle factory.
424: *
425: * @see WebSession#getRequestCycleFactory()
426: * @see IRequestCycleFactory
427: *
428: * @return Request cycle factory
429: */
430: protected IRequestCycleFactory getDefaultRequestCycleFactory() {
431: return new IRequestCycleFactory() {
432: private static final long serialVersionUID = 1L;
433:
434: public RequestCycle newRequestCycle(Session session,
435: Request request, Response response) {
436: // Respond to request
437: return new WebRequestCycle((WebSession) session,
438: (WebRequest) request, (WebResponse) response);
439: }
440: };
441: }
442:
443: /**
444: * @see wicket.Application#getSessionFactory()
445: */
446: protected ISessionFactory getSessionFactory() {
447: return this .sessionFactory;
448: }
449:
450: /**
451: * Initialize; if you need the wicket servlet for initialization, e.g.
452: * because you want to read an initParameter from web.xml or you want to
453: * read a resource from the servlet's context path, you can override this
454: * method and provide custom initialization. This method is called right
455: * after this application class is constructed, and the wicket servlet is
456: * set. <strong>Use this method for any application setup instead of the
457: * constructor.</strong>
458: */
459: protected void init() {
460: }
461:
462: /**
463: * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT.
464: */
465: protected void internalDestroy() {
466: ModificationWatcher resourceWatcher = getResourceSettings()
467: .getResourceWatcher();
468: if (resourceWatcher != null)
469: resourceWatcher.destroy();
470: super .internalDestroy();
471: bufferedResponses.clear();
472: // destroy the resource watcher
473: }
474:
475: /**
476: * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT.
477: *
478: * Internal intialization. First determine the deployment mode. First check
479: * the system property -Dwicket.configuration. If it does not exist check
480: * the servlet init parameter (
481: * <code><init-param><param-name>configuration</param-name></code>).
482: * If not found check the servlet context init paramert
483: * <code><context-param><param-name6gt;configuration</param-name></code>).
484: * If the parameter is "development" (which is default), settings
485: * appropriate for development are set. If it's "deployment" , deployment
486: * settings are used. If development is specified and a "sourceFolder" init
487: * parameter is also set, then resources in that folder will be polled for
488: * changes.
489: */
490: protected void internalInit() {
491: super .internalInit();
492:
493: // Set default error pages for HTML markup
494: getApplicationSettings().setPageExpiredErrorPage(
495: PageExpiredErrorPage.class);
496: getApplicationSettings().setInternalErrorPage(
497: InternalErrorPage.class);
498: getApplicationSettings().setAccessDeniedPage(
499: AccessDeniedPage.class);
500:
501: // Add resolver for automatically resolving HTML links
502: getPageSettings().addComponentResolver(new AutoLinkResolver());
503:
504: // Set resource finder to web app path
505: getResourceSettings().setResourceFinder(
506: new WebApplicationPath(getWicketServlet()
507: .getServletContext()));
508:
509: String contextPath = wicketServlet
510: .getInitParameter(Application.CONTEXTPATH);
511: if (contextPath != null) {
512: getApplicationSettings().setContextPath(contextPath);
513: }
514:
515: // Check if system property -Dwicket.configuration exists
516: String configuration = null;
517: try {
518: configuration = System.getProperty("wicket."
519: + Application.CONFIGURATION);
520: } catch (SecurityException e) {
521: // ignore; it is not allowed to read system properties
522: }
523:
524: // If no system parameter check servlet specific <init-param>
525: if (configuration == null) {
526: configuration = wicketServlet
527: .getInitParameter(Application.CONFIGURATION);
528: }
529: // If no system parameter and not <init-param>, than check
530: // <context-param>
531: if (configuration == null) {
532: configuration = wicketServlet.getServletContext()
533: .getInitParameter(Application.CONFIGURATION);
534: }
535:
536: // Development mode is default if not settings have been found
537: if (configuration != null) {
538: configure(configuration, wicketServlet
539: .getInitParameter("sourceFolder"));
540: } else {
541: configure(Application.DEVELOPMENT, wicketServlet
542: .getInitParameter("sourceFolder"));
543: }
544: }
545:
546: /**
547: * May be replaced by subclasses which whishes to uses there own
548: * implementation of IRequestCycleProcessor
549: *
550: * @return IRequestCycleProcessor
551: */
552: // TODO Doesn't this method belong in Application, not WebApplication?
553: protected IRequestCycleProcessor newRequestCycleProcessor() {
554: return new DefaultWebRequestCycleProcessor();
555: }
556:
557: /**
558: * @see wicket.Application#newSessionStore()
559: */
560: protected ISessionStore newSessionStore() {
561: return new HttpSessionStore();
562: }
563:
564: /**
565: * Create a new WebRequest. Subclasses of WebRequest could e.g. decode and
566: * obfuscated URL which has been encoded by an appropriate WebResponse.
567: *
568: * @param servletRequest
569: * @return a WebRequest object
570: */
571: protected WebRequest newWebRequest(
572: final HttpServletRequest servletRequest) {
573: return new ServletWebRequest(servletRequest);
574: }
575:
576: /**
577: * Create a WebResponse. Subclasses of WebRequest could e.g. encode wicket's
578: * default URL and hide the details from the user. A appropriate WebRequest
579: * must be implemented and configured to decode the encoded URL.
580: *
581: * @param servletResponse
582: * @return a WebResponse object
583: */
584: protected WebResponse newWebResponse(
585: final HttpServletResponse servletResponse) {
586: return (getRequestCycleSettings().getBufferResponse() ? new BufferedWebResponse(
587: servletResponse)
588: : new WebResponse(servletResponse));
589: }
590:
591: /*
592: * Set the application key value
593: */
594: protected final void setApplicationKey(String applicationKey) {
595: this .applicationKey = applicationKey;
596: }
597:
598: /**
599: * Add a buffered response to the redirect buffer.
600: *
601: * @param sessionId
602: * the session id
603: * @param bufferId
604: * the id that should be used for storing the buffer
605: * @param renderedResponse
606: * the response to buffer
607: */
608: final void addBufferedResponse(String sessionId, String bufferId,
609: BufferedHttpServletResponse renderedResponse) {
610: Map responsesPerSession = (Map) bufferedResponses
611: .get(sessionId);
612: if (responsesPerSession == null) {
613: responsesPerSession = new MostRecentlyUsedMap(4);
614: bufferedResponses.put(sessionId, responsesPerSession);
615: }
616: responsesPerSession.put(bufferId, renderedResponse);
617: }
618:
619: /**
620: * Gets a WebSession object from the HttpServletRequest, creating a new one
621: * if it doesn't already exist.
622: *
623: * @param request
624: * The http request object
625: * @return The session object
626: */
627: final WebSession getSession(final WebRequest request) {
628: ISessionStore sessionStore = getSessionStore();
629: Session session = sessionStore.lookup(request);
630:
631: if (session == null) {
632: // Create session using session factory
633: session = getSessionFactory().newSession();
634: // Set the client Locale for this session
635: session.setLocale(request.getLocale());
636:
637: if (sessionStore.getSessionId(request) != null) {
638: // Bind the session to the session store
639: sessionStore.bind(request, session);
640: }
641: }
642:
643: WebSession webSession;
644: if (session instanceof WebSession) {
645: webSession = (WebSession) session;
646: } else {
647: throw new WicketRuntimeException(
648: "Session created by a WebApplication session factory "
649: + "must be a subclass of WebSession");
650: }
651:
652: // Set application on session
653: session.setApplication(this );
654:
655: // Set session attribute name and attach/reattach http servlet session
656: webSession.initForRequest();
657:
658: return webSession;
659: }
660:
661: /**
662: * Log that this application is started.
663: */
664: final void logStarted() {
665: String version = getFrameworkSettings().getVersion();
666: StringBuffer b = new StringBuffer();
667: b.append("[").append(getName()).append("] Started Wicket ");
668: if (!"n/a".equals(version)) {
669: b.append("version ").append(version).append(" ");
670: }
671: b.append("in ").append(getConfigurationType()).append(" mode");
672: log.info(b.toString());
673: }
674:
675: /**
676: * Returns the redirect map where the buffered render pages are stored in
677: * and removes it immediately.
678: *
679: * @param sessionId
680: * the session id
681: *
682: * @param bufferId
683: * the id of the buffer as passed in as a request parameter
684: * @return the buffered response or null if not found (when this request is
685: * on a different box than the original request came in
686: */
687: final BufferedHttpServletResponse popBufferedResponse(
688: String sessionId, String bufferId) {
689: Map responsesPerSession = (Map) bufferedResponses
690: .get(sessionId);
691: if (responsesPerSession != null) {
692: BufferedHttpServletResponse buffered = (BufferedHttpServletResponse) responsesPerSession
693: .remove(bufferId);
694: if (responsesPerSession.size() == 0) {
695: bufferedResponses.remove(sessionId);
696: }
697: return buffered;
698: }
699: return null;
700: }
701:
702: /**
703: * Checks mount path is valid.
704: *
705: * @param path
706: * mount path
707: */
708: private void checkMountPath(String path) {
709: if (path == null) {
710: throw new IllegalArgumentException(
711: "Mount path cannot be null");
712: }
713: if (!path.startsWith("/")) {
714: throw new IllegalArgumentException(
715: "Mount path has to start with '/'");
716: }
717: if (path.startsWith("/resources/") || path.equals("/resources")) {
718: throw new IllegalArgumentException(
719: "Mount path cannot start with '/resources'");
720: }
721: }
722: }
|