ch.entwine.weblounge.common.content.ResourceUtils.java Source code

Java tutorial

Introduction

Here is the source code for ch.entwine.weblounge.common.content.ResourceUtils.java

Source

/*
 *  Weblounge: Web Content Management System
 *  Copyright (c) 2003 - 2011 The Weblounge Team
 *  http://entwinemedia.com/weblounge
 *
 *  This program 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
 *  of the License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with this program; if not, write to the Free Software Foundation
 *  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

package ch.entwine.weblounge.common.content;

import ch.entwine.weblounge.common.language.Language;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.util.Date;

import javax.servlet.http.HttpServletRequest;

/**
 * This class contains utility methods intended to facilitate dealing with
 * resource versions and names.
 */
public final class ResourceUtils {

    /** Logging facility */
    private static final Logger logger = LoggerFactory.getLogger(ResourceUtils.class);

    /** File size units */
    private static final String[] SIZE_UNITS = { "B", "kB", "MB", "GB", "TB" };

    /**
     * This class is not intended to be instantiated.
     */
    private ResourceUtils() {
        // Nothing to do here
    }

    /**
     * Returns <code>true</code> if the two uris match either by id or path. This
     * implementation takes into account that certain fields such as the id may
     * not (yet) be set.
     * 
     * @param a
     *          the first uri
     * @param b
     *          the second uri
     * @return <code>true</code> if the two uris point at the same resource
     */
    public static boolean equalsByIdOrPathAndVersion(ResourceURI a, ResourceURI b) {
        return uriEquals(a, b, true);
    }

    /**
     * Returns <code>true</code> if the two uris match either by id or path and
     * version. This implementation takes into account that certain fields such as
     * the id may not (yet) be set.
     * 
     * @param a
     *          the first uri
     * @param b
     *          the second uri
     * @return <code>true</code> if the two uris point at the same resource
     */
    public static boolean equalsByIdOrPath(ResourceURI a, ResourceURI b) {
        return uriEquals(a, b, false);
    }

    /**
     * Returns <code>true</code> if the two uris match. This implementation takes
     * into account that certain fields such as the id may not (yet) be set.
     * 
     * @param a
     *          the first uri
     * @param b
     *          the second uri
     * @param checkVersions
     *          <code>true</code> to also check equality on the version
     * @return <code>true</code> if the two uris point at the same resource
     */
    private static boolean uriEquals(ResourceURI a, ResourceURI b, boolean checkVersions) {
        long versionA = a.getVersion();
        long versionB = b.getVersion();

        // Test the identifier
        String idA = a.getIdentifier();
        String idB = b.getIdentifier();
        if (idA != null && idB != null) {
            if (idA.equals(idB) && (!checkVersions || versionA == versionB))
                return true;
            return false;
        }

        // Test the path
        String pathA = a.getPath();
        String pathB = b.getPath();
        if (pathA != null && pathB != null) {
            if (pathA.equals(pathB) && (!checkVersions || versionA == versionB))
                return true;
            return false;
        }

        return false;
    }

    /**
     * Returns <code>true</code> if the resource either is more recent than the
     * cached version on the client side or the request does not contain caching
     * information.
     * <p>
     * The calculation is made based on the availability of either the
     * <code>If-None-Match</code> or the <code>If-Modified-Since</code> header (in
     * this order).
     * 
     * @param request
     *          the client request
     * @param resource
     *          the resource
     * @return <code>true</code> if the resource is more recent than the version
     *         that is cached at the client.
     * @throws IllegalArgumentException
     *           if the <code>If-Modified-Since</code> header cannot be converted
     *           to a date.
     */
    public static boolean hasChanged(HttpServletRequest request, Resource<?> resource)
            throws IllegalArgumentException {
        return hasChanged(request, resource, null);
    }

    /**
     * Returns <code>true</code> if the resource either is more recent than the
     * cached version on the client side or the request does not contain caching
     * information.
     * <p>
     * The calculation is made based on the availability of either the
     * <code>If-None-Match</code> or the <code>If-Modified-Since</code> header (in
     * this order).
     * 
     * @param request
     *          the client request
     * @param resource
     *          the resource
     * @param language
     *          the language
     * @return <code>true</code> if the resource is more recent than the version
     *         that is cached at the client.
     * @throws IllegalArgumentException
     *           if the <code>If-Modified-Since</code> header cannot be converted
     *           to a date.
     */
    public static boolean hasChanged(HttpServletRequest request, Resource<?> resource, Language language)
            throws IllegalArgumentException {
        if (request.getHeader("If-None-Match") != null) {
            return isMismatch(request, getETagValue(resource));
        } else if (request.getHeader("If-Modified-Since") != null) {
            return isModified(request, resource, language);
        }
        return true;
    }

    /**
     * Returns <code>true</code> if the resource either is more recent than the
     * cached version on the client side with respect to the file's modification
     * date or the request does not contain caching information.
     * <p>
     * The calculation is made based on the availability of either the
     * <code>If-None-Match</code> or the <code>If-Modified-Since</code> header (in
     * this order).
     * 
     * @param request
     *          the client request
     * @param file
     *          the file
     * @return <code>true</code> if the resource is more recent than the version
     *         that is cached at the client.
     * @throws IllegalArgumentException
     *           if the <code>If-Modified-Since</code> header cannot be converted
     *           to a date.
     */
    public static boolean hasChanged(HttpServletRequest request, File file) throws IllegalArgumentException {
        if (file == null || !file.isFile())
            return true;
        return hasChanged(request, file.lastModified());
    }

    /**
     * Returns <code>true</code> if the resource either is more recent than the
     * cached version on the client side or the request does not contain caching
     * information.
     * <p>
     * The calculation is made based on the availability of either the
     * <code>If-None-Match</code> or the <code>If-Modified-Since</code> header (in
     * this order).
     * 
     * @param request
     *          the client request
     * @param date
     *          the date
     * @return <code>true</code> if the resource is more recent than the version
     *         that is cached at the client.
     * @throws IllegalArgumentException
     *           if the <code>If-Modified-Since</code> header cannot be converted
     *           to a date.
     */
    public static boolean hasChanged(HttpServletRequest request, long date) throws IllegalArgumentException {
        if (request.getHeader("If-None-Match") != null) {
            return isMismatch(request, getETagValue(date));
        } else if (request.getHeader("If-Modified-Since") != null) {
            return isModified(request, date);
        }
        return true;
    }

    /**
     * Returns <code>true</code> if the resource either is more recent than the
     * cached version on the client side or the request does not contain caching
     * information.
     * <p>
     * The calculation is made based on the availability of the
     * <code>If-Modified-Since</code> header. Use
     * {@link #hasChanged(HttpServletRequest, Resource))} to also take the
     * <code>If-None-Match</code> header into account.
     * 
     * @param request
     *          the client request
     * @param resource
     *          the resource
     * @return <code>true</code> if the resource is more recent than the version
     *         that is cached at the client.
     * @throws IllegalArgumentException
     *           if the <code>If-Modified-Since</code> header cannot be converted
     *           to a date.
     */
    public static boolean isModified(HttpServletRequest request, Resource<?> resource)
            throws IllegalArgumentException {
        return isModified(request, getModificationDate(resource, null).getTime());
    }

    /**
     * Returns <code>true</code> if the resource either is more recent than the
     * cached version on the client side or the request does not contain caching
     * information.
     * <p>
     * The calculation is made based on the availability of the
     * <code>If-Modified-Since</code> header. Use
     * {@link #hasChanged(HttpServletRequest, Resource, Language))} to also take
     * the <code>If-None-Match</code> header into account.
     * 
     * @param request
     *          the client request
     * @param resource
     *          the resource
     * @param language
     *          the language
     * @return <code>true</code> if the resource is more recent than the version
     *         that is cached at the client.
     * @throws IllegalArgumentException
     *           if the <code>If-Modified-Since</code> header cannot be converted
     *           to a date.
     */
    public static boolean isModified(HttpServletRequest request, Resource<?> resource, Language language)
            throws IllegalArgumentException {
        return isModified(request, getModificationDate(resource, language).getTime());
    }

    /**
     * Returns <code>true</code> if the resource either is more recent than the
     * cached version on the client side or the request does not contain caching
     * information.
     * <p>
     * The calculation is made based on the availability of the
     * <code>If-Modified-Since</code> header. Use
     * {@link #hasChanged(HttpServletRequest, long)} to also take the
     * <code>If-None-Match</code> header into account.
     * 
     * @param request
     *          the client request
     * @param date
     *          the date
     * @return <code>true</code> if the resource is more recent than the version
     *         that is cached at the client.
     * @throws IllegalArgumentException
     *           if the <code>If-Modified-Since</code> header cannot be converted
     *           to a date.
     */
    public static boolean isModified(HttpServletRequest request, long date) throws IllegalArgumentException {
        if (request.getHeader("If-Modified-Since") != null) {
            try {
                long cachedModificationDate = request.getDateHeader("If-Modified-Since");
                return cachedModificationDate < date;
            } catch (IllegalArgumentException e) {
                logger.debug("Client sent malformed 'If-Modified-Since' header: {}");
            }
        }
        return true;
    }

    /**
     * Returns <code>true</code> if the resource has not been modified according
     * to the expected <code>ETag</code> value, <code>false</code> if the cached
     * version on the client side is out dated or if the request did not contain
     * caching information.
     * <p>
     * The decision is based on the availability and value of the
     * <code>If-None-Match</code> header (called <code>ETag</code>). The computed
     * value of <code>eTag</code> is expected to be plain, i. e. without
     * surrounding quotes.
     * 
     * @param eTag
     *          the expected eTag value
     * @param request
     *          the client request
     * @return <code>true</code> if the resource's calculated eTag matches the one
     *         specified
     * @throws IllegalArgumentException
     *           if the <code>If-Modified-Since</code> cannot be converted to a
     *           date.
     */
    public static boolean isMismatch(HttpServletRequest request, String eTag) throws IllegalArgumentException {
        String eTagHeader = request.getHeader("If-None-Match");
        if (StringUtils.isBlank(eTagHeader))
            return true;
        return !eTagHeader.equals(eTag);
    }

    /**
     * Returns the value for the <code>ETag</code> header field, which is
     * calculated from the resource identifier and the resource's modification
     * date.
     * 
     * @param resource
     *          the resource
     * @return the <code>ETag</code> value
     */
    public static String getETagValue(Resource<?> resource) {
        return getETagValue(getModificationDate(resource, null).getTime());
    }

    /**
     * Returns the value for the <code>ETag</code> header field, which is
     * calculated from the file's modification date.
     * 
     * @param file
     *          the file
     * @return the <code>ETag</code> value
     * @throws IllegalArgumentException
     *           if <code>file</code> is <code>null</code>
     */
    public static String getETagValue(File file) {
        if (file == null)
            throw new IllegalArgumentException("File must not be null");
        return getETagValue(file.lastModified());
    }

    /**
     * Returns the value for the <code>ETag</code> header field, which is
     * calculated from the given modification date.
     * 
     * @param date
     *          the date in milliseconds
     * @return the <code>ETag</code> value
     */
    public static String getETagValue(long date) {
        return new StringBuffer().append("\"").append("WL-" + date).append("\"").toString();
    }

    /**
     * Returns the modification date. If the resource has never been modified, its
     * creation date is returned instead.
     * 
     * @param resource
     *          the resource
     * @return the modification date
     * @throws IllegalArgumentException
     *           if the resource is <code>null</code>
     */
    public static Date getModificationDate(Resource<?> resource) throws IllegalArgumentException {
        return getModificationDate(resource, null);
    }

    /**
     * Returns the modification date. If the resource has never been modified, its
     * creation date is returned instead.
     * 
     * @param resource
     *          the resource
     * @param language
     *          the language
     * @return the modification date
     * @throws IllegalArgumentException
     *           if the resource is <code>null</code>
     */
    public static Date getModificationDate(Resource<?> resource, Language language)
            throws IllegalArgumentException {
        if (resource == null)
            throw new IllegalArgumentException("Resource cannot be null");

        // Has a language been specified? If so, use the localized content
        if (language != null) {
            ResourceContent content = resource.getContent(language);
            if (content != null)
                return content.getCreationDate();
        }

        // The resource's modified date is the last resort. If nothing else helps,
        // return the creation date.
        return resource.getLastModified();
    }

    /**
     * Returns the version for the given version identifier. Available versions
     * are:
     * <ul>
     * <li>{@link Resource#LIVE}</li>
     * <li>{@link Resource#WORK}</li>
     * 
     * @param version
     *          the version identifier
     * @return the version string
     */
    public static long getVersion(String version) {
        if ("live".equals(version) || "index".equals(version)) {
            return Resource.LIVE;
        } else if ("work".equals(version)) {
            return Resource.WORK;
        } else {
            try {
                return Long.parseLong(version);
            } catch (NumberFormatException e) {
                return -1;
            }
        }
    }

    /**
     * Returns the document name for the given version. For the live version, this
     * method will return <code>index.xml</code>. Available versions are:
     * <ul>
     * <li>{@link Resource#LIVE}</li>
     * <li>{@link Resource#WORK}</li>
     * 
     * @param version
     *          the version identifier
     * @return the version string
     */
    public static String getDocument(long version) {
        if (version == Resource.LIVE)
            return "index.xml";
        else if (version == Resource.WORK)
            return "work.xml";
        else
            return Long.toString(version) + ".xml";
    }

    /**
     * Returns the version identifier for the given version. Available versions
     * are:
     * <ul>
     * <li>{@link Resource#LIVE}</li>
     * <li>{@link Resource#WORK}</li>
     * 
     * @param version
     *          the version identifier
     * @return the version string
     */
    public static String getVersionString(long version) {
        if (version == Resource.LIVE)
            return "live";
        else if (version == Resource.WORK)
            return "work";
        else
            return Long.toString(version);
    }

    /**
     * Returns the file size formatted in <tt>bytes</tt>, <tt>kilobytes</tt>,
     * <tt>megabytes</tt>, <tt>gigabytes</tt> and <tt>terabytes</tt>, where 1 KB
     * is equal to 1'024 bytes.
     * <p>
     * The number in front of the size unit will be formatted with one decimal
     * place, e. g. <tt>12.4KB</tt>.
     * 
     * @param sizeInBytes
     *          the file size in bytes
     * @return the file size formatted using appropriate units
     * @throws IllegalArgumentException
     *           if the file size is negative
     */
    public static String formatFileSize(long sizeInBytes) throws IllegalArgumentException {
        return formatFileSize(sizeInBytes, null);
    }

    /**
     * Returns the file size formatted in <tt>bytes</tt>, <tt>kilobytes</tt>,
     * <tt>megabytes</tt>, <tt>gigabytes</tt> and <tt>terabytes</tt>, where 1 kB
     * is equal to 1'000 bytes.
     * <p>
     * If no {@link NumberFormat} was provided,
     * <code>new DecimalFormat("0.#)</code> is used.
     * 
     * @param sizeInBytes
     *          the file size in bytes
     * @param format
     *          the number format
     * @return the file size formatted using appropriate units
     * @throws IllegalArgumentException
     *           if the file size is negative
     */
    public static String formatFileSize(long sizeInBytes, NumberFormat format) throws IllegalArgumentException {
        if (sizeInBytes < 0)
            throw new IllegalArgumentException("File size cannot be negative");
        int unitSelector = 0;
        double size = sizeInBytes;

        // Calculate the size to display
        while (size >= 1000 && unitSelector < SIZE_UNITS.length) {
            size /= 1000.0d;
            unitSelector++;
        }

        // Create a number formatter, if none was provided
        if (format == null) {
            DecimalFormatSymbols formatSymbols = new DecimalFormatSymbols();
            formatSymbols.setDecimalSeparator('.');
            format = new DecimalFormat("0.#", formatSymbols);
        }

        return format.format(size) + SIZE_UNITS[unitSelector];
    }

}