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