/**
* The MIT License (MIT)
*
* Copyright (c) 2014-2017 Yegor Bugayenko
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.takes.rs;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
/**
* The body of a response used by {@link RsWithBody}.
*
* @author Nicolas Filotto (nicolas.filotto@gmail.com)
* @version $Id: 55f7ee27f9684f72b1e4f34d5845673f89d17740 $
* @since 0.32
*/
@SuppressWarnings("PMD.TooManyMethods")
interface Body {
/**
* Gives an {@code InputStream} corresponding to the content of
* the body.
* @return The content of the body.
* @throws IOException in case the content of the body could not
* be provided.
*/
InputStream input() throws IOException;
/**
* Gives the length of the stream.
* @return The length of the stream.
* @throws IOException in case the length of the stream could not be
* retrieved.
*/
int length() throws IOException;
/**
* Content of a body based on an {@link java.net.URL}.
*/
final class Url implements Body {
/**
* The {@link java.net.URL} of the content.
*/
private final java.net.URL url;
/**
* Constructs an {@code URL} with the specified {@link java.net.URL}.
* @param content The {@link java.net.URL} of the content.
*/
Url(final java.net.URL content) {
this.url = content;
}
@Override
public InputStream input() throws IOException {
return this.url.openStream();
}
@Override
public int length() throws IOException {
try (final InputStream input = this.url.openStream()) {
return input.available();
}
}
}
/**
* Content of a body based on a byte array.
*/
final class ByteArray implements Body {
/**
* The content of the body in a byte array.
*/
private final byte[] bytes;
/**
* Constructs an {@code ByteArray} with the specified byte array.
* @param content The content of the body.
*/
ByteArray(final byte[] content) {
this.bytes = content.clone();
}
@Override
public InputStream input() {
return new ByteArrayInputStream(this.bytes);
}
@Override
public int length() {
return this.bytes.length;
}
}
/**
* The content of the body based on an {@link InputStream}.
*/
final class Stream implements Body {
/**
* The content of the body in an InputStream.
*/
private final InputStream stream;
/**
* The length of the stream.
*/
private final AtomicInteger length;
/**
* Constructs an {@code Stream} with the specified {@link InputStream}.
* @param input The content of the body as stream.
*/
Stream(final InputStream input) {
this.stream = input;
this.length = new AtomicInteger(-1);
}
@Override
public InputStream input() throws IOException {
this.estimate();
return this.stream;
}
@Override
public int length() throws IOException {
this.estimate();
return this.length.get();
}
/**
* Estimates the length of the {@code InputStream}.
* @throws IOException in case the length could not be estimated.
*/
private void estimate() throws IOException {
if (this.length.get() == -1) {
this.length.compareAndSet(-1, this.stream.available());
}
}
}
/**
* Decorator that will store the content of the underlying Body into a
* temporary File.
* <p><b>The content of the Body will be stored into a temporary
* file to be able to read it as many times as we want so use it only
* for large content, for small content use {@link Body.ByteArray}
* instead.</b>
*/
final class TempFile implements Body {
/**
* The temporary file that contains the content of the body.
*/
private final File file;
/**
* The underlying body.
*/
private final Body body;
/**
* Constructs a {@code TempFile} with the specified {@link Body}.
* @param body The content of the body to store into a temporary file.
*/
TempFile(final Body body) {
this.body = body;
this.file = new File(
System.getProperty("java.io.tmpdir"),
String.format(
"%s-%s.tmp",
Body.TempFile.class.getName(),
UUID.randomUUID().toString()
)
);
}
@Override
public InputStream input() throws IOException {
return new FileInputStream(this.file());
}
@Override
public int length() throws IOException {
return (int) this.file().length();
}
// Needed to remove the file once the Stream object is no more used.
// @checkstyle NoFinalizerCheck (2 lines)
// @checkstyle ProtectedMethodInFinalClassCheck (3 lines)
@Override
protected void finalize() throws Throwable {
try {
Files.delete(Paths.get(this.file.getAbsolutePath()));
} finally {
super.finalize();
}
}
/**
* Gives the {@code File} that contains the content of the underlying
* {@code Body}.
* @return The {@code File} in which we stored the content of the
* underlying {@code Body}.
* @throws IOException In case the content of the underlying
* {@code Body} could not be stored into the file.
*/
private File file() throws IOException {
synchronized (this.file) {
if (!this.file.exists()) {
this.file.deleteOnExit();
try (final InputStream content = this.body.input()) {
Files.copy(
content,
Paths.get(this.file.getAbsolutePath()),
StandardCopyOption.REPLACE_EXISTING
);
}
}
return this.file;
}
}
}
}