001: /*
002: * This file is part of the Echo Web Application Framework (hereinafter "Echo").
003: * Copyright (C) 2002-2005 NextApp, Inc.
004: *
005: * Version: MPL 1.1/GPL 2.0/LGPL 2.1
006: *
007: * The contents of this file are subject to the Mozilla Public License Version
008: * 1.1 (the "License"); you may not use this file except in compliance with
009: * the License. You may obtain a copy of the License at
010: * http://www.mozilla.org/MPL/
011: *
012: * Software distributed under the License is distributed on an "AS IS" basis,
013: * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
014: * for the specific language governing rights and limitations under the
015: * License.
016: *
017: * Alternatively, the contents of this file may be used under the terms of
018: * either the GNU General Public License Version 2 or later (the "GPL"), or
019: * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
020: * in which case the provisions of the GPL or the LGPL are applicable instead
021: * of those above. If you wish to allow use of your version of this file only
022: * under the terms of either the GPL or the LGPL, and not to allow others to
023: * use your version of this file under the terms of the MPL, indicate your
024: * decision by deleting the provisions above and replace them with the notice
025: * and other provisions required by the GPL or the LGPL. If you do not delete
026: * the provisions above, a recipient may use your version of this file under
027: * the terms of any one of the MPL, the GPL or the LGPL.
028: */
029:
030: package nextapp.echo2.webrender;
031:
032: import java.io.IOException;
033:
034: import javax.servlet.ServletException;
035: import javax.servlet.http.HttpServlet;
036: import javax.servlet.http.HttpServletRequest;
037: import javax.servlet.http.HttpServletResponse;
038:
039: import nextapp.echo2.webrender.service.CoreServices;
040: import nextapp.echo2.webrender.service.DebugPaneService;
041:
042: /**
043: * Echo <code>HttpServlet</code> implementation.
044: */
045: public abstract class WebRenderServlet extends HttpServlet {
046:
047: /**
048: * A <code>ThreadLocal</code> reference to the
049: * <code>Connection</code> relevant to the current thread.
050: */
051: private static final ThreadLocal activeConnection = new InheritableThreadLocal();
052:
053: /**
054: * A flag indicating whether caching should be disabled for all services.
055: * This flag is for testing purposes only, and should be disabled for
056: * production use.
057: */
058: public static final boolean DISABLE_CACHING = false;
059:
060: /**
061: * Request parameter identifying requested <code>Service</code>.
062: */
063: public static final String SERVICE_ID_PARAMETER = "serviceId";
064:
065: /**
066: * <code>Service</code> identifier of the 'default' service.
067: * The 'default' service is rendered when a client makes a request
068: * without a service identifier and a session DOES exist.
069: */
070: public static final String SERVICE_ID_DEFAULT = "Echo.Default";
071:
072: /**
073: * <code>Service</code> identifier of the 'new instance' service.
074: * The 'new instance' service is rendered when a client makes a request
075: * without a service identifier and a session DOES NOT exist..
076: */
077: public static final String SERVICE_ID_NEW_INSTANCE = "Echo.NewInstance";
078:
079: /**
080: * <code>Service</code> identifier of the 'session expired' service.
081: * The 'session expired' service is rendered when a client makes a
082: * request that has an identifier and is intended for an active session,
083: * but no session exists.
084: */
085: public static final String SERVICE_ID_SESSION_EXPIRED = "Echo.Expired";
086:
087: /**
088: * Global handler for multipart/form-data encoded HTTP requests.
089: */
090: private static MultipartRequestWrapper multipartRequestWrapper;
091:
092: private static final long startupTime = System.currentTimeMillis();
093:
094: /**
095: * Global <code>ServiceRegistry</code>.
096: */
097: private static final ServiceRegistry services = new ServiceRegistry();
098:
099: static {
100: CoreServices.install(services);
101: services.add(DebugPaneService.INSTANCE);
102: }
103:
104: /**
105: * An interface implemented by a supporting object that will handle
106: * multipart/form-data encoded HTTP requests. This type of request is
107: * required for file uploads. Echo does not provide internal support
108: * for file uploads, but instead provides hooks for file-upload handling
109: * components.
110: */
111: public static interface MultipartRequestWrapper {
112:
113: /**
114: * Returns a replacement <code>HttpServletRequest</code> object that
115: * may be used to handle a multipart/form-data encoded HTTP request.
116: *
117: * @param request The HTTP request provided from the servlet container
118: * that has multipart/form-data encoding.
119: * @return An HTTP request that is capable of handling
120: * multipart/form-data encoding.
121: */
122: public HttpServletRequest getWrappedRequest(
123: HttpServletRequest request) throws IOException,
124: ServletException;
125: }
126:
127: /**
128: * Returns a reference to the <code>Connection</code> that is
129: * relevant to the current thread, or null if no connection is relevant.
130: *
131: * @return the relevant <code>Connection</code>
132: */
133: public static final Connection getActiveConnection() {
134: return (Connection) activeConnection.get();
135: }
136:
137: /**
138: * Returns the multipart/form-data encoded HTTP request handler.
139: *
140: * @return The multipart/form-data encoded HTTP request handler.
141: * @see #setMultipartRequestWrapper
142: */
143: public static MultipartRequestWrapper getMultipartRequestWrapper() {
144: return multipartRequestWrapper;
145: }
146:
147: /**
148: * Sets the multipart/form-data encoded HTTP request handler.
149: * The multipart request wrapper can only be set one time. It should be set
150: * in a static block of your Echo application. This method will disregard
151: * additional attempts to set the wrapper if the provided wrapper's class
152: * is identical to the existing one. If the wrapper is already set and the
153: * new wrapper object's class is different or the wrapper is null, an
154: * exception is thrown.
155: *
156: * @param multipartRequestWrapper The handler for multipart/form-data
157: * encoded HTTP requests.
158: * @throws IllegalStateException if the application attempts to change
159: * a previously set multipart request handler.
160: */
161: public static final void setMultipartRequestWrapper(
162: MultipartRequestWrapper multipartRequestWrapper) {
163: if (WebRenderServlet.multipartRequestWrapper == null) {
164: WebRenderServlet.multipartRequestWrapper = multipartRequestWrapper;
165: } else {
166: if (multipartRequestWrapper == null
167: || !WebRenderServlet.multipartRequestWrapper
168: .getClass().getName().equals(
169: multipartRequestWrapper.getClass()
170: .getName())) {
171: throw new IllegalStateException(
172: "MultipartRequestWrapper already set.");
173: }
174: }
175: }
176:
177: /**
178: * Handles a GET request.
179: *
180: * @see #process(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
181: */
182: public final void doGet(HttpServletRequest request,
183: HttpServletResponse response) throws IOException,
184: ServletException {
185: process(request, response);
186: }
187:
188: /**
189: * Handles a POST request.
190: *
191: * @see #process(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
192: */
193: public final void doPost(HttpServletRequest request,
194: HttpServletResponse response) throws IOException,
195: ServletException {
196: process(request, response);
197: }
198:
199: /**
200: * Returns the service that corresponds to the specified Id.
201: *
202: * @param id The id of the service to return.
203: * @return The service corresponding to the specified Id.
204: */
205: private static Service getService(UserInstance userInstance,
206: String id) {
207: Service service;
208:
209: service = services.get(id);
210: if (id == null) {
211: if (userInstance == null) {
212: id = SERVICE_ID_NEW_INSTANCE;
213: } else {
214: id = SERVICE_ID_DEFAULT;
215: }
216: } else {
217: if (userInstance == null) {
218: id = SERVICE_ID_SESSION_EXPIRED;
219: }
220: }
221:
222: service = services.get(id);
223:
224: if (service == null) {
225: if (SERVICE_ID_DEFAULT.equals(id)) {
226: throw new RuntimeException(
227: "Service not registered: SERVICE_ID_DEFAULT");
228: } else if (SERVICE_ID_NEW_INSTANCE.equals(id)) {
229: throw new RuntimeException(
230: "Service not registered: SERVICE_ID_NEW_INSTANCE");
231: } else if (SERVICE_ID_SESSION_EXPIRED.equals(id)) {
232: throw new RuntimeException(
233: "Service not registered: SERVICE_ID_SESSION_EXPIRED");
234: }
235: }
236:
237: return service;
238: }
239:
240: /**
241: * Retrieves the global <code>ServiceRegistry</code>.
242: *
243: * @return The global <code>ServiceRegistry</code>.
244: */
245: public static ServiceRegistry getServiceRegistry() {
246: return services;
247: }
248:
249: /**
250: * Processes a HTTP request and generates a response.
251: *
252: * @param request the incoming <code>HttpServletRequest</code>
253: * @param response the outgoing <code>HttpServletResponse</code>
254: */
255: protected void process(HttpServletRequest request,
256: HttpServletResponse response) throws IOException,
257: ServletException {
258: Connection conn = null;
259: try {
260: conn = new Connection(this , request, response);
261: activeConnection.set(conn);
262: String serviceId = request
263: .getParameter(SERVICE_ID_PARAMETER);
264: Service service = getService(conn.getUserInstance(),
265: serviceId);
266: if (service == null) {
267: throw new ServletException("Service id \"" + serviceId
268: + "\" not registered.");
269: }
270: int version = service.getVersion();
271:
272: // Set caching directives.
273: if ((!DISABLE_CACHING) && version != Service.DO_NOT_CACHE) {
274: // Setting all of the following (possibly with the exception of "Expires")
275: // are *absolutely critical* in order to ensure proper caching of resources
276: // with Internet Explorer 6. Without "Last-Modified", IE6 appears to not
277: // cache images properly resulting in an substantially greater than expected
278: // performance impact.
279: response.setHeader("Cache-Control", "max-age=3600");
280: response.setDateHeader("Expires", System
281: .currentTimeMillis() + (86400000));
282: response.setDateHeader("Last-Modified", startupTime);
283: } else {
284: response.setHeader("Pragma", "no-cache");
285: response.setHeader("Cache-Control", "no-store");
286: response.setHeader("Expires", "0");
287: }
288:
289: service.service(conn);
290:
291: } catch (ServletException ex) {
292: if (conn != null) {
293: conn.disposeUserInstance();
294: }
295: throw (ex);
296: } catch (IOException ex) {
297: if (conn != null) {
298: conn.disposeUserInstance();
299: }
300: throw (ex);
301: } catch (RuntimeException ex) {
302: if (conn != null) {
303: conn.disposeUserInstance();
304: }
305: throw (ex);
306: } finally {
307: activeConnection.set(null);
308: }
309: }
310: }
|