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        GenericFileMessage<T> msg = commonBindToExchange(exchange);
117        populateHeaders(msg, false);
118    }
119    
120    /**
121     * Bind this GenericFile to an Exchange
122     */
123    public void bindToExchange(Exchange exchange, boolean isProbeContentTypeFromEndpoint) {
124        GenericFileMessage<T> msg = commonBindToExchange(exchange);
125        populateHeaders(msg, isProbeContentTypeFromEndpoint);
126    }
127
128    private GenericFileMessage<T> commonBindToExchange(Exchange exchange) {
129        Map<String, Object> headers;
130
131        exchange.setProperty(FileComponent.FILE_EXCHANGE_FILE, this);
132        GenericFileMessage<T> msg = new GenericFileMessage<T>(this);
133        if (exchange.hasOut()) {
134            headers = exchange.getOut().hasHeaders() ? exchange.getOut().getHeaders() : null;
135            exchange.setOut(msg);
136        } else {
137            headers = exchange.getIn().hasHeaders() ? exchange.getIn().getHeaders() : null;
138            exchange.setIn(msg);
139        }
140
141        // preserve any existing (non file) headers, before we re-populate headers
142        if (headers != null) {
143            msg.setHeaders(headers);
144            // remove any file related headers, as we will re-populate file headers
145            msg.removeHeaders("CamelFile*");
146        }
147        return msg;
148    }
149
150    /**
151     * Populates the {@link GenericFileMessage} relevant headers
152     *
153     * @param message the message to populate with headers
154     */
155    public void populateHeaders(GenericFileMessage<T> message, boolean isProbeContentTypeFromEndpoint) {
156        if (message != null) {
157            message.setHeader(Exchange.FILE_NAME_ONLY, getFileNameOnly());
158            message.setHeader(Exchange.FILE_NAME, getFileName());
159            message.setHeader(Exchange.FILE_NAME_CONSUMED, getFileName());
160            message.setHeader("CamelFileAbsolute", isAbsolute());
161            message.setHeader("CamelFileAbsolutePath", getAbsoluteFilePath());
162
163            if (extendedAttributes != null) {
164                message.setHeader("CamelFileExtendedAttributes", extendedAttributes);
165            }
166            
167            if ((isProbeContentTypeFromEndpoint || probeContentType) && file instanceof File) {
168                File f = (File) file;
169                Path path = f.toPath();
170                try {
171                    message.setHeader(Exchange.FILE_CONTENT_TYPE, Files.probeContentType(path));
172                } catch (Throwable e) {
173                    // just ignore the exception
174                }
175            }
176    
177            if (isAbsolute()) {
178                message.setHeader(Exchange.FILE_PATH, getAbsoluteFilePath());
179            } else {
180                // we must normalize path according to protocol if we build our own paths
181                String path = normalizePathToProtocol(getEndpointPath() + File.separator + getRelativeFilePath());
182                message.setHeader(Exchange.FILE_PATH, path);
183            }
184    
185            message.setHeader("CamelFileRelativePath", getRelativeFilePath());
186            message.setHeader(Exchange.FILE_PARENT, getParent());
187    
188            if (getFileLength() >= 0) {
189                message.setHeader(Exchange.FILE_LENGTH, getFileLength());
190            }
191            if (getLastModified() > 0) {
192                message.setHeader(Exchange.FILE_LAST_MODIFIED, getLastModified());
193            }
194        }
195    }
196    
197    protected boolean isAbsolute(String name) {
198        return FileUtil.isAbsolute(new File(name));
199    }
200    
201    protected String normalizePath(String name) {
202        return FileUtil.normalizePath(name);
203    }
204   
205    /**
206     * Changes the name of this remote file. This method alters the absolute and
207     * relative names as well.
208     *
209     * @param newName the new name
210     */
211    public void changeFileName(String newName) {
212        LOG.trace("Changing name to: {}", newName);
213
214        // Make sure the names is normalized.
215        String newFileName = FileUtil.normalizePath(newName);
216        String newEndpointPath = FileUtil.normalizePath(endpointPath.endsWith("" + File.separatorChar) ? endpointPath : endpointPath + File.separatorChar);
217
218        LOG.trace("Normalized endpointPath: {}", newEndpointPath);
219        LOG.trace("Normalized newFileName: ()", newFileName);
220
221        File file = new File(newFileName);
222        if (!absolute) {
223            // for relative then we should avoid having the endpoint path duplicated so clip it
224            if (ObjectHelper.isNotEmpty(newEndpointPath) && newFileName.startsWith(newEndpointPath)) {
225                // clip starting endpoint in case it was added
226                // use File.separatorChar as the normalizePath uses this as path separator so we should use the same
227                // in this logic here
228                if (newEndpointPath.endsWith("" + File.separatorChar)) {
229                    newFileName = ObjectHelper.after(newFileName, newEndpointPath);
230                } else {
231                    newFileName = ObjectHelper.after(newFileName, newEndpointPath + File.separatorChar);
232                }
233
234                // reconstruct file with clipped name
235                file = new File(newFileName);
236            }
237        }
238
239        // store the file name only
240        setFileNameOnly(file.getName());
241        setFileName(file.getName());
242
243        // relative path
244        if (file.getParent() != null) {
245            setRelativeFilePath(file.getParent() + getFileSeparator() + file.getName());
246        } else {
247            setRelativeFilePath(file.getName());
248        }
249
250        // absolute path
251        if (isAbsolute(newFileName)) {
252            setAbsolute(true);
253            setAbsoluteFilePath(newFileName);
254        } else {
255            setAbsolute(false);
256            // construct a pseudo absolute filename that the file operations uses even for relative only
257            String path = ObjectHelper.isEmpty(endpointPath) ? "" : endpointPath + getFileSeparator();
258            setAbsoluteFilePath(path + getRelativeFilePath());
259        }
260
261        if (LOG.isTraceEnabled()) {
262            LOG.trace("FileNameOnly: {}", getFileNameOnly());
263            LOG.trace("FileName: {}", getFileName());
264            LOG.trace("Absolute: {}", isAbsolute());
265            LOG.trace("Relative path: {}", getRelativeFilePath());
266            LOG.trace("Absolute path: {}", getAbsoluteFilePath());
267            LOG.trace("Name changed to: {}", this);
268        }
269    }
270
271    public String getRelativeFilePath() {
272        return relativeFilePath;
273    }
274
275    public void setRelativeFilePath(String relativeFilePath) {
276        this.relativeFilePath = normalizePathToProtocol(relativeFilePath);
277    }
278
279    public String getFileName() {
280        return fileName;
281    }
282
283    public void setFileName(String fileName) {
284        this.fileName = normalizePathToProtocol(fileName);
285    }
286
287    public long getFileLength() {
288        return fileLength;
289    }
290
291    public void setFileLength(long fileLength) {
292        this.fileLength = fileLength;
293    }
294
295    public long getLastModified() {
296        return lastModified;
297    }
298
299    public void setLastModified(long lastModified) {
300        this.lastModified = lastModified;
301    }
302
303    public String getCharset() {
304        return charset;
305    }
306
307    public void setCharset(String charset) {
308        this.charset = charset;
309    }
310
311    public Map<String, Object> getExtendedAttributes() {
312        return extendedAttributes;
313    }
314
315    public void setExtendedAttributes(Map<String, Object> extendedAttributes) {
316        this.extendedAttributes = extendedAttributes;
317    }
318
319    @Override
320    public T getFile() {
321        return file;
322    }
323
324    public void setFile(T file) {
325        this.file = file;
326    }
327
328    public Object getBody() {
329        return getBinding().getBody(this);
330    }
331
332    public void setBody(Object os) {
333        getBinding().setBody(this, os);
334    }
335
336    public String getParent() {
337        String parent;
338        if (isAbsolute()) {
339            String name = getAbsoluteFilePath();
340            File path = new File(name);
341            parent = path.getParent();
342        } else {
343            String name = getRelativeFilePath();
344            File path;
345            if (name != null) {
346                path = new File(endpointPath, name);
347            } else {
348                path = new File(endpointPath);
349            }
350            parent = path.getParent();
351        }
352        return normalizePathToProtocol(parent);
353    }
354
355    public GenericFileBinding<T> getBinding() {
356        if (binding == null) {
357            binding = new GenericFileDefaultBinding<T>();
358        }
359        return binding;
360    }
361
362    public void setBinding(GenericFileBinding<T> binding) {
363        this.binding = binding;
364    }
365
366    public void setAbsoluteFilePath(String absoluteFilePath) {
367        this.absoluteFilePath = normalizePathToProtocol(absoluteFilePath);
368    }
369
370    public String getAbsoluteFilePath() {
371        return absoluteFilePath;
372    }
373
374    public boolean isAbsolute() {
375        return absolute;
376    }
377
378    public void setAbsolute(boolean absolute) {
379        this.absolute = absolute;
380    }
381
382    public String getEndpointPath() {
383        return endpointPath;
384    }
385
386    public void setEndpointPath(String endpointPath) {
387        this.endpointPath = normalizePathToProtocol(endpointPath);
388    }
389
390    public String getFileNameOnly() {
391        return fileNameOnly;
392    }
393
394    public void setFileNameOnly(String fileNameOnly) {
395        this.fileNameOnly = fileNameOnly;
396    }
397
398    public boolean isDirectory() {
399        return directory;
400    }
401
402    public void setDirectory(boolean directory) {
403        this.directory = directory;
404    }
405
406    public String getCopyFromAbsoluteFilePath() {
407        return copyFromAbsoluteFilePath;
408    }
409
410    public void setCopyFromAbsoluteFilePath(String copyFromAbsoluteFilePath) {
411        this.copyFromAbsoluteFilePath = copyFromAbsoluteFilePath;
412    }
413
414    /**
415     * Fixes the path separator to be according to the protocol
416     */
417    protected String normalizePathToProtocol(String path) {
418        if (ObjectHelper.isEmpty(path)) {
419            return path;
420        }
421        path = path.replace('/', getFileSeparator());
422        path = path.replace('\\', getFileSeparator());
423        return path;
424    }
425
426    @Override
427    public String toString() {
428        return "GenericFile[" + (absolute ? absoluteFilePath : relativeFilePath) + "]";
429    }
430}