All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.wings.externalizer.AbstractExternalizeManager Maven / Gradle / Ivy

/*
 * Copyright 2000,2005 wingS development team.
 *
 * This file is part of wingS (http://wingsframework.org).
 *
 * wingS is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1
 * of the License, or (at your option) any later version.
 *
 * Please see COPYING for the complete licence.
 */
package org.wings.externalizer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.wings.io.Device;
import org.wings.util.StringUtil;
import org.wings.resource.ResourceNotFoundException;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;

/**
 * @author Armin Haaf
 */
public abstract class AbstractExternalizeManager {
    protected final static Logger LOG = LoggerFactory.getLogger(AbstractExternalizeManager.class);

    /**
     * The identifier generated, if the {@link ExternalizeManager} did not find
     * an apropriate {@link Externalizer}.
     */
    public static final String NOT_FOUND_IDENTIFIER = "0";


    /*---------------------------------------------------------------
     * The externalized ID is just a counter start starts with zero. This
     * happens with each start of the server, and thus generates the same
     * ID if we restart the application (especially, if we are in the
     * development phase). Since we externalize the resource with a long
     * caching timeout, the browser might not refetch a resource externalized
     * in a fresh instance of the web-application, since the browser has cached
     * it already.
     * Thus, we need a unique prefix for each externalized resource, that
     * changes with each start of the server.
     * These static variables create a new ID every UNIQUE_TIMESLICE, which
     * means, that, if we use a 2-character prefix, can offer the browser
     * the timeframe of FINAL_EXPIRES for this resource to be cached (since
     * after that time, we have an roll-over of the ID's).
     *----------------------------------------------------------------*/

    /**
     * in seconds
     */
    public static final int UNIQUE_TIMESLICE = 20;

    /**
     * in seconds; Computed from UNIQUE_TIMESLICE; do not change.
     */
    public final long FINAL_EXPIRES =
            (StringUtil.MAX_RADIX * StringUtil.MAX_RADIX - 1) * UNIQUE_TIMESLICE;

    /**
     * Prefix for the externalized ID; long. Computed, do not change.
     */
    protected final long PREFIX_TIMESLICE =
            ((System.currentTimeMillis() / 1000) % FINAL_EXPIRES) / UNIQUE_TIMESLICE;

    // Flags

    /**
     * for an externalized object with the final flag on the expired date
     * header is set to a big value. If the final flag is off, the browser
     * or proxy does not cache the object.
     */
    public static final int FINAL = 8;

    /**
     * for an externalized object with the request flag on, the externalized
     * object is removed from the {@link ExternalizeManager} after one request
     * of the object.
     */
    public static final int REQUEST = 1;

    /**
     * for an externalized object with the session flag on, the externalized
     * object only available to requests within the session which created the
     * object. The object is not accessible anymore after the session is
     * destroyed (it is garbage collected after the session is garbage
     * collected)
     */
    public static final int SESSION = 2;

    /**
     * for an externalized object with the gobal flag on, the externalized
     * object is available to all requests. Also it is never garbage collected
     * and available for the lifecycle of the servlet container.
     */
    public static final int GLOBAL = 4;

    /**
     * To generate the identifier for a externalized object.
     */
    private long counter = 0;

    /**
     * To search for an already externalized object. This performs way better
     * than search in the value set of the
     * identifier-{@link ExternalizedResource} map.
     */
    protected final Map reverseExternalized;

    /**
     * To support Session local externalizing, the {@link ExternalizeManager}
     * needs to encode the session identifier of the servlet container in the
     * URL of the externalized object. This is set in the constructor
     * and should work (I hope so) with all servlet containers.
     */
    protected String sessionEncoding = "";

    /**
     * String prefixed to every created externlizer identifier via {@link #createIdentifier()}
     */
    private String prefix;
    private static final String FOO = "http://foo/foo";


    public AbstractExternalizeManager() {
        if (LOG.isDebugEnabled()) {
            LOG.debug("Externalizer scope using prefix" + prefix + "expires in " + FINAL_EXPIRES + " seconds ");
        }

        reverseExternalized = Collections.synchronizedMap(new HashMap());
        setPrefix(StringUtil.toShortestAlphaNumericString(PREFIX_TIMESLICE, 2));
    }

    public void setResponse(HttpServletResponse response) {
        if (response != null) {
            sessionEncoding = response.encodeURL(FOO).substring(FOO.length());
        }
    }

    protected final synchronized long getNextIdentifier() {
        return ++counter;
    }

    /**
     * String prefixed to every created externlizer identifier via {@link #createIdentifier()}
     */
    public String getPrefix() {
        return prefix;
    }

    /**
     * String prefixed to every created externlizer identifier via {@link #createIdentifier()}
     */
    public void setPrefix(final String prefix) {
        if (LOG.isDebugEnabled())
            LOG.debug("Externalizer prefix changed from "+this.prefix + " to "+prefix);
        this.prefix = prefix;
    }


    protected final String createIdentifier() {
        return prefix + StringUtil.toShortestAlphaNumericString(getNextIdentifier());
    }

    /**
     * store the {@link ExternalizedResource} in a map.
     * The {@link ExternalizedResource} should later on accessible by the
     * identifier {@link #getExternalizedResource}, {@link #removeExternalizedResource}
     */
    protected abstract void storeExternalizedResource(String identifier,
                                                      ExternalizedResource extInfo);

    /**
     * get the {@link ExternalizedResource} by identifier.
     *
     * @return null, if not found!!
     */
    public abstract ExternalizedResource getExternalizedResource(String identifier);

    /**
     * removes the {@link ExternalizedResource} by identifier.
     */
    public abstract void removeExternalizedResource(String identifier);

    /**
     * externalizes (make a java object available for a browser) an object with
     * the given {@link Externalizer}. The object is externalized in the
     * {@link #SESSION} scope.
     *
     * @return a URL for accessing the object relative to the base URL.
     */
    public String externalize(Object obj, Externalizer externalizer) {
        return externalize(obj, externalizer, SESSION);
    }

    /**
     * externalizes (make a java object available for a browser) an object with
     * the given {@link Externalizer}. If the given headers are !=null the
     * headers overwrite the headers from the {@link Externalizer}.
     * The object is externalized in the
     * {@link #SESSION} scope.
     *
     * @return a URL for accessing the object relative to the base URL.
     */
    public String externalize(Object obj, Externalizer externalizer, Collection headers) {
        return externalize(obj, externalizer, headers, SESSION);
    }

    /**
     * externalizes (make a java object available for a browser) an object with
     * the given {@link Externalizer}. Valid flags are (this may change, look
     * also in the static variable section)
     * 
    *
  • {@link #FINAL}
  • *
  • {@link #REQUEST}
  • *
  • {@link #SESSION}
  • *
  • {@link #GLOBAL}
  • *
* * @return a URL for accessing the object relative to the base URL. */ public String externalize(Object obj, Externalizer externalizer, int flags) { if (obj == null || externalizer == null) throw new IllegalStateException("no externalizer"); return externalize(obj, externalizer, null, null, flags); } /** * externalizes (make a java object available for a browser) an object with * the given {@link Externalizer}. If the given headers are !=null the * headers overwrite the headers from the {@link Externalizer}. * Valid flags are (this may change, look * also in the static variable section) *
    *
  • {@link #FINAL}
  • *
  • {@link #REQUEST}
  • *
  • {@link #SESSION}
  • *
  • {@link #GLOBAL}
  • *
* * @return a URL for accessing the object relative to the base URL. */ public String externalize(Object obj, Externalizer externalizer, Collection headers, int flags) { if (obj == null || externalizer == null) throw new IllegalStateException("no externalizer"); return externalize(obj, externalizer, null, headers, flags); } /** * externalizes (make a java object available for a browser) an object with * the given {@link Externalizer}. * If the mimeType!=null, mimeType overwrites the mimeType of the * {@link Externalizer}. * The object is externalized in the * {@link #SESSION} scope. * * @return a URL for accessing the object relative to the base URL. */ public String externalize(Object obj, Externalizer externalizer, String mimeType) { return externalize(obj, externalizer, mimeType, null, SESSION); } /** * externalizes (make a java object available for a browser) an object with * the given {@link Externalizer}. * If the mimeType!=null, mimeType overwrites the mimeType of the * {@link Externalizer}. * If the given headers are !=null the * headers overwrite the headers from the {@link Externalizer}. * * @return a URL for accessing the object relative to the base URL. */ public String externalize(Object obj, Externalizer externalizer, String mimeType, Collection headers) { return externalize(obj, externalizer, mimeType, headers, SESSION); } /** * externalizes (make a java object available for a browser) an object with * the given {@link Externalizer}. * If the mimeType!=null, mimeType overwrites the mimeType of the * {@link Externalizer}. * If the given headers are !=null the * headers overwrite the headers from the {@link Externalizer}. * Valid flags are (this may change, look * also in the static variable section) *
    *
  • {@link #FINAL}
  • *
  • {@link #REQUEST}
  • *
  • {@link #SESSION}
  • *
  • {@link #GLOBAL}
  • *
* * @return a URL for accessing the object relative to the base URL. */ public String externalize(Object obj, Externalizer externalizer, String mimeType, Collection headers, int flags) { if (externalizer == null) { throw new IllegalStateException("no externalizer"); } ExternalizedResource extInfo = new ExternalizedResource(obj, externalizer, mimeType, headers, flags); extInfo.setId(externalizer.getId(obj)); if ((flags & GLOBAL) > 0) { // session encoding is not necessary here return SystemExternalizeManager.getSharedInstance().externalize(extInfo); } else { return externalize(extInfo); } } /** * externalizes (make a java object available for a browser) the object in * extInfo. * * @return a URL for accessing the externalized object relative to the base URL. */ public String externalize(ExternalizedResource extInfo) { String identifier = (String) reverseExternalized.get(extInfo); if (identifier == null) { identifier = extInfo.getId(); if (identifier != null) { if (this == SystemExternalizeManager.getSharedInstance()) { identifier = '-' + identifier; } else { // do nothing } } else { identifier = createIdentifier(); } String extension = extInfo.getExtension(); if (extension != null) identifier += ('.' + extension); extInfo.setId(identifier); storeExternalizedResource(identifier, extInfo); reverseExternalized.put(extInfo, identifier); } return identifier + sessionEncoding; } /** * externalizes (make a java object available for a browser) the object in * extInfo. * * @return a URL for accessing the externalized object relative to the base URL. */ public String getId(String url) { if (url == null || url.length() == 0) { return url; } String result; if (url.charAt(0) == '-') { result = url; } else { result = url.substring(0, url.length() - sessionEncoding.length()); } return result; } /** * delivers a externalized object identfied with the given identifier to a * client. * It sends an error (404), if the identifier is not registered. */ public void deliver(String identifier, HttpServletResponse response, Device out) throws IOException { ExternalizedResource extInfo = getExternalizedResource(identifier); if (extInfo == null) { LOG.warn("identifier " + identifier + " not found"); response.sendError(HttpServletResponse.SC_NOT_FOUND); return; } deliver(extInfo, response, out); } public void deliver(ExternalizedResource extInfo, HttpServletResponse response, Device out) throws IOException { /* FIXME: re-implement. if ( extInfo.deliverOnce() ) { removeExternalizedResource(identifier); } */ if (extInfo.getMimeType() != null) { response.setContentType(extInfo.getMimeType()); } // FIXME find out, if this is correct: if the content length // is not size preserving (like a gzip-device), then we must not // send the content size we know.. if (out.isSizePreserving()) { int resourceLen = extInfo .getExternalizer().getLength(extInfo.getObject()); if (resourceLen > 0) { LOG.debug(extInfo.getMimeType() + ": " + resourceLen); response.setContentLength(resourceLen); } } Collection headers = extInfo.getHeaders(); if (headers != null) { for (Object header : headers) { Map.Entry entry = (Map.Entry) header; if (entry.getValue() instanceof String) { response.addHeader((String) entry.getKey(), (String) entry.getValue()); } else if (entry.getValue() instanceof Date) { response.addDateHeader((String) entry.getKey(), ((Date) entry.getValue()).getTime()); } else if (entry.getValue() instanceof Integer) { response.addIntHeader((String) entry.getKey(), (Integer) entry.getValue()); } // end of if () } } if (!response.containsHeader("Expires")) { /* * This would be the correct way to do it; alas, that means, that * for static resources, after a day or so, no caching could take * place, since the last modification was at the first time, the * resource was externalized (since it doesn't change). * .. have to think about it. */ //response.setDateHeader("Expires", // (1000*FINAL_EXPIRES) // + extInfo.getLastModified()); // .. so do this for now, which is the best approximation of what // we want. response.setDateHeader("Expires", System.currentTimeMillis() + (1000 * FINAL_EXPIRES)); } try { extInfo.getExternalizer().write(extInfo.getObject(), out); } catch (ResourceNotFoundException e) { LOG.debug("Unable to deliver resource due to: " + e.getMessage()+". Sending 404!"); response.reset(); response.sendError(404, e.getMessage()); } out.flush(); } public void clear() { reverseExternalized.clear(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy