/** * Copyright 2005-2014 Restlet * * The contents of this file are subject to the terms of one of the following * open source licenses: Apache 2.0 or or EPL 1.0 (the "Licenses"). You can * select the license that you prefer but you may not use this file except in * compliance with one of these Licenses. * * You can obtain a copy of the Apache 2.0 license at * http://www.opensource.org/licenses/apache-2.0 * * You can obtain a copy of the EPL 1.0 license at * http://www.opensource.org/licenses/eclipse-1.0 * * See the Licenses for the specific language governing permissions and * limitations under the Licenses. * * Alternatively, you can obtain a royalty free commercial license with less * limitations, transferable or non-transferable, directly at * http://restlet.com/products/restlet-framework * * Restlet is a registered trademark of Restlet S.A.S. */ package org.restlet.engine.application; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Reader; import java.nio.channels.ReadableByteChannel; import java.nio.channels.WritableByteChannel; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.zip.Deflater; import java.util.zip.DeflaterOutputStream; import java.util.zip.GZIPOutputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import org.restlet.data.Disposition; import org.restlet.data.Encoding; import org.restlet.engine.io.IoUtils; import org.restlet.representation.Representation; import org.restlet.util.WrapperList; import org.restlet.util.WrapperRepresentation; // [excludes gwt] /** * Content that encodes a wrapped content. Allows to apply only one encoding. * * @author Jerome Louvel */ public class EncodeRepresentation extends WrapperRepresentation { /** * Returns the list of supported encodings. * * @return The list of supported encodings. */ public static List<Encoding> getSupportedEncodings() { return Arrays.<Encoding> asList(Encoding.GZIP, Encoding.DEFLATE, Encoding.DEFLATE_NOWRAP, Encoding.ZIP, Encoding.IDENTITY); } /** Indicates if the encoding can happen. */ private volatile boolean canEncode; /** The encoding to apply. */ private volatile Encoding encoding; /** The applied encodings. */ private volatile List<Encoding> encodings; /** * Constructor. * * @param encoding * Encoder algorithm. * @param wrappedRepresentation * The wrapped representation. */ public EncodeRepresentation(Encoding encoding, Representation wrappedRepresentation) { super(wrappedRepresentation); this.canEncode = getSupportedEncodings().contains(encoding); this.encodings = null; this.encoding = encoding; } /** * Indicates if the encoding can happen. * * @return True if the encoding can happen. */ public boolean canEncode() { return this.canEncode; } /** * Returns the available size in bytes of the encoded representation if * known, UNKNOWN_SIZE (-1) otherwise. * * @return The available size in bytes if known, UNKNOWN_SIZE (-1) * otherwise. */ @Override public long getAvailableSize() { long result = UNKNOWN_SIZE; if (canEncode()) { if (this.encoding.equals(Encoding.IDENTITY)) { result = getWrappedRepresentation().getAvailableSize(); } } else { result = getWrappedRepresentation().getAvailableSize(); } return result; } /** * Returns a readable byte channel. If it is supported by a file a read-only * instance of FileChannel is returned. * * @return A readable byte channel. */ @Override public ReadableByteChannel getChannel() throws IOException { if (canEncode()) { return IoUtils.getChannel(this); } else { return getWrappedRepresentation().getChannel(); } } /** * Returns the applied encodings. * * @return The applied encodings. */ @Override public List<Encoding> getEncodings() { if (this.encodings == null) { this.encodings = new WrapperList<Encoding>() { @Override public boolean add(Encoding element) { if (element == null) { throw new IllegalArgumentException( "Cannot add a null encoding."); } return super.add(element); } @Override public void add(int index, Encoding element) { if (element == null) { throw new IllegalArgumentException( "Cannot add a null encoding."); } super.add(index, element); } @Override public boolean addAll(Collection<? extends Encoding> elements) { boolean addNull = (elements == null); if (!addNull) { for (final Iterator<? extends Encoding> iterator = elements .iterator(); !addNull && iterator.hasNext();) { addNull = (iterator.next() == null); } } if (addNull) { throw new IllegalArgumentException( "Cannot add a null encoding."); } return super.addAll(elements); } @Override public boolean addAll(int index, Collection<? extends Encoding> elements) { boolean addNull = (elements == null); if (!addNull) { for (final Iterator<? extends Encoding> iterator = elements .iterator(); !addNull && iterator.hasNext();) { addNull = (iterator.next() == null); } } if (addNull) { throw new IllegalArgumentException( "Cannot add a null encoding."); } return super.addAll(index, elements); } }; this.encodings.addAll(getWrappedRepresentation().getEncodings()); if (canEncode()) { this.encodings.add(this.encoding); } } return this.encodings; } @Override public Reader getReader() throws IOException { if (canEncode()) { return IoUtils.getReader(getStream(), getCharacterSet()); } else { return getWrappedRepresentation().getReader(); } } /** * Returns the size in bytes of the encoded representation if known, * UNKNOWN_SIZE (-1) otherwise. * * @return The size in bytes if known, UNKNOWN_SIZE (-1) otherwise. */ @Override public long getSize() { long result = UNKNOWN_SIZE; if (canEncode()) { if (this.encoding.equals(Encoding.IDENTITY)) { result = getWrappedRepresentation().getSize(); } } else { result = getWrappedRepresentation().getSize(); } return result; } @Override public InputStream getStream() throws IOException { if (canEncode()) { return IoUtils.getStream(this); } else { return getWrappedRepresentation().getStream(); } } @Override public String getText() throws IOException { if (canEncode()) { return IoUtils.toString(getStream(), getCharacterSet()); } else { return getWrappedRepresentation().getText(); } } @Override public void write(OutputStream outputStream) throws IOException { if (canEncode()) { DeflaterOutputStream encoderOutputStream = null; if (this.encoding.equals(Encoding.GZIP)) { encoderOutputStream = new GZIPOutputStream(outputStream); } else if (this.encoding.equals(Encoding.DEFLATE)) { encoderOutputStream = new DeflaterOutputStream(outputStream); } else if (this.encoding.equals(Encoding.DEFLATE_NOWRAP)) { encoderOutputStream = new DeflaterOutputStream(outputStream, new Deflater(Deflater.DEFAULT_COMPRESSION, true)); } else if (this.encoding.equals(Encoding.ZIP)) { @SuppressWarnings("resource") final ZipOutputStream stream = new ZipOutputStream(outputStream); String name = "entry"; if (getWrappedRepresentation().getDisposition() != null) { name = getWrappedRepresentation() .getDisposition() .getParameters() .getFirstValue(Disposition.NAME_FILENAME, true, name); } stream.putNextEntry(new ZipEntry(name)); encoderOutputStream = stream; } else if (this.encoding.equals(Encoding.IDENTITY)) { // Encoder unnecessary for identity encoding } if (encoderOutputStream != null) { getWrappedRepresentation().write(encoderOutputStream); encoderOutputStream.flush(); encoderOutputStream.finish(); } else { getWrappedRepresentation().write(outputStream); } } else { getWrappedRepresentation().write(outputStream); } } @Override public void write(WritableByteChannel writableChannel) throws IOException { if (canEncode()) { OutputStream os = IoUtils.getStream(writableChannel); write(os); os.flush(); } else { getWrappedRepresentation().write(writableChannel); } } @Override public void write(java.io.Writer writer) throws IOException { if (canEncode()) { OutputStream os = IoUtils.getStream(writer, getCharacterSet()); write(os); os.flush(); } else { getWrappedRepresentation().write(writer); } } }