001/*
002 * ====================================================================
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements.  See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership.  The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License.  You may obtain a copy of the License at
010 *
011 *   http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing,
014 * software distributed under the License is distributed on an
015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
016 * KIND, either express or implied.  See the License for the
017 * specific language governing permissions and limitations
018 * under the License.
019 * ====================================================================
020 *
021 * This software consists of voluntary contributions made by many
022 * individuals on behalf of the Apache Software Foundation.  For more
023 * information on the Apache Software Foundation, please see
024 * <http://www.apache.org/>.
025 *
026 */
027
028package org.apache.http.nio.entity;
029
030import java.io.File;
031import java.io.FileInputStream;
032import java.io.IOException;
033import java.io.InputStream;
034import java.io.OutputStream;
035import java.io.RandomAccessFile;
036import java.nio.channels.FileChannel;
037
038import org.apache.http.entity.AbstractHttpEntity;
039import org.apache.http.entity.ContentType;
040import org.apache.http.nio.ContentEncoder;
041import org.apache.http.nio.ContentEncoderChannel;
042import org.apache.http.nio.FileContentEncoder;
043import org.apache.http.nio.IOControl;
044import org.apache.http.util.Args;
045
046/**
047 * A self contained, repeatable non-blocking entity that retrieves its content
048 * from a file. This class is mostly used to stream large files of different
049 * types, so one needs to supply the content type of the file to make sure
050 * the content can be correctly recognized and processed by the recipient.
051 *
052 * @since 4.0
053 */
054@SuppressWarnings("deprecation")
055public class NFileEntity extends AbstractHttpEntity
056                         implements HttpAsyncContentProducer, ProducingNHttpEntity {
057
058    private final File file;
059    private RandomAccessFile accessfile;
060    private FileChannel fileChannel;
061    private long idx = -1;
062    private boolean useFileChannels;
063
064    /**
065     * Creates new instance of NFileEntity from the given source {@link File}
066     * with the given content type. If {@code useFileChannels} is set to
067     * {@code true}, the entity will try to use {@link FileContentEncoder}
068     * interface to stream file content directly from the file channel.
069     *
070     * @param file the source file.
071     * @param contentType the content type of the file.
072     * @param useFileChannels flag whether the direct transfer from the file
073     *   channel should be attempted.
074     *
075     * @since 4.2
076     */
077    public NFileEntity(final File file, final ContentType contentType, final boolean useFileChannels) {
078        Args.notNull(file, "File");
079        this.file = file;
080        this.useFileChannels = useFileChannels;
081        if (contentType != null) {
082            setContentType(contentType.toString());
083        }
084    }
085
086    /**
087     * @since 4.2
088     */
089    public NFileEntity(final File file) {
090        Args.notNull(file, "File");
091        this.file = file;
092    }
093    /**
094     * Creates new instance of NFileEntity from the given source {@link File}
095     * with the given content type.
096     *
097     * @param file the source file.
098     * @param contentType the content type of the file.
099     *
100     * @since 4.2
101     */
102    public NFileEntity(final File file, final ContentType contentType) {
103        this(file, contentType, true);
104    }
105
106    /**
107     * @deprecated (4.2) use {@link #NFileEntity(File, ContentType, boolean)}
108     */
109    @Deprecated
110    public NFileEntity(final File file, final String contentType, final boolean useFileChannels) {
111        Args.notNull(file, "File");
112        this.file = file;
113        this.useFileChannels = useFileChannels;
114        setContentType(contentType);
115    }
116
117    /**
118     * @deprecated (4.2) use {@link #NFileEntity(File, ContentType)}
119     */
120    @Deprecated
121    public NFileEntity(final File file, final String contentType) {
122        this(file, contentType, true);
123    }
124
125    /**
126     * {@inheritDoc}
127     *
128     * @since 4.2
129     */
130    @Override
131    public void close() throws IOException {
132        if (accessfile != null) {
133            accessfile.close();
134        }
135        accessfile = null;
136        fileChannel = null;
137    }
138
139    /**
140     * {@inheritDoc}
141     *
142     * @deprecated (4.2) use {@link #close()}
143     */
144    @Override
145    @Deprecated
146    public void finish() throws IOException {
147        close();
148    }
149
150    @Override
151    public long getContentLength() {
152        return file.length();
153    }
154
155    @Override
156    public boolean isRepeatable() {
157        return true;
158    }
159
160    @Override
161    public void produceContent(final ContentEncoder encoder, final IOControl ioctrl)
162            throws IOException {
163        if (accessfile == null) {
164            accessfile = new RandomAccessFile(this.file, "r");
165        }
166        if (fileChannel == null) {
167            fileChannel = accessfile.getChannel();
168            idx = 0;
169        }
170
171        final long transferred;
172        if (useFileChannels && encoder instanceof FileContentEncoder) {
173            transferred = ((FileContentEncoder)encoder)
174                .transfer(fileChannel, idx, Long.MAX_VALUE);
175        } else {
176            transferred = fileChannel.
177                transferTo(idx, Long.MAX_VALUE, new ContentEncoderChannel(encoder));
178        }
179        if (transferred > 0) {
180            idx += transferred;
181        }
182        if (idx >= fileChannel.size()) {
183            encoder.complete();
184            close();
185        }
186    }
187
188    @Override
189    public boolean isStreaming() {
190        return false;
191    }
192
193    @Override
194    public InputStream getContent() throws IOException {
195        return new FileInputStream(this.file);
196    }
197
198    @Override
199    public void writeTo(final OutputStream outstream) throws IOException {
200        Args.notNull(outstream, "Output stream");
201        final InputStream instream = new FileInputStream(this.file);
202        try {
203            final byte[] tmp = new byte[4096];
204            int l;
205            while ((l = instream.read(tmp)) != -1) {
206                outstream.write(tmp, 0, l);
207            }
208            outstream.flush();
209        } finally {
210            instream.close();
211        }
212    }
213
214}