diff --git a/src/main/java/org/takes/rq/RqHeaders.java b/src/main/java/org/takes/rq/RqHeaders.java index 9f43703c4..986e502dc 100644 --- a/src/main/java/org/takes/rq/RqHeaders.java +++ b/src/main/java/org/takes/rq/RqHeaders.java @@ -181,6 +181,13 @@ final class Smart implements RqHeaders { public Smart(final RqHeaders req) { this.origin = req; } + /** + * Ctor. + * @param req Original request + */ + public Smart(final Request req) { + this(new RqHeaders.Base(req)); + } @Override public List header(final CharSequence name) throws IOException { diff --git a/src/main/java/org/takes/rq/multipart/RqMtBase.java b/src/main/java/org/takes/rq/multipart/RqMtBase.java index 2d27db6cc..bf3d1e455 100644 --- a/src/main/java/org/takes/rq/multipart/RqMtBase.java +++ b/src/main/java/org/takes/rq/multipart/RqMtBase.java @@ -24,17 +24,17 @@ package org.takes.rq.multipart; import java.io.File; -import java.io.FileInputStream; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; -import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.nio.ByteBuffer; import java.nio.channels.Channels; -import java.nio.channels.FileChannel; import java.nio.channels.ReadableByteChannel; import java.nio.channels.WritableByteChannel; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.StandardOpenOption; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -44,19 +44,13 @@ import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; -import lombok.EqualsAndHashCode; import org.takes.HttpException; import org.takes.Request; import org.takes.misc.Sprintf; -import org.takes.misc.Utf8String; import org.takes.misc.VerboseIterable; -import org.takes.rq.RqGreedy; import org.takes.rq.RqHeaders; import org.takes.rq.RqLengthAware; -import org.takes.rq.RqLive; import org.takes.rq.RqMultipart; -import org.takes.rq.RqWithHeader; -import org.takes.rq.TempInputStream; /** * Request decorator, that decodes FORM data from @@ -75,16 +69,14 @@ * @since 0.33 * @see * Forms in HTML - * @checkstyle ClassDataAbstractionCouplingCheck (500 lines) - * @see RqGreedy - * @todo #651:30min This class is highly coupled, it should be refactored in - * order to be able to reduce the overall coupling of this class which will - * allow to remove the violations ClassDataAbstractionCouplingCheck and - * PMD.ExcessiveImports. + * @see org.takes.rq.RqGreedy */ -@SuppressWarnings("PMD.ExcessiveImports") -@EqualsAndHashCode(of = "origin") +@lombok.EqualsAndHashCode(of = "origin") public final class RqMtBase implements RqMultipart { + /** + * The encoding used to create the request. + */ + private static final Charset ENCODING = Charset.forName("UTF-8"); /** * Pattern to get boundary from header. */ @@ -179,9 +171,7 @@ public InputStream body() throws IOException { */ private Map> requests( final Request req) throws IOException { - final String header = new RqHeaders.Smart( - new RqHeaders.Base(req) - ).single("Content-Type"); + final String header = new RqHeaders.Smart(req).single("Content-Type"); if (!header.toLowerCase(Locale.ENGLISH) .startsWith("multipart/form-data")) { throw new HttpException( @@ -210,9 +200,9 @@ private Map> requests( "failed to read the request body" ); } - final byte[] boundary = new Utf8String( - String.format("%s--%s", RqMtBase.CRLF, matcher.group(1)) - ).bytes(); + final byte[] boundary = String.format( + "%s--%s", RqMtBase.CRLF, matcher.group(1) + ).getBytes(RqMtBase.ENCODING); this.buffer.flip(); this.buffer.position(boundary.length - 2); final Collection requests = new LinkedList<>(); @@ -240,32 +230,23 @@ private Request make(final byte[] boundary, final File file = File.createTempFile( RqMultipart.class.getName(), ".tmp" ); - final FileChannel channel = new RandomAccessFile( - file, "rw" - ).getChannel(); - try { + try (WritableByteChannel channel = Files.newByteChannel( + file.toPath(), + StandardOpenOption.READ, + StandardOpenOption.WRITE + ) + ) { channel.write( ByteBuffer.wrap( - new Utf8String(this.head().iterator().next()).bytes() + this.head().iterator().next().getBytes(RqMtBase.ENCODING) ) ); channel.write( - ByteBuffer.wrap(new Utf8String(RqMtBase.CRLF).bytes()) + ByteBuffer.wrap(RqMtBase.CRLF.getBytes(RqMtBase.ENCODING)) ); this.copy(channel, boundary, body); - } finally { - channel.close(); } - return new RqWithHeader( - new RqLive( - new TempInputStream( - new FileInputStream(file), - file - ) - ), - "Content-Length", - String.valueOf(file.length()) - ); + return new RqTemp(file); } /** * Copy until boundary reached. @@ -324,9 +305,8 @@ private static Map> asMap( final Collection reqs) throws IOException { final Map> map = new HashMap<>(reqs.size()); for (final Request req : reqs) { - final String header = new RqHeaders.Smart( - new RqHeaders.Base(req) - ).single("Content-Disposition"); + final String header = + new RqHeaders.Smart(req).single("Content-Disposition"); final Matcher matcher = RqMtBase.NAME.matcher(header); if (!matcher.matches()) { throw new HttpException( diff --git a/src/main/java/org/takes/rq/multipart/RqTemp.java b/src/main/java/org/takes/rq/multipart/RqTemp.java new file mode 100644 index 000000000..c468b841d --- /dev/null +++ b/src/main/java/org/takes/rq/multipart/RqTemp.java @@ -0,0 +1,64 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2014-2016 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.rq.multipart; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import lombok.EqualsAndHashCode; +import org.takes.rq.RqLive; +import org.takes.rq.RqWithHeader; +import org.takes.rq.RqWrap; +import org.takes.rq.TempInputStream; + +/** + * Request with a temporary file as body. The temporary file will be deleted + * automatically when the body of the request will be closed. + * @author Nicolas Filotto (nicolas.filotto@gmail.com) + * @version $Id$ + * @since 0.33 + * @see org.takes.rq.RqLive + * @see org.takes.rq.TempInputStream + */ +@EqualsAndHashCode(callSuper = true) +final class RqTemp extends RqWrap { + + /** + * Creates a {@code RqTemp} with the specified temporary file. + * @param file The temporary that will be automatically deleted when the + * body of the request will be closed. + * @throws IOException If fails + */ + RqTemp(final File file) throws IOException { + super( + new RqWithHeader( + new RqLive( + new TempInputStream(new FileInputStream(file), file) + ), + "Content-Length", + String.valueOf(file.length()) + ) + ); + } +} diff --git a/src/test/java/org/takes/rq/multipart/RqTempTest.java b/src/test/java/org/takes/rq/multipart/RqTempTest.java new file mode 100644 index 000000000..2fdb1412a --- /dev/null +++ b/src/test/java/org/takes/rq/multipart/RqTempTest.java @@ -0,0 +1,73 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2014-2016 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.rq.multipart; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.Test; +import org.takes.Request; + +/** + * Test case for {@link RqTemp}. + * @author Nicolas Filotto (nicolas.filotto@gmail.com) + * @version $Id$ + * @since 0.33 + */ +public final class RqTempTest { + + /** + * RqTemp can delete the underlying temporary file. + * @throws IOException if some problem occurs. + */ + @Test + public void deletesTempFile() throws IOException { + final File file = File.createTempFile( + RqTempTest.class.getName(), + ".tmp" + ); + Files.write( + file.toPath(), + "Temp file deletion test".getBytes(StandardCharsets.UTF_8) + ); + final Request request = new RqTemp(file); + try { + MatcherAssert.assertThat( + "File is not created!", + file.exists(), + Matchers.is(true) + ); + } finally { + request.body().close(); + } + MatcherAssert.assertThat( + "File exists after stream closure", + file.exists(), + Matchers.is(false) + ); + } +}