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