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