blob: 71a1117d1c62cc64710e7fcdace201ec947af34b [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.camel.processor.aggregate.zipfile;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import org.apache.camel.AggregationStrategy;
import org.apache.camel.Exchange;
import org.apache.camel.WrappedFile;
import org.apache.camel.component.file.FileConsumer;
import org.apache.camel.component.file.GenericFile;
import org.apache.camel.component.file.GenericFileMessage;
import org.apache.camel.component.file.GenericFileOperationFailedException;
import org.apache.camel.spi.Synchronization;
import org.apache.camel.util.FileUtil;
import org.apache.camel.util.IOHelper;
/**
* This aggregation strategy will aggregate all incoming messages into a ZIP file.
* <p>If the incoming exchanges contain {@link GenericFileMessage} file name will
* be taken from the body otherwise the body content will be treated as a byte
* array and the ZIP entry will be named using the message id (unless the flag
* useFilenameHeader is set to true.</p>
* <p><b>Note:</b> Please note that this aggregation strategy requires eager
* completion check to work properly.</p>
*/
public class ZipAggregationStrategy implements AggregationStrategy {
private String filePrefix;
private String fileSuffix = ".zip";
private boolean preserveFolderStructure;
private boolean useFilenameHeader;
private File parentDir = new File(System.getProperty("java.io.tmpdir"));
public ZipAggregationStrategy() {
this(false, false);
}
/**
* @param preserveFolderStructure if true, the folder structure is preserved when the source is
* a type of {@link GenericFileMessage}. If used with a file, use recursive=true.
*/
public ZipAggregationStrategy(boolean preserveFolderStructure) {
this(preserveFolderStructure, false);
}
/**
* @param preserveFolderStructure if true, the folder structure is preserved when the source is
* a type of {@link GenericFileMessage}. If used with a file, use recursive=true.
* @param useFilenameHeader if true, the filename header will be used to name aggregated byte arrays
* within the ZIP file.
*/
public ZipAggregationStrategy(boolean preserveFolderStructure, boolean useFilenameHeader) {
this.preserveFolderStructure = preserveFolderStructure;
this.useFilenameHeader = useFilenameHeader;
}
/**
* Gets the prefix used when creating the ZIP file name.
* @return the prefix
*/
public String getFilePrefix() {
return filePrefix;
}
/**
* Sets the prefix that will be used when creating the ZIP filename.
* @param filePrefix prefix to use on ZIP file.
*/
public void setFilePrefix(String filePrefix) {
this.filePrefix = filePrefix;
}
/**
* Gets the suffix used when creating the ZIP file name.
* @return the suffix
*/
public String getFileSuffix() {
return fileSuffix;
}
/**
* Sets the suffix that will be used when creating the ZIP filename.
* @param fileSuffix suffix to use on ZIP file.
*/
public void setFileSuffix(String fileSuffix) {
this.fileSuffix = fileSuffix;
}
public File getParentDir() {
return parentDir;
}
/**
* Sets the parent directory to use for writing temporary files.
*/
public void setParentDir(File parentDir) {
this.parentDir = parentDir;
}
/**
* Sets the parent directory to use for writing temporary files.
*/
public void setParentDir(String parentDir) {
this.parentDir = new File(parentDir);
}
@Override
public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
File zipFile;
Exchange answer = oldExchange;
// Guard against empty new exchanges
if (newExchange == null) {
return oldExchange;
}
// First time for this aggregation
if (oldExchange == null) {
try {
zipFile = FileUtil.createTempFile(this.filePrefix, this.fileSuffix, this.parentDir);
} catch (IOException e) {
throw new GenericFileOperationFailedException(e.getMessage(), e);
}
answer = newExchange;
answer.addOnCompletion(new DeleteZipFileOnCompletion(zipFile));
} else {
zipFile = oldExchange.getIn().getBody(File.class);
}
Object body = newExchange.getIn().getBody();
if (body instanceof WrappedFile) {
body = ((WrappedFile) body).getFile();
}
if (body instanceof File) {
try {
File appendFile = (File) body;
// do not try to append empty files
if (appendFile.length() > 0) {
String entryName = preserveFolderStructure ? newExchange.getIn().getHeader(Exchange.FILE_NAME, String.class) : newExchange.getIn().getMessageId();
addFileToZip(zipFile, appendFile, this.preserveFolderStructure ? entryName : null);
GenericFile<File> genericFile =
FileConsumer.asGenericFile(
zipFile.getParent(), zipFile, Charset.defaultCharset().toString(), false);
genericFile.bindToExchange(answer);
}
} catch (Exception e) {
throw new GenericFileOperationFailedException(e.getMessage(), e);
}
} else {
// Handle all other messages
try {
byte[] buffer = newExchange.getIn().getMandatoryBody(byte[].class);
// do not try to append empty data
if (buffer.length > 0) {
String entryName = useFilenameHeader ? newExchange.getIn().getHeader(Exchange.FILE_NAME, String.class) : newExchange.getIn().getMessageId();
addEntryToZip(zipFile, entryName, buffer, buffer.length);
GenericFile<File> genericFile = FileConsumer.asGenericFile(
zipFile.getParent(), zipFile, Charset.defaultCharset().toString(), false);
genericFile.bindToExchange(answer);
}
} catch (Exception e) {
throw new GenericFileOperationFailedException(e.getMessage(), e);
}
}
return answer;
}
private static void addFileToZip(File source, File file, String fileName) throws IOException {
File tmpZip = Files.createTempFile(source.getName(), null).toFile();
tmpZip.delete();
if (!source.renameTo(tmpZip)) {
throw new IOException("Could not make temp file (" + source.getName() + ")");
}
byte[] buffer = new byte[8192];
FileInputStream fis = new FileInputStream(tmpZip);
ZipInputStream zin = new ZipInputStream(fis);
ZipOutputStream out = new ZipOutputStream(new FileOutputStream(source));
try {
InputStream in = new FileInputStream(file);
out.putNextEntry(new ZipEntry(fileName == null ? file.getName() : fileName));
for (int read = in.read(buffer); read > -1; read = in.read(buffer)) {
out.write(buffer, 0, read);
}
out.closeEntry();
IOHelper.close(in);
for (ZipEntry ze = zin.getNextEntry(); ze != null; ze = zin.getNextEntry()) {
out.putNextEntry(ze);
for (int read = zin.read(buffer); read > -1; read = zin.read(buffer)) {
out.write(buffer, 0, read);
}
out.closeEntry();
}
} finally {
IOHelper.close(fis, zin, out);
}
tmpZip.delete();
}
private static void addEntryToZip(File source, String entryName, byte[] buffer, int length) throws IOException {
File tmpZip = Files.createTempFile(source.getName(), null).toFile();
tmpZip.delete();
if (!source.renameTo(tmpZip)) {
throw new IOException("Cannot create temp file: " + source.getName());
}
FileInputStream fis = new FileInputStream(tmpZip);
ZipInputStream zin = new ZipInputStream(fis);
ZipOutputStream out = new ZipOutputStream(new FileOutputStream(source));
try {
out.putNextEntry(new ZipEntry(entryName));
out.write(buffer, 0, length);
out.closeEntry();
for (ZipEntry ze = zin.getNextEntry(); ze != null; ze = zin.getNextEntry()) {
out.putNextEntry(ze);
for (int read = zin.read(buffer); read > -1; read = zin.read(buffer)) {
out.write(buffer, 0, read);
}
out.closeEntry();
}
} finally {
IOHelper.close(fis, zin, out);
}
tmpZip.delete();
}
/**
* This callback class is used to clean up the temporary ZIP file once the exchange has completed.
*/
private class DeleteZipFileOnCompletion implements Synchronization {
private final File fileToDelete;
DeleteZipFileOnCompletion(File fileToDelete) {
this.fileToDelete = fileToDelete;
}
@Override
public void onFailure(Exchange exchange) {
// Keep the file if something gone a miss.
}
@Override
public void onComplete(Exchange exchange) {
FileUtil.deleteFile(this.fileToDelete);
}
}
}