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 }