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