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