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 }