/* * Copyright (C) 2014 Civilian Framework. * * Licensed under the Civilian License (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.civilian-framework.org/license.txt * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.civilian.content; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.HashMap; import java.util.StringTokenizer; import org.civilian.internal.HeaderParser; import org.civilian.util.Check; /** * CompressionScheme represents a scheme to compress or decompress * binary content. */ public abstract class CompressionScheme { /** * The name of the "identity" compression scheme. */ public static final String IDENTITY = "identity"; /** * The name of the "gzip" compression scheme. */ public static final String GZIP = "gzip"; /** * An alternate name of the "gzip" compression scheme. */ public static final String X_GZIP = "x-gzip"; /** * The name of the "deflate" compression scheme. */ public static final String DEFLATE = "deflate"; /** * The name of the "compress" compression scheme. */ public static final String COMPRESS = "compress"; /** * An alternate name of the "compress" compression scheme. */ public static final String X_COMPRESS = "x-compress"; /** * The default implementation of the "gzip" compression scheme. */ public static final CompressionScheme DEFAULT_GZIP_SCHEME = new GZip(); /** * The default implementation of the "compress" compression scheme. */ public static final CompressionScheme DEFAULT_COMPRESS_SCHEME = new Zip(); /** * The default implementation of the "deflate" compression scheme. */ public static final CompressionScheme DEFAULT_DEFLATE_SCHEME = new Deflate(); /** * The default implementation of the "identity" compression scheme. */ public static final CompressionScheme DEFAULT_IDENTITY_SCHEME = new Identity(); private static final HashMap<String,CompressionScheme> REGISTRY = new HashMap<>(); private static CompressionScheme preferred_ = DEFAULT_GZIP_SCHEME; private static CompressionScheme identity_ = DEFAULT_IDENTITY_SCHEME; static { set(GZIP, DEFAULT_GZIP_SCHEME); set(X_GZIP, DEFAULT_GZIP_SCHEME); set(COMPRESS, DEFAULT_COMPRESS_SCHEME); set(X_COMPRESS, DEFAULT_COMPRESS_SCHEME); set(DEFLATE, DEFAULT_DEFLATE_SCHEME); set(IDENTITY, DEFAULT_IDENTITY_SCHEME); } /** * Returns a CompressionScheme for a name. * @return the scheme or null if not registered. */ public static CompressionScheme get(String name) { return REGISTRY.get(name); } /** * Returns a CompressionScheme for a name. * @return the scheme or the defaultScheme if not registered. */ public static CompressionScheme get(String name, CompressionScheme defaultScheme) { CompressionScheme scheme = get(name); return scheme != null ? scheme : defaultScheme; } /** * Sets or removes a compression scheme * @param name the name * @param scheme the scheme or null if the scheme under the name should * be unregistered */ public static void set(String name, CompressionScheme scheme) { Check.notNull(name, "name"); if (scheme != null) { REGISTRY.put(name, scheme); if (scheme.isIdentity()) identity_ = scheme; } else REGISTRY.remove(name); } /** * Returns the preferred CompressionScheme. By default this is the "gzip" scheme. */ public static CompressionScheme getPreferred() { return preferred_; } /** * Sets the preferred compression scheme. */ public static void setPreferred(CompressionScheme preferred) { preferred_ = Check.notNull(preferred, "preferred"); } /** * Returns the preferred CompressionScheme. By default this is the "gzip" scheme. */ public static CompressionScheme getIdentity() { return identity_; } /** * Sets the preferred compression scheme. */ public static void setIdentity(CompressionScheme identity) { identity_ = Check.notNull(identity, "identity"); } /** * Returns the best matching Compression-Scheme for the Accept-Encoding header of a request. * @param accept a String defining the accepted compression schemes, as given by * the Accept-Encoding header of a request. It must not be null. * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html">http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html</a>. * @return the best matching scheme. The returned scheme may be the identity scheme. * Null is returned if no scheme matched and identity was explicitly excluded */ public static CompressionScheme match(String accept) { Check.notNull(accept, "accept"); if (accept.length() == 0) return identity_; if ("*".equals(accept)) return getPreferred(); // next contains weights if (accept.contains(";")) return matchWeighted(accept); // now: accept either a single name or a list of names // check it it contains the preferred if (accept.contains(preferred_.getName())) return preferred_; // check if it is a single name if (!accept.contains(",")) return get(accept, identity_); // parse the list StringTokenizer st = new StringTokenizer(accept, ","); while(st.hasMoreTokens()) { String next = st.nextToken().trim(); CompressionScheme scheme = get(next); if (scheme != null) return scheme; } // give up return identity_; } private static CompressionScheme matchWeighted(String accept) { CompressionScheme best = null; boolean identityAllowed = true; double bestQuality = 0.0; try { HeaderParser parser = new HeaderParser(accept); parser.next(); while(parser.getToken() == HeaderParser.Token.ITEM) { boolean isWildcard = "*".equals(parser.item); CompressionScheme scheme = isWildcard ? preferred_ : get(parser.item); double quality = 1.0; while(parser.next() == HeaderParser.Token.PARAM) { if ("q".equals(parser.paramName)) quality = Double.parseDouble(parser.paramValue); } if ((scheme != null) && (quality > bestQuality)) { best = scheme; bestQuality = quality; } if ((quality == 0) && ((scheme == identity_) || isWildcard)) identityAllowed = false; } } catch(RuntimeException e) { // ignore } if (best != null) return best; else return identityAllowed ? identity_ : null; } /** * Creates a new CompressionScheme * @param name the scheme name */ public CompressionScheme(String name) { name_ = name; } /** * Returns the name. */ public String getName() { return name_; } /** * Returns if this scheme is the identity scheme. */ public boolean isIdentity() { return IDENTITY.equals(name_); } /** * Wraps a InputStream for compressed binary content which decompresses the data. */ public abstract InputStream wrap(InputStream in) throws IOException; /** * Wraps a OutputStream for uncompressed binary content which compresses the data. */ public abstract OutputStream wrap(OutputStream out) throws IOException; /** * The GZip CompressionScheme. */ public static class GZip extends CompressionScheme { public GZip() { super(GZIP); } @Override public InputStream wrap(InputStream in) throws IOException { return new java.util.zip.GZIPInputStream(in); } @Override public OutputStream wrap(OutputStream out) throws IOException { return new GZipOutputStream(out); } } /** * GZipOutputStream is a java.util.zip.GZIPOutputStream. * which actively releases its deflater. */ public static class GZipOutputStream extends java.util.zip.GZIPOutputStream { public GZipOutputStream(OutputStream out) throws IOException { super(out, true /*syncFlush*/); } /** * Also releases the deflater. */ @Override public void finish() throws IOException { super.finish(); def.end(); } } /** * The Zip CompressionScheme. */ public static class Zip extends CompressionScheme { public Zip() { super(COMPRESS); } @Override public InputStream wrap(InputStream in) throws IOException { return new java.util.zip.ZipInputStream(in); } @Override public OutputStream wrap(OutputStream out) throws IOException { return new ZipOutputStream(out); } } /** * ZipOutputStream is a java.util.zip.ZipOutputStream. * which actively releases its deflater. */ public static class ZipOutputStream extends java.util.zip.ZipOutputStream { public ZipOutputStream(OutputStream out) throws IOException { super(out); } /** * Also releases the deflater. */ @Override public void finish() throws IOException { super.finish(); def.end(); } } /** * The Deflate CompressionScheme. */ public static class Deflate extends CompressionScheme { public Deflate() { super(DEFLATE); } @Override public InputStream wrap(InputStream in) throws IOException { return new java.util.zip.InflaterInputStream(in); } @Override public OutputStream wrap(OutputStream out) throws IOException { return new DeflaterOutputStream(out); } } /** * DeflaterOutputStream is a java.util.zip.DeflaterOutputStream. * which actively releases its deflater. */ public static class DeflaterOutputStream extends java.util.zip.DeflaterOutputStream { public DeflaterOutputStream(OutputStream out) throws IOException { super(out); } /** * Also releases the deflater. */ @Override public void finish() throws IOException { super.finish(); def.end(); } } /** * The Deflate CompressionScheme. */ public static class Identity extends CompressionScheme { public Identity() { super(IDENTITY); } @Override public InputStream wrap(InputStream in) throws IOException { return in; } @Override public OutputStream wrap(OutputStream out) throws IOException { return out; } } private String name_; }