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     */
017    package org.apache.camel.component.file;
018    
019    import java.io.File;
020    import java.io.FileInputStream;
021    import java.io.FileOutputStream;
022    import java.io.IOException;
023    import java.io.InputStream;
024    import java.io.RandomAccessFile;
025    import java.nio.ByteBuffer;
026    import java.nio.channels.FileChannel;
027    import java.util.List;
028    
029    import org.apache.camel.Exchange;
030    import org.apache.camel.InvalidPayloadException;
031    import org.apache.camel.util.ExchangeHelper;
032    import org.apache.camel.util.ObjectHelper;
033    import org.apache.commons.logging.Log;
034    import org.apache.commons.logging.LogFactory;
035    
036    /**
037     * File operations for {@link java.io.File}.
038     */
039    public class FileOperations implements GenericFileOperations<File> {
040        private static final transient Log LOG = LogFactory.getLog(FileOperations.class);
041        private FileEndpoint endpoint;
042    
043        public FileOperations() {
044        }
045    
046        public FileOperations(FileEndpoint endpoint) {
047            this.endpoint = endpoint;
048        }
049    
050        public void setEndpoint(GenericFileEndpoint endpoint) {
051            this.endpoint = (FileEndpoint) endpoint;
052        }
053    
054        public boolean deleteFile(String name) throws GenericFileOperationFailedException {        
055            File file = new File(name);
056            return file.exists() && file.delete();
057        }
058    
059        public boolean renameFile(String from, String to) throws GenericFileOperationFailedException {
060            File file = new File(from);
061            File target = new File(to);        
062            return file.renameTo(target);
063        }
064    
065        public boolean existsFile(String name) throws GenericFileOperationFailedException {
066            File file = new File(name);
067            return file.exists();
068        }
069    
070        public boolean buildDirectory(String directory, boolean absolute) throws GenericFileOperationFailedException {
071            ObjectHelper.notNull(endpoint, "endpoint");       
072    
073            // always create endpoint defined directory
074            if (endpoint.isAutoCreate() && !endpoint.getFile().exists()) {
075                if (LOG.isTraceEnabled()) {
076                    LOG.trace("Building starting directory: " + endpoint.getFile());
077                }
078                endpoint.getFile().mkdirs();
079            }
080    
081            if (ObjectHelper.isEmpty(directory)) {
082                // no directory to build so return true to indicate ok
083                return true;
084            }
085    
086            File endpointPath = endpoint.getFile();
087            File target = new File(directory);
088    
089            File path;
090            if (absolute) {
091                // absolute path
092                path = target;
093            } else if (endpointPath.equals(target)) {
094                // its just the root of the endpoint path
095                path = endpointPath;
096            } else {
097                // relative after the endpoint path
098                String afterRoot = ObjectHelper.after(directory, endpointPath.getPath() + File.separator);
099                if (ObjectHelper.isNotEmpty(afterRoot)) {
100                    // dir is under the root path
101                    path = new File(endpoint.getFile(), afterRoot);
102                } else {
103                    // dir is relative to the root path
104                    path = new File(endpoint.getFile(), directory);
105                }
106            }
107    
108            if (path.isDirectory() && path.exists()) {
109                // the directory already exists
110                return true;
111            } else {
112                if (LOG.isTraceEnabled()) {
113                    LOG.trace("Building directory: " + path);
114                }
115                return path.mkdirs();
116            }
117        }
118    
119        public List<File> listFiles() throws GenericFileOperationFailedException {
120            // noop
121            return null;
122        }
123    
124        public List<File> listFiles(String path) throws GenericFileOperationFailedException {
125            // noop
126            return null;
127        }
128    
129        public void changeCurrentDirectory(String path) throws GenericFileOperationFailedException {
130            // noop
131        }
132    
133        public String getCurrentDirectory() throws GenericFileOperationFailedException {
134            // noop
135            return null;
136        }
137    
138        public boolean retrieveFile(String name, Exchange exchange) throws GenericFileOperationFailedException {
139            // noop as we use type converters to read the body content for java.io.File
140            return true;
141        }
142    
143        public boolean storeFile(String fileName, Exchange exchange) throws GenericFileOperationFailedException {
144            ObjectHelper.notNull(endpoint, "endpoint");
145    
146            File file = new File(fileName);
147    
148            // if an existing file already exsists what should we do?
149            if (file.exists()) {
150                if (endpoint.getFileExist() == GenericFileExist.Ignore) {
151                    // ignore but indicate that the file was written
152                    if (LOG.isTraceEnabled()) {
153                        LOG.trace("An existing file already exists: " + file + ". Ignore and do not override it.");
154                    }
155                    return true;
156                } else if (endpoint.getFileExist() == GenericFileExist.Fail) {
157                    throw new GenericFileOperationFailedException("File already exist: " + file + ". Cannot write new file.");
158                }
159            }
160    
161            // we can write the file by 3 different techniques
162            // 1. write file to file
163            // 2. rename a file from a local work path
164            // 3. write stream to file
165            try {
166    
167                // is the body file based
168                File source = null;
169                if (exchange.getIn().getBody() instanceof File || exchange.getIn().getBody() instanceof GenericFile) {
170                    source = exchange.getIn().getBody(File.class);
171                }
172    
173                if (source != null) {
174                    // okay we know the body is a file type
175    
176                    // so try to see if we can optimize by renaming the local work path file instead of doing
177                    // a full file to file copy, as the local work copy is to be deleted afterwords anyway
178                    // local work path
179                    File local = exchange.getIn().getHeader(Exchange.FILE_LOCAL_WORK_PATH, File.class);
180                    if (local != null && local.exists()) {
181                        boolean renamed = writeFileByLocalWorkPath(local, file);
182                        if (renamed) {
183                            // clear header as we have renamed the file
184                            exchange.getIn().setHeader(Exchange.FILE_LOCAL_WORK_PATH, null);
185                            // return as the operation is complete, we just renamed the local work file
186                            // to the target.
187                            return true;
188                        }
189                    } else if (source.exists()) {
190                        // no there is no local work file so use file to file copy if the source exists
191                        writeFileByFile(source, file);
192                        return true;
193                    }
194                }
195    
196                // fallback and use stream based
197                InputStream in = ExchangeHelper.getMandatoryInBody(exchange, InputStream.class);
198                writeFileByStream(in, file);
199                return true;
200            } catch (IOException e) {
201                throw new GenericFileOperationFailedException("Cannot store file: " + file, e);
202            } catch (InvalidPayloadException e) {
203                throw new GenericFileOperationFailedException("Cannot store file: " + file, e);
204            }
205        }
206    
207        private boolean writeFileByLocalWorkPath(File source, File file) {
208            if (LOG.isTraceEnabled()) {
209                LOG.trace("Using local work file being renamed from: " + source + " to: " + file);
210            }
211            return source.renameTo(file);
212        }
213    
214        private void writeFileByFile(File source, File target) throws IOException {
215            FileChannel in = new FileInputStream(source).getChannel();
216            FileChannel out = null;
217            try {
218                out = prepareOutputFileChannel(target, out);
219    
220                if (LOG.isTraceEnabled()) {
221                    LOG.trace("Using FileChannel to transfer from: " + in + " to: " + out);
222                }
223                in.transferTo(0, in.size(), out);
224            } finally {
225                ObjectHelper.close(in, source.getName(), LOG);
226                ObjectHelper.close(out, source.getName(), LOG);
227            }
228        }
229    
230        private void writeFileByStream(InputStream in, File target) throws IOException {
231            FileChannel out = null;
232            try {
233                out = prepareOutputFileChannel(target, out);
234    
235                if (LOG.isTraceEnabled()) {
236                    LOG.trace("Using InputStream to transfer from: " + in + " to: " + out);
237                }
238                int size = endpoint.getBufferSize();
239                byte[] buffer = new byte[size];
240                ByteBuffer byteBuffer = ByteBuffer.wrap(buffer);
241                while (true) {
242                    int count = in.read(buffer);
243                    if (count <= 0) {
244                        break;
245                    } else if (count < size) {
246                        byteBuffer = ByteBuffer.wrap(buffer, 0, count);
247                        out.write(byteBuffer);
248                        break;
249                    } else {
250                        out.write(byteBuffer);
251                        byteBuffer.clear();
252                    }
253                }
254            } finally {
255                ObjectHelper.close(in, target.getName(), LOG);
256                ObjectHelper.close(out, target.getName(), LOG);
257            }
258        }
259    
260        /**
261         * Creates and prepares the output file channel. Will position itself in correct position if eg. it should append
262         * or override any existing content.
263         */
264        private FileChannel prepareOutputFileChannel(File target, FileChannel out) throws IOException {
265            if (endpoint.getFileExist() == GenericFileExist.Append) {
266                out = new RandomAccessFile(target, "rw").getChannel();
267                out = out.position(out.size());
268            } else {
269                // will override
270                out = new FileOutputStream(target).getChannel();
271            }
272            return out;
273        }
274    }