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