001: /*
002: * Copyright 2000,2005 wingS development team.
003: *
004: * This file is part of wingS (http://wingsframework.org).
005: *
006: * wingS is free software; you can redistribute it and/or modify
007: * it under the terms of the GNU Lesser General Public License
008: * as published by the Free Software Foundation; either version 2.1
009: * of the License, or (at your option) any later version.
010: *
011: * Please see COPYING for the complete licence.
012: */
013: package org.wings.externalizer;
014:
015: import org.apache.commons.logging.Log;
016: import org.apache.commons.logging.LogFactory;
017: import org.wings.io.Device;
018: import org.wings.util.StringUtil;
019: import org.wings.resource.ResourceNotFoundException;
020:
021: import javax.servlet.http.HttpServletResponse;
022: import java.io.IOException;
023: import java.util.*;
024:
025: /**
026: * @author <a href="mailto:haaf@mercatis.de">Armin Haaf</a>
027: */
028: public abstract class AbstractExternalizeManager {
029: protected final static Log LOG = LogFactory
030: .getLog(AbstractExternalizeManager.class);
031:
032: /**
033: * The identifier generated, if the {@link ExternalizeManager} did not find
034: * an apropriate {@link Externalizer}.
035: */
036: public static final String NOT_FOUND_IDENTIFIER = "0";
037:
038: /*---------------------------------------------------------------
039: * The externalized ID is just a counter start starts with zero. This
040: * happens with each start of the server, and thus generates the same
041: * ID if we restart the application (especially, if we are in the
042: * development phase). Since we externalize the resource with a long
043: * caching timeout, the browser might not refetch a resource externalized
044: * in a fresh instance of the web-application, since the browser has cached
045: * it already.
046: * Thus, we need a unique prefix for each externalized resource, that
047: * changes with each start of the server.
048: * These static variables create a new ID every UNIQUE_TIMESLICE, which
049: * means, that, if we use a 2-character prefix, can offer the browser
050: * the timeframe of FINAL_EXPIRES for this resource to be cached (since
051: * after that time, we have an roll-over of the ID's).
052: *----------------------------------------------------------------*/
053:
054: /**
055: * in seconds
056: */
057: public final int UNIQUE_TIMESLICE = 20;
058:
059: /**
060: * in seconds; Computed from UNIQUE_TIMESLICE; do not change.
061: */
062: public final long FINAL_EXPIRES = (StringUtil.MAX_RADIX
063: * StringUtil.MAX_RADIX - 1)
064: * UNIQUE_TIMESLICE;
065:
066: /**
067: * Prefix for the externalized ID; long. Computed, do not change.
068: */
069: protected final long PREFIX_TIMESLICE = ((System
070: .currentTimeMillis() / 1000) % FINAL_EXPIRES)
071: / UNIQUE_TIMESLICE;
072:
073: // Flags
074:
075: /**
076: * for an externalized object with the final flag on the expired date
077: * header is set to a big value. If the final flag is off, the browser
078: * or proxy does not cache the object.
079: */
080: public static final int FINAL = 8;
081:
082: /**
083: * for an externalized object with the request flag on, the externalized
084: * object is removed from the {@link ExternalizeManager} after one request
085: * of the object.
086: */
087: public static final int REQUEST = 1;
088:
089: /**
090: * for an externalized object with the session flag on, the externalized
091: * object only available to requests within the session which created the
092: * object. The object is not accessible anymore after the session is
093: * destroyed (it is garbage collected after the session is garbage
094: * collected)
095: */
096: public static final int SESSION = 2;
097:
098: /**
099: * for an externalized object with the gobal flag on, the externalized
100: * object is available to all requests. Also it is never garbage collected
101: * and available for the lifecycle of the servlet container.
102: */
103: public static final int GLOBAL = 4;
104:
105: /**
106: * To generate the identifier for a externalized object.
107: */
108: private long counter = 0;
109:
110: /**
111: * To search for an already externalized object. This performs way better
112: * than search in the value set of the
113: * identifier-{@link ExternalizedResource} map.
114: */
115: protected final Map<ExternalizedResource, String> reverseExternalized;
116:
117: /**
118: * To support Session local externalizing, the {@link ExternalizeManager}
119: * needs to encode the session identifier of the servlet container in the
120: * URL of the externalized object. This is set in the constructor
121: * and should work (I hope so) with all servlet containers.
122: */
123: protected String sessionEncoding = "";
124:
125: /**
126: * String prefixed to every created externlizer identifier via {@link #createIdentifier()}
127: */
128: private String prefix;
129: private static final String FOO = "http://foo/foo";
130:
131: public AbstractExternalizeManager() {
132: if (LOG.isDebugEnabled()) {
133: LOG.debug("Externalizer scope using prefix" + prefix
134: + "expires in " + FINAL_EXPIRES + " seconds ");
135: }
136:
137: reverseExternalized = Collections
138: .synchronizedMap(new HashMap<ExternalizedResource, String>());
139: setPrefix(StringUtil.toShortestAlphaNumericString(
140: PREFIX_TIMESLICE, 2));
141: }
142:
143: public void setResponse(HttpServletResponse response) {
144: if (response != null) {
145: sessionEncoding = response.encodeURL(FOO).substring(
146: FOO.length());
147: }
148: }
149:
150: protected final synchronized long getNextIdentifier() {
151: return ++counter;
152: }
153:
154: /**
155: * String prefixed to every created externlizer identifier via {@link #createIdentifier()}
156: */
157: public String getPrefix() {
158: return prefix;
159: }
160:
161: /**
162: * String prefixed to every created externlizer identifier via {@link #createIdentifier()}
163: */
164: public void setPrefix(final String prefix) {
165: if (LOG.isDebugEnabled())
166: LOG.debug("Externalizer prefix changed from " + this .prefix
167: + " to " + prefix);
168: this .prefix = prefix;
169: }
170:
171: protected final String createIdentifier() {
172: return getPrefix()
173: + StringUtil
174: .toShortestAlphaNumericString(getNextIdentifier());
175: }
176:
177: /**
178: * store the {@link ExternalizedResource} in a map.
179: * The {@link ExternalizedResource} should later on accessible by the
180: * identifier {@link #getExternalizedResource}, {@link #removeExternalizedResource}
181: */
182: protected abstract void storeExternalizedResource(
183: String identifier, ExternalizedResource extInfo);
184:
185: /**
186: * get the {@link ExternalizedResource} by identifier.
187: *
188: * @return null, if not found!!
189: */
190: public abstract ExternalizedResource getExternalizedResource(
191: String identifier);
192:
193: /**
194: * removes the {@link ExternalizedResource} by identifier.
195: */
196: public abstract void removeExternalizedResource(String identifier);
197:
198: /**
199: * externalizes (make a java object available for a browser) an object with
200: * the given {@link Externalizer}. The object is externalized in the
201: * {@link #SESSION} scope.
202: *
203: * @return a URL for accessing the object relative to the base URL.
204: */
205: public String externalize(Object obj, Externalizer externalizer) {
206: return externalize(obj, externalizer, SESSION);
207: }
208:
209: /**
210: * externalizes (make a java object available for a browser) an object with
211: * the given {@link Externalizer}. If the given headers are !=null the
212: * headers overwrite the headers from the {@link Externalizer}.
213: * The object is externalized in the
214: * {@link #SESSION} scope.
215: *
216: * @return a URL for accessing the object relative to the base URL.
217: */
218: public String externalize(Object obj, Externalizer externalizer,
219: Collection headers) {
220: return externalize(obj, externalizer, headers, SESSION);
221: }
222:
223: /**
224: * externalizes (make a java object available for a browser) an object with
225: * the given {@link Externalizer}. Valid flags are (this may change, look
226: * also in the static variable section)
227: * <ul>
228: * <li>{@link #FINAL}</li>
229: * <li>{@link #REQUEST}</li>
230: * <li>{@link #SESSION}</li>
231: * <li>{@link #GLOBAL}</li>
232: * </ul>
233: *
234: * @return a URL for accessing the object relative to the base URL.
235: */
236: public String externalize(Object obj, Externalizer externalizer,
237: int flags) {
238: if (obj == null || externalizer == null)
239: throw new IllegalStateException("no externalizer");
240:
241: return externalize(obj, externalizer, null, null, flags);
242: }
243:
244: /**
245: * externalizes (make a java object available for a browser) an object with
246: * the given {@link Externalizer}. If the given headers are !=null the
247: * headers overwrite the headers from the {@link Externalizer}.
248: * Valid flags are (this may change, look
249: * also in the static variable section)
250: * <ul>
251: * <li>{@link #FINAL}</li>
252: * <li>{@link #REQUEST}</li>
253: * <li>{@link #SESSION}</li>
254: * <li>{@link #GLOBAL}</li>
255: * </ul>
256: *
257: * @return a URL for accessing the object relative to the base URL.
258: */
259: public String externalize(Object obj, Externalizer externalizer,
260: Collection headers, int flags) {
261: if (obj == null || externalizer == null)
262: throw new IllegalStateException("no externalizer");
263:
264: return externalize(obj, externalizer, null, headers, flags);
265: }
266:
267: /**
268: * externalizes (make a java object available for a browser) an object with
269: * the given {@link Externalizer}.
270: * If the mimeType!=null, mimeType overwrites the mimeType of the
271: * {@link Externalizer}.
272: * The object is externalized in the
273: * {@link #SESSION} scope.
274: *
275: * @return a URL for accessing the object relative to the base URL.
276: */
277: public String externalize(Object obj, Externalizer externalizer,
278: String mimeType) {
279: return externalize(obj, externalizer, mimeType, null, SESSION);
280: }
281:
282: /**
283: * externalizes (make a java object available for a browser) an object with
284: * the given {@link Externalizer}.
285: * If the mimeType!=null, mimeType overwrites the mimeType of the
286: * {@link Externalizer}.
287: * If the given headers are !=null the
288: * headers overwrite the headers from the {@link Externalizer}.
289: *
290: * @return a URL for accessing the object relative to the base URL.
291: */
292: public String externalize(Object obj, Externalizer externalizer,
293: String mimeType, Collection headers) {
294: return externalize(obj, externalizer, mimeType, headers,
295: SESSION);
296: }
297:
298: /**
299: * externalizes (make a java object available for a browser) an object with
300: * the given {@link Externalizer}.
301: * If the mimeType!=null, mimeType overwrites the mimeType of the
302: * {@link Externalizer}.
303: * If the given headers are !=null the
304: * headers overwrite the headers from the {@link Externalizer}.
305: * Valid flags are (this may change, look
306: * also in the static variable section)
307: * <ul>
308: * <li>{@link #FINAL}</li>
309: * <li>{@link #REQUEST}</li>
310: * <li>{@link #SESSION}</li>
311: * <li>{@link #GLOBAL}</li>
312: * </ul>
313: *
314: * @return a URL for accessing the object relative to the base URL.
315: */
316: public String externalize(Object obj, Externalizer externalizer,
317: String mimeType, Collection headers, int flags) {
318: if (externalizer == null) {
319: throw new IllegalStateException("no externalizer");
320: }
321: ExternalizedResource extInfo = new ExternalizedResource(obj,
322: externalizer, mimeType, headers, flags);
323:
324: extInfo.setId(externalizer.getId(obj));
325: if ((flags & GLOBAL) > 0) {
326: // session encoding is not necessary here
327: return SystemExternalizeManager.getSharedInstance()
328: .externalize(extInfo);
329: } else {
330: return externalize(extInfo);
331: }
332: }
333:
334: /**
335: * externalizes (make a java object available for a browser) the object in
336: * extInfo.
337: *
338: * @return a URL for accessing the externalized object relative to the base URL.
339: */
340: public String externalize(ExternalizedResource extInfo) {
341: String identifier = (String) reverseExternalized.get(extInfo);
342:
343: if (identifier == null) {
344: identifier = extInfo.getId();
345: if (identifier != null)
346: identifier = "-" + identifier;
347: else
348: identifier = createIdentifier();
349:
350: String extension = extInfo.getExtension();
351: if (extension != null)
352: identifier += ("." + extension);
353:
354: extInfo.setId(identifier);
355: storeExternalizedResource(identifier, extInfo);
356: reverseExternalized.put(extInfo, identifier);
357: }
358:
359: return identifier + sessionEncoding;
360: }
361:
362: /**
363: * externalizes (make a java object available for a browser) the object in
364: * extInfo.
365: *
366: * @return a URL for accessing the externalized object relative to the base URL.
367: */
368: public String getId(String url) {
369: if (url == null || url.length() == 0) {
370: return url;
371: }
372: String result;
373: if (url.charAt(0) == '-') {
374: result = url;
375: } else {
376: result = url.substring(0, url.length()
377: - sessionEncoding.length());
378: }
379: return result;
380: }
381:
382: /**
383: * delivers a externalized object identfied with the given identifier to a
384: * client.
385: * It sends an error (404), if the identifier is not registered.
386: */
387: public void deliver(String identifier,
388: HttpServletResponse response, Device out)
389: throws IOException {
390: ExternalizedResource extInfo = getExternalizedResource(identifier);
391:
392: if (extInfo == null) {
393: LOG.warn("identifier " + identifier + " not found");
394: response.sendError(HttpServletResponse.SC_NOT_FOUND);
395: return;
396: }
397: deliver(extInfo, response, out);
398: }
399:
400: public void deliver(ExternalizedResource extInfo,
401: HttpServletResponse response, Device out)
402: throws IOException {
403: /* FIXME: re-implement.
404: if ( extInfo.deliverOnce() ) {
405: removeExternalizedResource(identifier);
406: }
407: */
408:
409: if (extInfo.getMimeType() != null) {
410: response.setContentType(extInfo.getMimeType());
411: }
412:
413: // FIXME find out, if this is correct: if the content length
414: // is not size preserving (like a gzip-device), then we must not
415: // send the content size we know..
416: if (out.isSizePreserving()) {
417: int resourceLen = extInfo.getExternalizer().getLength(
418: extInfo.getObject());
419: if (resourceLen > 0) {
420: LOG.debug(extInfo.getMimeType() + ": " + resourceLen);
421: response.setContentLength(resourceLen);
422: }
423: }
424:
425: Collection headers = extInfo.getHeaders();
426: if (headers != null) {
427: for (Object header : headers) {
428: Map.Entry entry = (Map.Entry) header;
429: if (entry.getValue() instanceof String) {
430: response.addHeader((String) entry.getKey(),
431: (String) entry.getValue());
432: } else if (entry.getValue() instanceof Date) {
433: response.addDateHeader((String) entry.getKey(),
434: ((Date) entry.getValue()).getTime());
435:
436: } else if (entry.getValue() instanceof Integer) {
437: response.addIntHeader((String) entry.getKey(),
438: ((Integer) entry.getValue()).intValue());
439:
440: } // end of if ()
441: }
442: }
443:
444: if (!response.containsHeader("Expires")) {
445: /*
446: * This would be the correct way to do it; alas, that means, that
447: * for static resources, after a day or so, no caching could take
448: * place, since the last modification was at the first time, the
449: * resource was externalized (since it doesn't change).
450: * .. have to think about it.
451: */
452: //response.setDateHeader("Expires",
453: // (1000*FINAL_EXPIRES)
454: // + extInfo.getLastModified());
455: // .. so do this for now, which is the best approximation of what
456: // we want.
457: response.setDateHeader("Expires", System
458: .currentTimeMillis()
459: + (1000 * FINAL_EXPIRES));
460: }
461:
462: try {
463: extInfo.getExternalizer().write(extInfo.getObject(), out);
464: } catch (ResourceNotFoundException e) {
465: LOG.debug("Unable to deliver resource due to: "
466: + e.getMessage() + ". Sending 404!");
467: response.reset();
468: response.sendError(404, e.getMessage());
469: }
470: out.flush();
471: }
472:
473: public void clear() {
474: reverseExternalized.clear();
475: }
476: }
|