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