/** * 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.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.zip.GZIPInputStream; import java.util.zip.Inflater; import java.util.zip.InflaterInputStream; import java.util.zip.ZipInputStream; import org.restlet.data.Encoding; import org.restlet.engine.io.IoUtils; import org.restlet.representation.Representation; import org.restlet.util.WrapperRepresentation; // [excludes gwt] /** * Representation that decodes a wrapped representation if its encoding is * supported. If at least one encoding of the wrapped representation is not * supported, then the wrapped representation is not decoded. * * @author Jerome Louvel */ public class DecodeRepresentation 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 decoding can happen. */ private volatile boolean decoding; /** List of encodings still applied to the decodeRepresentation */ private final List<Encoding> wrappedEncodings; /** * Constructor. * * @param wrappedRepresentation * The wrapped representation. */ public DecodeRepresentation(Representation wrappedRepresentation) { super(wrappedRepresentation); this.decoding = getSupportedEncodings().containsAll( wrappedRepresentation.getEncodings()); this.wrappedEncodings = new CopyOnWriteArrayList<Encoding>( wrappedRepresentation.getEncodings()); } /** * 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 (isDecoding()) { return IoUtils.getChannel(getStream()); } else { return getWrappedRepresentation().getChannel(); } } /** * Returns a decoded stream for a given encoding and coded stream. * * @param encoding * The encoding to use. * @param encodedStream * The encoded stream. * @return The decoded stream. * @throws IOException */ private InputStream getDecodedStream(Encoding encoding, InputStream encodedStream) throws IOException { InputStream result = null; if (encodedStream != null) { if (encoding.equals(Encoding.GZIP)) { result = new GZIPInputStream(encodedStream); } else if (encoding.equals(Encoding.DEFLATE)) { result = new InflaterInputStream(encodedStream); } else if (encoding.equals(Encoding.DEFLATE_NOWRAP)) { result = new InflaterInputStream(encodedStream, new Inflater( true)); } else if (encoding.equals(Encoding.ZIP)) { @SuppressWarnings("resource") final ZipInputStream stream = new ZipInputStream(encodedStream); if (stream.getNextEntry() != null) { result = stream; } } else if (encoding.equals(Encoding.IDENTITY)) { throw new IOException( "Decoder unecessary for identity decoding"); } } return result; } /** * Returns the encodings applied to the entity. * * @return The encodings applied to the entity. */ @Override public List<Encoding> getEncodings() { if (isDecoding()) { return new ArrayList<Encoding>(); } else { return this.wrappedEncodings; } } @Override public Reader getReader() throws IOException { if (isDecoding()) { return IoUtils.getReader(getStream(), getCharacterSet()); } else { return getWrappedRepresentation().getReader(); } } /** * Returns the size in bytes of the decoded 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 (isDecoding()) { boolean identity = true; for (final Iterator<Encoding> iter = getEncodings().iterator(); identity && iter.hasNext();) { identity = (iter.next().equals(Encoding.IDENTITY)); } if (identity) { result = getWrappedRepresentation().getSize(); } } else { result = getWrappedRepresentation().getSize(); } return result; } /** * Returns a stream with the representation's content. * * @return A stream with the representation's content. */ @Override public InputStream getStream() throws IOException { InputStream result = getWrappedRepresentation().getStream(); if (isDecoding()) { for (int i = this.wrappedEncodings.size() - 1; i >= 0; i--) { if (!this.wrappedEncodings.get(i).equals(Encoding.IDENTITY)) { result = getDecodedStream(this.wrappedEncodings.get(i), result); } } } return result; } /** * Converts the representation to a string value. Be careful when using this * method as the conversion of large content to a string fully stored in * memory can result in OutOfMemoryErrors being thrown. * * @return The representation as a string value. */ @Override public String getText() throws IOException { if (isDecoding()) { return IoUtils.toString(getStream(), getCharacterSet()); } else { return getWrappedRepresentation().getText(); } } /** * Indicates if the decoding can happen. * * @return True if the decoding can happen. */ public boolean isDecoding() { return this.decoding; } /** * Writes the representation to a byte stream. * * @param outputStream * The output stream. */ @Override public void write(OutputStream outputStream) throws IOException { if (isDecoding()) { IoUtils.copy(getStream(), outputStream); } else { getWrappedRepresentation().write(outputStream); } } /** * Writes the representation to a byte channel. * * @param writableChannel * A writable byte channel. */ @Override public void write(WritableByteChannel writableChannel) throws IOException { if (isDecoding()) { OutputStream os = IoUtils.getStream(writableChannel); write(os); os.flush(); } else { getWrappedRepresentation().write(writableChannel); } } }