001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.camel.converter.stream;
018
019import java.io.BufferedInputStream;
020import java.io.File;
021import java.io.FileInputStream;
022import java.io.FileNotFoundException;
023import java.io.IOException;
024import java.io.InputStream;
025import java.io.OutputStream;
026import java.nio.channels.Channels;
027import java.nio.channels.FileChannel;
028import java.nio.channels.WritableByteChannel;
029import java.util.ArrayList;
030import java.util.List;
031import javax.crypto.CipherInputStream;
032
033import org.apache.camel.RuntimeCamelException;
034import org.apache.camel.StreamCache;
035import org.apache.camel.util.IOHelper;
036
037/**
038 * A {@link StreamCache} for {@link File}s
039 */
040public final class FileInputStreamCache extends InputStream implements StreamCache {
041    private InputStream stream;
042    private final File file;
043    private final CipherPair ciphers;
044    private final long length;
045    private final FileInputStreamCache.FileInputStreamCloser closer;
046
047    public FileInputStreamCache(File file) throws FileNotFoundException {
048        this(file, null, new FileInputStreamCloser());
049    }
050    
051    FileInputStreamCache(File file, CipherPair ciphers, FileInputStreamCloser closer) throws FileNotFoundException {
052        this.file = file;
053        this.stream = null;
054        this.ciphers = ciphers;
055        this.length = file.length();
056        this.closer = closer;
057        this.closer.add(this);
058    }
059    
060    @Override
061    public void close() {
062        if (stream != null) {
063            IOHelper.close(stream);
064        }
065    }
066
067    @Override
068    public void reset() {
069        // reset by closing and creating a new stream based on the file
070        close();
071        // reset by creating a new stream based on the file
072        stream = null;
073        
074        if (!file.exists()) {
075            throw new RuntimeCamelException("Cannot reset stream from file " + file);
076        }
077    }
078
079    public void writeTo(OutputStream os) throws IOException {
080        if (stream == null && ciphers == null) {
081            FileInputStream s = new FileInputStream(file);
082            long len = file.length();
083            WritableByteChannel out;
084            if (os instanceof WritableByteChannel) {
085                out = (WritableByteChannel)os;
086            } else {
087                out = Channels.newChannel(os);
088            }
089            FileChannel fc = s.getChannel();
090            long pos = 0;
091            while (pos < len) {
092                long i = fc.transferTo(pos, len - pos, out);
093                pos += i;
094            }
095            s.close();
096            fc.close();
097        } else {
098            IOHelper.copy(getInputStream(), os);
099        }
100    }
101
102    public StreamCache copy() throws IOException {
103        FileInputStreamCache copy = new FileInputStreamCache(file, ciphers, closer);
104        return copy;
105    }
106
107    public boolean inMemory() {
108        return false;
109    }
110
111    public long length() {
112        return length;
113    }
114
115    @Override
116    public int available() throws IOException {
117        return getInputStream().available();
118    }
119
120    @Override
121    public int read() throws IOException {
122        return getInputStream().read();
123    }
124
125    protected InputStream getInputStream() throws IOException {
126        if (stream == null) {
127            stream = createInputStream(file);
128        }
129        return stream;
130    }
131
132    private InputStream createInputStream(File file) throws IOException {
133        InputStream in = new BufferedInputStream(new FileInputStream(file));
134        if (ciphers != null) {
135            in = new CipherInputStream(in, ciphers.getDecryptor()) {
136                boolean closed;
137                public void close() throws IOException {
138                    if (!closed) {
139                        super.close();
140                        closed = true;
141                    }
142                }
143            };
144        }
145        return in;
146    }
147
148    /** 
149     * Collects all FileInputStreamCache instances of a temporary file which must be closed
150     * at the end of the route.
151     * 
152     * @see CachedOutputStream
153     */
154    static class FileInputStreamCloser {
155        
156        // there can be several input streams, for example in the multi-cast parallel processing
157        private List<FileInputStreamCache> fileInputStreamCaches;
158        
159        /** Adds a FileInputStreamCache instance to the closer.
160         * <p>
161         * Must be synchronized, because can be accessed by several threads. 
162         */
163        synchronized void add(FileInputStreamCache fileInputStreamCache) {
164            if (fileInputStreamCaches == null) {
165                fileInputStreamCaches = new ArrayList<FileInputStreamCache>(3);
166            }
167            fileInputStreamCaches.add(fileInputStreamCache);
168        }
169        
170        void close() {
171            if (fileInputStreamCaches != null) {
172                for (FileInputStreamCache fileInputStreamCache : fileInputStreamCaches) {
173                    fileInputStreamCache.close();
174                }
175                fileInputStreamCaches.clear();
176            }
177        }
178    }
179
180}