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}