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 }