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 }