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 */
017package org.apache.camel.component.file;
018
019import java.io.File;
020import java.util.Map;
021
022import org.apache.camel.Exchange;
023import org.apache.camel.WrappedFile;
024import org.apache.camel.util.FileUtil;
025import org.apache.camel.util.ObjectHelper;
026import org.slf4j.Logger;
027import org.slf4j.LoggerFactory;
028
029/**
030 * Generic File. Specific implementations of a file based endpoint need to
031 * provide a File for transfer.
032 */
033public class GenericFile<T> implements WrappedFile<T>  {
034    private static final Logger LOG = LoggerFactory.getLogger(GenericFile.class);
035
036    private String endpointPath;
037    private String fileName;
038    private String fileNameOnly;
039    private String relativeFilePath;
040    private String absoluteFilePath;
041    private long fileLength;
042    private long lastModified;
043    private T file;
044    private GenericFileBinding<T> binding;
045    private boolean absolute;
046    private boolean directory;
047    private String charset;
048
049    public char getFileSeparator() {
050        return File.separatorChar;
051    }
052
053    /**
054     * Creates a copy based on the source
055     *
056     * @param source the source
057     * @return a copy of the source
058     */
059    @SuppressWarnings("unchecked")
060    public GenericFile<T> copyFrom(GenericFile<T> source) {
061        GenericFile<T> result;
062        try {
063            result = source.getClass().newInstance();
064        } catch (Exception e) {
065            throw ObjectHelper.wrapRuntimeCamelException(e);
066        }
067        result.setEndpointPath(source.getEndpointPath());
068        result.setAbsolute(source.isAbsolute());
069        result.setDirectory(source.isDirectory());
070        result.setAbsoluteFilePath(source.getAbsoluteFilePath());
071        result.setRelativeFilePath(source.getRelativeFilePath());
072        result.setFileName(source.getFileName());
073        result.setFileNameOnly(source.getFileNameOnly());
074        result.setFileLength(source.getFileLength());
075        result.setLastModified(source.getLastModified());
076        result.setFile(source.getFile());
077        result.setBody(source.getBody());
078        result.setBinding(source.getBinding());
079        result.setCharset(source.getCharset());
080
081        copyFromPopulateAdditional(source, result);
082        return result;
083    }
084
085    /**
086     * Copies additional information from the source to the result.
087     * <p/>
088     * Inherited classes can override this method and copy their specific data.
089     *
090     * @param source  the source
091     * @param result  the result
092     */
093    public void copyFromPopulateAdditional(GenericFile<T> source, GenericFile<T> result) {
094        // noop
095    }
096
097    /**
098     * Bind this GenericFile to an Exchange
099     */
100    public void bindToExchange(Exchange exchange) {
101        Map<String, Object> headers;
102
103        exchange.setProperty(FileComponent.FILE_EXCHANGE_FILE, this);
104        GenericFileMessage<T> msg = new GenericFileMessage<T>(this);
105        if (exchange.hasOut()) {
106            headers = exchange.getOut().hasHeaders() ? exchange.getOut().getHeaders() : null;
107            exchange.setOut(msg);
108        } else {
109            headers = exchange.getIn().hasHeaders() ? exchange.getIn().getHeaders() : null;
110            exchange.setIn(msg);
111        }
112
113        // preserve any existing (non file) headers, before we re-populate headers
114        if (headers != null) {
115            msg.setHeaders(headers);
116            // remove any file related headers, as we will re-populate file headers
117            msg.removeHeaders("CamelFile*");
118        }
119        populateHeaders(msg);
120    }
121
122    /**
123     * Populates the {@link GenericFileMessage} relevant headers
124     *
125     * @param message the message to populate with headers
126     */
127    public void populateHeaders(GenericFileMessage<T> message) {
128        if (message != null) {
129            message.setHeader(Exchange.FILE_NAME_ONLY, getFileNameOnly());
130            message.setHeader(Exchange.FILE_NAME, getFileName());
131            message.setHeader(Exchange.FILE_NAME_CONSUMED, getFileName());
132            message.setHeader("CamelFileAbsolute", isAbsolute());
133            message.setHeader("CamelFileAbsolutePath", getAbsoluteFilePath());
134    
135            if (isAbsolute()) {
136                message.setHeader(Exchange.FILE_PATH, getAbsoluteFilePath());
137            } else {
138                // we must normalize path according to protocol if we build our own paths
139                String path = normalizePathToProtocol(getEndpointPath() + File.separator + getRelativeFilePath());
140                message.setHeader(Exchange.FILE_PATH, path);
141            }
142    
143            message.setHeader("CamelFileRelativePath", getRelativeFilePath());
144            message.setHeader(Exchange.FILE_PARENT, getParent());
145    
146            if (getFileLength() >= 0) {
147                message.setHeader(Exchange.FILE_LENGTH, getFileLength());
148            }
149            if (getLastModified() > 0) {
150                message.setHeader(Exchange.FILE_LAST_MODIFIED, getLastModified());
151            }
152        }
153    }
154    
155    protected boolean isAbsolute(String name) {
156        return FileUtil.isAbsolute(new File(name));
157    }
158    
159    protected String normalizePath(String name) {
160        return FileUtil.normalizePath(name);
161    }
162   
163    /**
164     * Changes the name of this remote file. This method alters the absolute and
165     * relative names as well.
166     *
167     * @param newName the new name
168     */
169    public void changeFileName(String newName) {
170        LOG.trace("Changing name to: {}", newName);
171
172        // Make sure the names is normalized.
173        String newFileName = FileUtil.normalizePath(newName);
174        String newEndpointPath = FileUtil.normalizePath(endpointPath);
175
176        LOG.trace("Normalized endpointPath: {}", newEndpointPath);
177        LOG.trace("Normalized newFileName: ()", newFileName);
178
179        File file = new File(newFileName);
180        if (!absolute) {
181            // for relative then we should avoid having the endpoint path duplicated so clip it
182            if (ObjectHelper.isNotEmpty(newEndpointPath) && newFileName.startsWith(newEndpointPath)) {
183                // clip starting endpoint in case it was added
184                // use File.separatorChar as the normalizePath uses this as path separator so we should use the same
185                // in this logic here
186                if (newEndpointPath.endsWith("" + File.separatorChar)) {
187                    newFileName = ObjectHelper.after(newFileName, newEndpointPath);
188                } else {
189                    newFileName = ObjectHelper.after(newFileName, newEndpointPath + File.separatorChar);
190                }
191
192                // reconstruct file with clipped name
193                file = new File(newFileName);
194            }
195        }
196
197        // store the file name only
198        setFileNameOnly(file.getName());
199        setFileName(file.getName());
200
201        // relative path
202        if (file.getParent() != null) {
203            setRelativeFilePath(file.getParent() + getFileSeparator() + file.getName());
204        } else {
205            setRelativeFilePath(file.getName());
206        }
207
208        // absolute path
209        if (isAbsolute(newFileName)) {
210            setAbsolute(true);
211            setAbsoluteFilePath(newFileName);
212        } else {
213            setAbsolute(false);
214            // construct a pseudo absolute filename that the file operations uses even for relative only
215            String path = ObjectHelper.isEmpty(endpointPath) ? "" : endpointPath + getFileSeparator();
216            setAbsoluteFilePath(path + getRelativeFilePath());
217        }
218
219        if (LOG.isTraceEnabled()) {
220            LOG.trace("FileNameOnly: {}", getFileNameOnly());
221            LOG.trace("FileName: {}", getFileName());
222            LOG.trace("Absolute: {}", isAbsolute());
223            LOG.trace("Relative path: {}", getRelativeFilePath());
224            LOG.trace("Absolute path: {}", getAbsoluteFilePath());
225            LOG.trace("Name changed to: {}", this);
226        }
227    }
228
229    public String getRelativeFilePath() {
230        return relativeFilePath;
231    }
232
233    public void setRelativeFilePath(String relativeFilePath) {
234        this.relativeFilePath = normalizePathToProtocol(relativeFilePath);
235    }
236
237    public String getFileName() {
238        return fileName;
239    }
240
241    public void setFileName(String fileName) {
242        this.fileName = normalizePathToProtocol(fileName);
243    }
244
245    public long getFileLength() {
246        return fileLength;
247    }
248
249    public void setFileLength(long fileLength) {
250        this.fileLength = fileLength;
251    }
252
253    public long getLastModified() {
254        return lastModified;
255    }
256
257    public void setLastModified(long lastModified) {
258        this.lastModified = lastModified;
259    }
260
261    public String getCharset() {
262        return charset;
263    }
264
265    public void setCharset(String charset) {
266        this.charset = charset;
267    }
268
269    @Override
270    public T getFile() {
271        return file;
272    }
273
274    public void setFile(T file) {
275        this.file = file;
276    }
277
278    public Object getBody() {
279        return getBinding().getBody(this);
280    }
281
282    public void setBody(Object os) {
283        getBinding().setBody(this, os);
284    }
285
286    public String getParent() {
287        String parent;
288        if (isAbsolute()) {
289            String name = getAbsoluteFilePath();
290            File path = new File(name);
291            parent = path.getParent();
292        } else {
293            String name = getRelativeFilePath();
294            File path;
295            if (name != null) {
296                path = new File(endpointPath, name);
297            } else {
298                path = new File(endpointPath);
299            }
300            parent = path.getParent();
301        }
302        return normalizePathToProtocol(parent);
303    }
304
305    public GenericFileBinding<T> getBinding() {
306        if (binding == null) {
307            binding = new GenericFileDefaultBinding<T>();
308        }
309        return binding;
310    }
311
312    public void setBinding(GenericFileBinding<T> binding) {
313        this.binding = binding;
314    }
315
316    public void setAbsoluteFilePath(String absoluteFilePath) {
317        this.absoluteFilePath = normalizePathToProtocol(absoluteFilePath);
318    }
319
320    public String getAbsoluteFilePath() {
321        return absoluteFilePath;
322    }
323
324    public boolean isAbsolute() {
325        return absolute;
326    }
327
328    public void setAbsolute(boolean absolute) {
329        this.absolute = absolute;
330    }
331
332    public String getEndpointPath() {
333        return endpointPath;
334    }
335
336    public void setEndpointPath(String endpointPath) {
337        this.endpointPath = normalizePathToProtocol(endpointPath);
338    }
339
340    public String getFileNameOnly() {
341        return fileNameOnly;
342    }
343
344    public void setFileNameOnly(String fileNameOnly) {
345        this.fileNameOnly = fileNameOnly;
346    }
347
348    public boolean isDirectory() {
349        return directory;
350    }
351
352    public void setDirectory(boolean directory) {
353        this.directory = directory;
354    }
355
356    /**
357     * Fixes the path separator to be according to the protocol
358     */
359    protected String normalizePathToProtocol(String path) {
360        if (ObjectHelper.isEmpty(path)) {
361            return path;
362        }
363        path = path.replace('/', getFileSeparator());
364        path = path.replace('\\', getFileSeparator());
365        return path;
366    }
367
368    @Override
369    public String toString() {
370        return "GenericFile[" + (absolute ? absoluteFilePath : relativeFilePath) + "]";
371    }
372}