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.NoTypeConversionAvailableException;
032 import org.apache.camel.util.ExchangeHelper;
033 import org.apache.camel.util.ObjectHelper;
034 import org.apache.commons.logging.Log;
035 import org.apache.commons.logging.LogFactory;
036
037 /**
038 * File operations for {@link java.io.File}.
039 */
040 public class FileOperations implements GenericFileOperations<File> {
041 private static final transient Log LOG = LogFactory.getLog(FileOperations.class);
042 private FileEndpoint endpoint;
043
044 public FileOperations() {
045 }
046
047 public FileOperations(FileEndpoint endpoint) {
048 this.endpoint = endpoint;
049 }
050
051 public void setEndpoint(GenericFileEndpoint endpoint) {
052 this.endpoint = (FileEndpoint) endpoint;
053 }
054
055 public boolean deleteFile(String name) throws GenericFileOperationFailedException {
056 File file = new File(name);
057 return file.exists() && file.delete();
058 }
059
060 public boolean renameFile(String from, String to) throws GenericFileOperationFailedException {
061 File file = new File(from);
062 File target = new File(to);
063 return file.renameTo(target);
064 }
065
066 public boolean buildDirectory(String directory, boolean absolute) throws GenericFileOperationFailedException {
067 ObjectHelper.notNull(endpoint, "endpoint");
068
069 // always create endpoint defined directory
070 if (endpoint.isAutoCreate() && !endpoint.getFile().exists()) {
071 if (LOG.isTraceEnabled()) {
072 LOG.trace("Building starting directory: " + endpoint.getFile());
073 }
074 endpoint.getFile().mkdirs();
075 }
076
077 if (ObjectHelper.isEmpty(directory)) {
078 // no directory to build so return true to indicate ok
079 return true;
080 }
081
082 File endpointPath = endpoint.getFile();
083 File target = new File(directory);
084
085 File path;
086 if (absolute) {
087 // absolute path
088 path = target;
089 } else if (endpointPath.equals(target)) {
090 // its just the root of the endpoint path
091 path = endpointPath;
092 } else {
093 // relative after the endpoint path
094 String afterRoot = ObjectHelper.after(directory, endpointPath.getPath() + File.separator);
095 if (ObjectHelper.isNotEmpty(afterRoot)) {
096 // dir is under the root path
097 path = new File(endpoint.getFile(), afterRoot);
098 } else {
099 // dir is relative to the root path
100 path = new File(endpoint.getFile(), directory);
101 }
102 }
103
104 if (path.isDirectory() && path.exists()) {
105 // the directory already exists
106 return true;
107 } else {
108 if (LOG.isTraceEnabled()) {
109 LOG.trace("Building directory: " + path);
110 }
111 return path.mkdirs();
112 }
113 }
114
115 public List<File> listFiles() throws GenericFileOperationFailedException {
116 // noop
117 return null;
118 }
119
120 public List<File> listFiles(String path) throws GenericFileOperationFailedException {
121 // noop
122 return null;
123 }
124
125 public void changeCurrentDirectory(String path) throws GenericFileOperationFailedException {
126 // noop
127 }
128
129 public String getCurrentDirectory() throws GenericFileOperationFailedException {
130 // noop
131 return null;
132 }
133
134 public boolean retrieveFile(String name, GenericFileExchange<File> exchange) throws GenericFileOperationFailedException {
135 // noop as we use type converters to read the body content for java.io.File
136 return true;
137 }
138
139 public boolean storeFile(String fileName, GenericFileExchange<File> exchange) throws GenericFileOperationFailedException {
140 ObjectHelper.notNull(endpoint, "endpoint");
141
142 // we can write the file by 3 different techniques
143 // 1. write file to file
144 // 2. rename a file from a local work path
145 // 3. write stream to file
146
147 File file = new File(fileName);
148 try {
149
150 // is the body file based
151 File source = null;
152 try {
153 if (exchange.getIn().getBody() instanceof File || exchange.getIn().getBody() instanceof GenericFile) {
154 source = exchange.getIn().getBody(File.class);
155 }
156 } catch (NoTypeConversionAvailableException e) {
157 // ignore
158 }
159
160 if (source != null) {
161 // okay we know the body is a file type
162
163 // so try to see if we can optimize by renaming the local work path file instead of doing
164 // a full file to file copy, as the local work copy is to be deleted afterwords anyway
165 // local work path
166 File local = exchange.getIn().getHeader(Exchange.FILE_LOCAL_WORK_PATH, File.class);
167 if (local != null && local.exists()) {
168 boolean renamed = writeFileByLocalWorkPath(local, file);
169 if (renamed) {
170 // clear header as we have renamed the file
171 exchange.getIn().setHeader(Exchange.FILE_LOCAL_WORK_PATH, null);
172 // return as the operation is complete, we just renamed the local work file
173 // to the target.
174 return true;
175 }
176 } else if (source.exists()) {
177 // no there is no local work file so use file to file copy if the source exists
178 writeFileByFile(source, file);
179 return true;
180 }
181 }
182
183 // fallback and use stream based
184 InputStream in = ExchangeHelper.getMandatoryInBody(exchange, InputStream.class);
185 writeFileByStream(in, file);
186 return true;
187 } catch (IOException e) {
188 throw new GenericFileOperationFailedException("Cannot store file: " + file, e);
189 } catch (InvalidPayloadException e) {
190 throw new GenericFileOperationFailedException("Cannot store file: " + file, e);
191 }
192 }
193
194 private boolean writeFileByLocalWorkPath(File source, File file) {
195 if (LOG.isTraceEnabled()) {
196 LOG.trace("Using local work file being renamed from: " + source + " to: " + file);
197 }
198 return source.renameTo(file);
199 }
200
201 private void writeFileByFile(File source, File target) throws IOException {
202 FileChannel in = new FileInputStream(source).getChannel();
203 FileChannel out = null;
204 try {
205 out = prepareOutputFileChannel(target, out);
206
207 if (LOG.isTraceEnabled()) {
208 LOG.trace("Using FileChannel to transfer from: " + in + " to: " + out);
209 }
210 in.transferTo(0, in.size(), out);
211 } finally {
212 ObjectHelper.close(in, source.getName(), LOG);
213 ObjectHelper.close(out, source.getName(), LOG);
214 }
215 }
216
217 private void writeFileByStream(InputStream in, File target) throws IOException {
218 FileChannel out = null;
219 try {
220 out = prepareOutputFileChannel(target, out);
221
222 if (LOG.isTraceEnabled()) {
223 LOG.trace("Using InputStream to transfer from: " + in + " to: " + out);
224 }
225 int size = endpoint.getBufferSize();
226 byte[] buffer = new byte[size];
227 ByteBuffer byteBuffer = ByteBuffer.wrap(buffer);
228 while (true) {
229 int count = in.read(buffer);
230 if (count <= 0) {
231 break;
232 } else if (count < size) {
233 byteBuffer = ByteBuffer.wrap(buffer, 0, count);
234 out.write(byteBuffer);
235 break;
236 } else {
237 out.write(byteBuffer);
238 byteBuffer.clear();
239 }
240 }
241 } finally {
242 ObjectHelper.close(in, target.getName(), LOG);
243 ObjectHelper.close(out, target.getName(), LOG);
244 }
245 }
246
247 /**
248 * Creates and prepares the output file channel. Will position itself in correct position if eg. it should append
249 * or override any existing content.
250 */
251 private FileChannel prepareOutputFileChannel(File target, FileChannel out) throws IOException {
252 if (endpoint.isAppend()) {
253 out = new RandomAccessFile(target, "rw").getChannel();
254 out = out.position(out.size());
255 } else {
256 out = new FileOutputStream(target).getChannel();
257 }
258 return out;
259 }
260
261 }