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 }