Migrate Zip Extension as a core module

This commit is contained in:
abilan
2023-02-16 13:06:44 -05:00
parent 39c73d404a
commit 210a2f9d2c
48 changed files with 2821 additions and 5 deletions

View File

@@ -87,8 +87,8 @@ ext {
lettuceVersion = '6.2.3.RELEASE'
log4jVersion = '2.19.0'
mailVersion = '1.0.0'
micrometerPropagationVersion = '1.0.1-SNAPSHOT'
micrometerTracingVersion = '1.0.2-SNAPSHOT'
micrometerPropagationVersion = '1.0.2'
micrometerTracingVersion = '1.0.2'
micrometerVersion = '1.11.0-SNAPSHOT'
mockitoVersion = '5.1.1'
mongoDriverVersion = '4.8.2'
@@ -96,7 +96,7 @@ ext {
pahoMqttClientVersion = '1.2.5'
postgresVersion = '42.5.2'
r2dbch2Version = '1.0.0.RELEASE'
reactorVersion = '2022.0.3-SNAPSHOT'
reactorVersion = '2022.0.3'
resilience4jVersion = '2.0.2'
romeToolsVersion = '1.18.0'
rsocketVersion = '1.1.3'
@@ -104,16 +104,17 @@ ext {
smackVersion = '4.4.6'
springAmqpVersion = '3.0.2-SNAPSHOT'
springDataVersion = '2023.0.0-SNAPSHOT'
springGraphqlVersion = '1.2.0-SNAPSHOT'
springGraphqlVersion = '1.1.2-SNAPSHOT'
springKafkaVersion = '3.0.3-SNAPSHOT'
springRetryVersion = '2.0.0'
springSecurityVersion = '6.1.0-SNAPSHOT'
springVersion = '6.0.5-SNAPSHOT'
springVersion = '6.0.5'
springWsVersion = '4.0.1'
testcontainersVersion = '1.17.6'
tomcatVersion = '11.0.0-M1'
xmlUnitVersion = '2.9.1'
xstreamVersion = '1.4.20'
ztZipVersion = '1.15'
javaProjects = subprojects - project(':spring-integration-bom')
}
@@ -1094,6 +1095,15 @@ project('spring-integration-zeromq') {
}
}
project('spring-integration-zip') {
description = 'Spring Integration Zip Support'
dependencies {
api project(':spring-integration-file')
api "org.zeroturnaround:zt-zip:$ztZipVersion"
}
}
project('spring-integration-zookeeper') {
description = 'Spring Integration Zookeeper Support'
dependencies {

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2015-2023 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.integration.zip;
/**
* Zip adapter specific message headers.
*
* @author Gunnar Hillert
*
* @since 6.1
*/
public abstract class ZipHeaders {
public static final String PREFIX = "zip_";
public static final String ZIP_ENTRY_FILE_NAME = PREFIX + "entryFilename";
public static final String ZIP_ENTRY_PATH = PREFIX + "entryPath";
public static final String ZIP_ENTRY_LAST_MODIFIED_DATE = PREFIX + "entryLastModifiedDate";
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2015-2023 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.integration.zip.config.xml;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.xml.AbstractTransformerParser;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.util.StringUtils;
/**
* Base class for Zip transformer parsers.
*
* @author Gunnar Hillert
* @author Artem Bilan
*
* @since 6.1
*/
public abstract class AbstractZipTransformerParser extends AbstractTransformerParser {
/**
* @param element The XML Element to process
* @param parserContext The Spring ParserContext
* @param builder BeanDefinitionBuilder for constructing Bean Definitions
*/
@Override
protected final void parseTransformer(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
String deleteFiles = element.getAttribute("delete-files");
if (StringUtils.hasText(deleteFiles)) {
builder.addPropertyValue("deleteFiles", deleteFiles);
}
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "charset");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "result-type", "zipResultType");
postProcessTransformer(element, parserContext, builder);
}
/**
* Subclasses may override this method to provide additional configuration.
*
* @param element The XML Element to process
* @param parserContext The Spring ParserContext
* @param builder BeanDefinitionBuilder for constructing Bean Definitions
*/
protected void postProcessTransformer(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2015-2023 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.integration.zip.config.xml;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.integration.zip.transformer.UnZipTransformer;
/**
* Parser for the 'unzip-transformer' element.
*
* @author Gunnar Hillert
* @author Artem Bilan
*
* @since 6.1
*/
public class UnZipTransformerParser extends AbstractZipTransformerParser {
@Override
protected String getTransformerClassName() {
return UnZipTransformer.class.getName();
}
@Override
protected void postProcessTransformer(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "expect-single-result");
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2015-2023 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.integration.zip.config.xml;
import org.springframework.integration.config.xml.AbstractIntegrationNamespaceHandler;
/**
* The namespace handler for the Zip namespace
*
* @author Gunnar Hillert
*
* @since 6.1
*
*/
public class ZipNamespaceHandler extends AbstractIntegrationNamespaceHandler {
@Override
public void init() {
this.registerBeanDefinitionParser("zip-transformer", new ZipTransformerParser());
this.registerBeanDefinitionParser("unzip-transformer", new UnZipTransformerParser());
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2015-2023 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.integration.zip.config.xml;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.integration.zip.transformer.ZipTransformer;
/**
* Parser for the 'zip-transformer' element.
*
* @author Gunnar Hillert
* @author Artem Bilan
*
* @since 6.1
*/
public class ZipTransformerParser extends AbstractZipTransformerParser {
@Override
protected String getTransformerClassName() {
return ZipTransformer.class.getName();
}
@Override
protected void postProcessTransformer(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "compression-level");
}
}

View File

@@ -0,0 +1,4 @@
/**
* Provides parser classes to provide Xml namespace support for the Zip components.
*/
package org.springframework.integration.zip.config.xml;

View File

@@ -0,0 +1,4 @@
/**
* Root package of the Zip Module.
*/
package org.springframework.integration.zip;

View File

@@ -0,0 +1,66 @@
/*
* Copyright 2015-2023 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.integration.zip.splitter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.commons.io.FilenameUtils;
import org.springframework.integration.file.FileHeaders;
import org.springframework.integration.splitter.AbstractMessageSplitter;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.zip.ZipHeaders;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.util.Assert;
/**
*
* @author Gunnar Hillert
* @author Andriy Kryvtsun
* @author Artem Bilan
*
* @since 6.1
*/
public class UnZipResultSplitter extends AbstractMessageSplitter {
@Override
@SuppressWarnings("unchecked")
protected Object splitMessage(Message<?> message) {
Assert.state(message.getPayload() instanceof Map,
"The UnZipResultSplitter supports only Map<String, Object> payload");
Map<String, Object> unzippedEntries = (Map<String, Object>) message.getPayload();
MessageHeaders headers = message.getHeaders();
List<MessageBuilder<Object>> messageBuilders = new ArrayList<>(unzippedEntries.size());
for (Map.Entry<String, Object> entry : unzippedEntries.entrySet()) {
String path = FilenameUtils.getPath(entry.getKey());
String filename = FilenameUtils.getName(entry.getKey());
MessageBuilder<Object> messageBuilder = MessageBuilder.withPayload(entry.getValue())
.setHeader(FileHeaders.FILENAME, filename)
.setHeader(ZipHeaders.ZIP_ENTRY_PATH, path)
.copyHeadersIfAbsent(headers);
messageBuilders.add(messageBuilder);
}
return messageBuilders;
}
}

View File

@@ -0,0 +1,4 @@
/**
* Classes to support Splitter pattern for Zip.
*/
package org.springframework.integration.zip.splitter;

View File

@@ -0,0 +1,117 @@
/*
* Copyright 2015-2023 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.integration.zip.transformer;
import java.io.File;
import java.nio.charset.Charset;
import org.springframework.integration.file.DefaultFileNameGenerator;
import org.springframework.integration.file.FileNameGenerator;
import org.springframework.integration.transformer.AbstractTransformer;
import org.springframework.messaging.Message;
import org.springframework.util.Assert;
/**
* Base class for transformers that provide Zip compression.
*
* @author Gunnar Hillert
* @author Artem Bilan
*
* @since 6.1
*/
public abstract class AbstractZipTransformer extends AbstractTransformer {
protected Charset charset = Charset.defaultCharset();
protected FileNameGenerator fileNameGenerator;
protected ZipResultType zipResultType = ZipResultType.FILE;
protected File workDirectory =
new File(System.getProperty("java.io.tmpdir") + File.separator + "ziptransformer");
protected boolean deleteFiles;
/**
* If the payload is an instance of {@link File}, this property specifies
* whether to delete the {@link File} after transformation.
* Default is <em>false</em>.
* @param deleteFiles Defaults to <em>false</em> if not set
*/
public void setDeleteFiles(boolean deleteFiles) {
this.deleteFiles = deleteFiles;
}
/**
* Set the work-directory. The work directory is used when the {@link ZipResultType}
* is set to {@link ZipResultType#FILE}. By default, this property is set to
* the System temporary directory containing a subdirectory "ziptransformer".
* @param workDirectory Must not be null and must not represent a file.
*/
public void setWorkDirectory(File workDirectory) {
Assert.notNull(workDirectory, "workDirectory must not be null.");
Assert.isTrue(workDirectory.isDirectory(), "The workDirectory specified must be a directory.");
this.workDirectory = workDirectory;
}
/**
* Define the format of the data returned after transformation. Available
* options are:
* <ul>
* <li>File</li>
* <li>Byte Array</li>
* </ul>
* Defaults to {@link ZipResultType#FILE}.
* @param zipResultType Must not be null
*/
public void setZipResultType(ZipResultType zipResultType) {
Assert.notNull(zipResultType, "The zipResultType must not be empty.");
this.zipResultType = zipResultType;
}
@Override
protected void onInit() {
super.onInit();
if (!this.workDirectory.exists()) {
logger.info(() -> "Creating work directory: " + this.workDirectory);
Assert.isTrue(this.workDirectory.mkdirs(), () -> "Can't create the 'workDirectory': " + this.workDirectory);
}
DefaultFileNameGenerator defaultFileNameGenerator = new DefaultFileNameGenerator();
defaultFileNameGenerator.setBeanFactory(getBeanFactory());
defaultFileNameGenerator.setConversionService(getConversionService());
this.fileNameGenerator = defaultFileNameGenerator;
}
/**
* @param message the message and its payload must not be null.
*/
@Override
protected Object doTransform(Message<?> message) {
return doZipTransform(message);
}
/**
* Subclasses must implement this method to provide the Zip transformation
* logic.
* @param message The message will never be null.
* @return The result of the Zip transformation.
*/
protected abstract Object doZipTransform(Message<?> message);
}

View File

@@ -0,0 +1,137 @@
/*
* Copyright 2015-2023 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.integration.zip.transformer;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collection;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
import org.apache.commons.io.IOUtils;
import org.zeroturnaround.zip.ZipEntrySource;
import org.zeroturnaround.zip.ZipException;
import org.springframework.core.log.LogAccessor;
/**
* Once the Spring Integration Zip support matures, we need to contribute the
* methods in this utility class back to the ZT Zip project.
*
* @author Gunnar Hillert
*
* @since 6.1
*/
public class SpringZipUtils {
private static final LogAccessor logger = new LogAccessor(SpringZipUtils.class);
public static byte[] pack(Collection<ZipEntrySource> entries, int compressionLevel) {
logger.debug(() -> "Creating byte array from: " + entries);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
pack(entries, outputStream, compressionLevel);
return outputStream.toByteArray();
}
public static void pack(Collection<ZipEntrySource> entries, File zip, int compressionLevel) {
logger.debug(() -> "Creating '" + zip + "' from " + entries + ".");
FileOutputStream outputStream;
try {
outputStream = new FileOutputStream(zip);
}
catch (FileNotFoundException e) {
throw new IllegalStateException(String.format("File '%s' not found.", zip.getAbsolutePath()), e);
}
pack(entries, outputStream, compressionLevel);
}
private static void pack(Collection<ZipEntrySource> entries, OutputStream outputStream, int compressionLevel) {
ZipOutputStream out = null;
final BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream);
try {
out = new ZipOutputStream(bufferedOutputStream);
out.setLevel(compressionLevel);
for (ZipEntrySource entry : entries) {
addEntry(entry, out);
}
}
catch (IOException e) {
throw rethrow(e);
}
finally {
IOUtils.closeQuietly(out);
}
}
private static void addEntry(ZipEntrySource entry, ZipOutputStream out) throws IOException {
out.putNextEntry(entry.getEntry());
InputStream in = entry.getInputStream();
if (in != null) {
try {
IOUtils.copy(in, out);
}
finally {
IOUtils.closeQuietly(in);
}
}
out.closeEntry();
}
private static ZipException rethrow(IOException e) {
throw new ZipException(e);
}
public static void copy(InputStream in, File file) throws IOException {
OutputStream out = new BufferedOutputStream(new FileOutputStream(file));
try {
IOUtils.copy(in, out);
}
finally {
IOUtils.closeQuietly(out);
}
}
public byte[] copy(InputStream in) throws IOException {
return IOUtils.toByteArray(in);
}
static boolean isValid(final File file) {
ZipFile zipfile = null;
try {
zipfile = new ZipFile(file);
return true;
}
catch (IOException e) {
return false;
}
finally {
IOUtils.closeQuietly(zipfile);
}
}
}

View File

@@ -0,0 +1,205 @@
/*
* Copyright 2015-2023 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.integration.zip.transformer;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.zip.ZipEntry;
import org.apache.commons.io.IOUtils;
import org.zeroturnaround.zip.ZipEntryCallback;
import org.zeroturnaround.zip.ZipException;
import org.zeroturnaround.zip.ZipUtil;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandlingException;
import org.springframework.messaging.MessagingException;
/**
* Transformer implementation that applies an UnZip transformation to the message
* payload.
*
* @author Gunnar Hillert
* @author Artem Bilan
* @author Ingo Dueppe
*
* @since 6.1
*/
public class UnZipTransformer extends AbstractZipTransformer {
private volatile boolean expectSingleResult = false;
/**
*
* This parameter indicates that only one result object shall be returned as
* a result from the executed Unzip operation. If set to <code>true</code> and
* more than 1 element is returned, then that
* 1 element is extracted and returned as payload.
* If the result map contains more than 1 element and
* {@link #expectSingleResult} is <code>true</code>, then a
* {@link MessagingException} is thrown.
* If set to <code>false</code>, the complete result list is returned as the
* payload. This is the {@code default}.
* @param expectSingleResult If not set explicitly, will default to false
*/
public void setExpectSingleResult(boolean expectSingleResult) {
this.expectSingleResult = expectSingleResult;
}
@Override
protected Object doZipTransform(final Message<?> message) {
try {
Object payload = message.getPayload();
Object unzippedData;
InputStream inputStream = null;
try {
if (payload instanceof final File filePayload) {
if (filePayload.isDirectory()) {
throw new UnsupportedOperationException("Cannot unzip a directory: " +
filePayload.getAbsolutePath());
}
if (!SpringZipUtils.isValid(filePayload)) {
throw new IllegalStateException("Not a zip file: " + filePayload.getAbsolutePath());
}
inputStream = new FileInputStream(filePayload);
}
else if (payload instanceof InputStream) {
inputStream = (InputStream) payload;
}
else if (payload instanceof byte[]) {
inputStream = new ByteArrayInputStream((byte[]) payload);
}
else {
throw new IllegalArgumentException("Unsupported payload type '" + payload.getClass().getSimpleName()
+ "'. The only supported payload types are java.io.File, byte[] and java.io.InputStream");
}
final SortedMap<String, Object> uncompressedData = new TreeMap<>();
ZipUtil.iterate(inputStream, new ZipEntryCallback() {
@Override
public void process(InputStream zipEntryInputStream, ZipEntry zipEntry) throws IOException {
final String zipEntryName = zipEntry.getName();
final long zipEntryTime = zipEntry.getTime();
final long zipEntryCompressedSize = zipEntry.getCompressedSize();
final String type = zipEntry.isDirectory() ? "directory" : "file";
logger.info(() -> String.format("Unpacking Zip Entry - Name: '%s',Time: '%s', " +
"Compressed Size: '%s', Type: '%s'",
zipEntryName, zipEntryTime, zipEntryCompressedSize, type));
if (ZipResultType.FILE.equals(zipResultType)) {
final File destinationFile = checkPath(message, zipEntryName);
if (zipEntry.isDirectory()) {
destinationFile.mkdirs(); //NOSONAR false positive
}
else {
mkDirOfAncestorDirectories(destinationFile);
SpringZipUtils.copy(zipEntryInputStream, destinationFile);
uncompressedData.put(zipEntryName, destinationFile);
}
}
else if (ZipResultType.BYTE_ARRAY.equals(zipResultType)) {
if (!zipEntry.isDirectory()) {
checkPath(message, zipEntryName);
byte[] data = IOUtils.toByteArray(zipEntryInputStream);
uncompressedData.put(zipEntryName, data);
}
}
else {
throw new IllegalStateException("Unsupported zipResultType: " + zipResultType);
}
}
public File checkPath(final Message<?> message, final String zipEntryName) throws IOException {
final File tempDir = new File(workDirectory, message.getHeaders().getId().toString());
tempDir.mkdirs(); //NOSONAR false positive
final File destinationFile = new File(tempDir, zipEntryName);
/* If we see the relative traversal string of ".." we need to make sure
* that the outputdir + name doesn't leave the outputdir.
*/
if (!destinationFile.getCanonicalPath()
.startsWith(tempDir.getCanonicalPath() + File.separator)) {
throw new ZipException("The file " + zipEntryName +
" is trying to leave the target output directory of " + workDirectory);
}
return destinationFile;
}
});
if (uncompressedData.isEmpty()) {
logger.warn(() -> "No data unzipped from payload with message Id " + message.getHeaders().getId());
unzippedData = null;
}
else {
if (this.expectSingleResult) {
if (uncompressedData.size() == 1) {
unzippedData = uncompressedData.values().iterator().next();
}
else {
throw new MessagingException(message,
String.format("The UnZip operation extracted %s "
+ "result objects but expectSingleResult was 'true'.", uncompressedData
.size()));
}
}
else {
unzippedData = uncompressedData;
}
}
IOUtils.closeQuietly(inputStream);
if (payload instanceof final File filePayload && this.deleteFiles) {
if (!filePayload.delete() && logger.isWarnEnabled()) {
logger.warn(() -> "failed to delete File '" + filePayload + "'");
}
}
}
finally {
IOUtils.closeQuietly(inputStream);
}
return unzippedData;
}
catch (Exception e) {
throw new MessageHandlingException(message, "Failed to apply Zip transformation.", e);
}
}
private static void mkDirOfAncestorDirectories(File destinationFile) {
File parentDirectory = destinationFile.getParentFile();
if (parentDirectory != null) {
parentDirectory.mkdirs();
}
}
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2015-2023 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.integration.zip.transformer;
/**
* @author Gunnar Hillert
*
* @since 6.1
*/
public enum ZipResultType {
FILE, BYTE_ARRAY
}

View File

@@ -0,0 +1,223 @@
/*
* Copyright 2015-2023 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.integration.zip.transformer;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.zip.Deflater;
import org.apache.commons.io.FilenameUtils;
import org.zeroturnaround.zip.ByteSource;
import org.zeroturnaround.zip.FileSource;
import org.zeroturnaround.zip.ZipEntrySource;
import org.springframework.integration.file.FileHeaders;
import org.springframework.integration.transformer.Transformer;
import org.springframework.integration.zip.ZipHeaders;
import org.springframework.messaging.Message;
import org.springframework.util.Assert;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.StringUtils;
/**
* {@link Transformer} implementation that applies a Zip transformation to the
* message payload. Keep in mind that Zip entry timestamps are recorded only to
* two 2 second precision:
* <p>
* See also: <a href="http://mindprod.com/jgloss/zip.html"/>
* <p>
* If you want to generate Zip files larger than {@code 4GB}, you must use Java 7:
* <p>
* See also: <a href="https://blogs.oracle.com/xuemingshen/entry/zip64_support_for_4g_zipfile"/>
*
* @author Gunnar Hillert
* @author Artem Bilan
*
* @since 6.1
*/
public class ZipTransformer extends AbstractZipTransformer {
private static final String ZIP_EXTENSION = ".zip";
private volatile int compressionLevel = Deflater.DEFAULT_COMPRESSION;
private volatile boolean useFileAttributes = true;
/**
* Set the compression level. Default is {@link Deflater#DEFAULT_COMPRESSION}.
* @param compressionLevel Must be an integer value from 0-9.
*/
public void setCompressionLevel(int compressionLevel) {
Assert.isTrue(compressionLevel >= 0 && compressionLevel <= 9, "Acceptable levels are 0-9");
this.compressionLevel = compressionLevel;
}
/**
* Specify whether the name of the file shall be used for the zip entry.
* @param useFileAttributes Defaults to true if not set explicitly
*/
public void setUseFileAttributes(boolean useFileAttributes) {
this.useFileAttributes = useFileAttributes;
}
/**
* The payload may encompass the following types:
* <ul>
* <li>{@link File}
*...<li>{@link String}
*...<li>byte[]
*...<li>{@link Iterable}
* </ul>
* When providing an {@link Iterable}, nested Iterables are not supported. However,
* payloads can be any of the other supported types.
*/
@Override
protected Object doZipTransform(Message<?> message) {
final Object payload = message.getPayload();
final Object zippedData;
final String baseFileName = this.fileNameGenerator.generateFileName(message);
final String zipEntryName;
final String zipFileName;
if (message.getHeaders().containsKey(ZipHeaders.ZIP_ENTRY_FILE_NAME)) {
zipEntryName = (String) message.getHeaders().get(ZipHeaders.ZIP_ENTRY_FILE_NAME);
}
else {
zipEntryName = baseFileName;
}
if (message.getHeaders().containsKey(FileHeaders.FILENAME)) {
zipFileName = (String) message.getHeaders().get(FileHeaders.FILENAME);
}
else {
zipFileName = baseFileName + ZIP_EXTENSION;
}
final Date lastModifiedDate;
if (message.getHeaders().containsKey(ZipHeaders.ZIP_ENTRY_LAST_MODIFIED_DATE)) {
lastModifiedDate = (Date) message.getHeaders().get(ZipHeaders.ZIP_ENTRY_LAST_MODIFIED_DATE);
}
else {
lastModifiedDate = new Date();
}
java.util.List<ZipEntrySource> entries = new ArrayList<>();
if (payload instanceof Iterable<?>) {
int counter = 1;
String baseName = FilenameUtils.getBaseName(zipEntryName);
String fileExtension = FilenameUtils.getExtension(zipEntryName);
if (StringUtils.hasText(fileExtension)) {
fileExtension = FilenameUtils.EXTENSION_SEPARATOR_STR + fileExtension;
}
for (Object item : (Iterable<?>) payload) {
final ZipEntrySource zipEntrySource = createZipEntrySource(item, lastModifiedDate, baseName + "_"
+ counter + fileExtension, this.useFileAttributes);
logger.debug(() -> "ZipEntrySource path: '" + zipEntrySource.getPath() + "'");
entries.add(zipEntrySource);
counter++;
}
}
else {
final ZipEntrySource zipEntrySource =
createZipEntrySource(payload, lastModifiedDate, zipEntryName, this.useFileAttributes);
entries.add(zipEntrySource);
}
final byte[] zippedBytes = SpringZipUtils.pack(entries, this.compressionLevel);
if (ZipResultType.FILE.equals(this.zipResultType)) {
final File zippedFile = new File(this.workDirectory, zipFileName);
try {
FileCopyUtils.copy(zippedBytes, zippedFile);
}
catch (IOException ex) {
throw new UncheckedIOException(ex);
}
zippedData = zippedFile;
}
else if (ZipResultType.BYTE_ARRAY.equals(this.zipResultType)) {
zippedData = zippedBytes;
}
else {
throw new IllegalStateException("Unsupported zipResultType " + this.zipResultType);
}
if (this.deleteFiles) {
if (payload instanceof Iterable<?>) {
for (Object item : (Iterable<?>) payload) {
deleteFile(item);
}
}
else {
deleteFile(payload);
}
}
return getMessageBuilderFactory()
.withPayload(zippedData)
.copyHeaders(message.getHeaders())
.setHeader(FileHeaders.FILENAME, zipFileName)
.build();
}
private void deleteFile(Object fileToDelete) {
if (fileToDelete instanceof File && !((File) fileToDelete).delete()) {
logger.warn(() -> "Failed to delete File '" + fileToDelete + "'");
}
}
private ZipEntrySource createZipEntrySource(Object item,
Date lastModifiedDate, String zipEntryName, boolean useFileAttributes) {
if (item instanceof final File filePayload) {
String fileName = useFileAttributes ? filePayload.getName() : zipEntryName;
if (((File) item).isDirectory()) {
throw new UnsupportedOperationException("Zipping of directories is not supported.");
}
return new FileSource(fileName, filePayload);
}
else if (item instanceof byte[] || item instanceof String) {
byte[] bytesToCompress;
if (item instanceof String) {
bytesToCompress = ((String) item).getBytes(this.charset);
}
else {
bytesToCompress = (byte[]) item;
}
return new ByteSource(zipEntryName, bytesToCompress, lastModifiedDate.getTime());
}
else {
throw new IllegalArgumentException("Unsupported payload type. The only supported payloads are " +
"java.io.File, java.lang.String, and byte[]");
}
}
}

View File

@@ -0,0 +1,4 @@
/**
* Classes to support Transformer pattern for Zip.
*/
package org.springframework.integration.zip.transformer;

View File

@@ -0,0 +1 @@
http\://www.springframework.org/schema/integration/zip=org.springframework.integration.zip.config.xml.ZipNamespaceHandler

View File

@@ -0,0 +1,4 @@
http\://www.springframework.org/schema/integration/zip/spring-integration-zip-1.0.xsd=org/springframework/integration/zip/config/spring-integration-zip.xsd
http\://www.springframework.org/schema/integration/zip/spring-integration-zip.xsd=org/springframework/integration/zip/config/spring-integration-zip.xsd
https\://www.springframework.org/schema/integration/zip/spring-integration-zip-1.0.xsd=org/springframework/integration/zip/config/spring-integration-zip.xsd
https\://www.springframework.org/schema/integration/zip/spring-integration-zip.xsd=org/springframework/integration/zip/config/spring-integration-zip.xsd

View File

@@ -0,0 +1,4 @@
# Tooling related information for the integration Zip namespace
http\://www.springframework.org/schema/integration/zip@name=integration Zip Namespace
http\://www.springframework.org/schema/integration/zip@prefix=int-zip
http\://www.springframework.org/schema/integration/zip@icon=org/springframework/integration/zip/config/spring-integration-zip.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 539 B

View File

@@ -0,0 +1,126 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.springframework.org/schema/integration/zip"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:integration="http://www.springframework.org/schema/integration"
targetNamespace="http://www.springframework.org/schema/integration/zip"
elementFormDefault="qualified">
<xsd:import namespace="http://www.springframework.org/schema/beans" />
<xsd:import namespace="http://www.springframework.org/schema/tool" />
<xsd:import namespace="http://www.springframework.org/schema/integration"
schemaLocation="https://www.springframework.org/schema/integration/spring-integration.xsd" />
<xsd:annotation>
<xsd:documentation>
Defines the configuration elements for the Spring Integration
Zip Adapter.
</xsd:documentation>
</xsd:annotation>
<xsd:element name="zip-transformer">
<xsd:annotation>
<xsd:documentation>
Creates a Transformer that compresses message
payloads using Zip compressions. The following payload types are
supported:
- java.io.File
- byte[]
- String
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="transformerType">
<xsd:attribute name="compression-level" type="xsd:integer"
use="optional">
<xsd:annotation>
<xsd:documentation>
Sets the compression level. Default is
java.util.zip.Deflater.DEFAULT_COMPRESSION
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:element name="unzip-transformer">
<xsd:annotation>
<xsd:documentation>
Creates a Transformer that decompresses message
payloads using Zip compressions. The following payload types are
supported:
- java.io.File
- byte[]
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="transformerType">
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:complexType name="transformerType">
<xsd:sequence>
<xsd:element ref="integration:poller" minOccurs="0" maxOccurs="1"/>
<xsd:element name="request-handler-advice-chain" type="integration:handlerAdviceChainType" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="id" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Identifies the underlying Spring bean definition (EventDrivenConsumer)
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attributeGroup ref="integration:inputOutputChannelGroup"/>
<xsd:attribute name="delete-files" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
If the payload is an instance of {@link File}, this
attribute specifies whether to delete the {@link File} after
transformation. The default is 'false'.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="expect-single-result" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
If set to 'true', this property indicates
that only one result object shall be returned as a result
from the executed Unzip operation. Defaults to 'false'.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="result-type">
<xsd:annotation>
<xsd:documentation>
Defines the format of the data returned after
transformation. Available options are:
- File
- Byte Array
Depending on the used input format, not all
options may be applicable.
</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:union memberTypes="resultType xsd:string" />
</xsd:simpleType>
</xsd:attribute>
</xsd:complexType>
<xsd:simpleType name="resultType">
<xsd:restriction base="xsd:token">
<xsd:enumeration value="BYTE_ARRAY" />
<xsd:enumeration value="FILE" />
</xsd:restriction>
</xsd:simpleType>
</xsd:schema>

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-zip="http://www.springframework.org/schema/integration/zip"
xmlns:int-file="http://www.springframework.org/schema/integration/file"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/integration/file https://www.springframework.org/schema/integration/file/spring-integration-file.xsd
http://www.springframework.org/schema/integration/zip https://www.springframework.org/schema/integration/zip/spring-integration-zip.xsd
http://www.springframework.org/schema/integration https://www.springframework.org/schema/integration/spring-integration.xsd">
<context:property-placeholder/>
<int:channel id="input"/>
<int:chain input-channel="input" output-channel="out">
<int-zip:unzip-transformer result-type="BYTE_ARRAY"/>
<int:splitter>
<bean class="org.springframework.integration.zip.splitter.UnZipResultSplitter"/>
</int:splitter>
</int:chain>
<int:channel id="out">
<int:interceptors>
<int:wire-tap channel="logger"/>
</int:interceptors>
</int:channel>
<int:logging-channel-adapter id="logger" log-full-message="true" level="INFO"/>
<int-file:outbound-channel-adapter id="write-file" channel="out" directory-expression="'${workDir}/' + headers.zip_entryPath"/>
</beans>

View File

@@ -0,0 +1,160 @@
/*
* Copyright 2015-2023 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.integration.zip;
import java.io.File;
import java.io.InputStream;
import org.apache.commons.io.IOUtils;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.zeroturnaround.zip.ZipException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.transformer.MessageTransformationException;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
*
* @author Gunnar Hillert
* @author Artem Bilan
*
* @since 6.1
*/
@SpringJUnitConfig
@DirtiesContext
public class UnZip2FileTests {
@Autowired
private ApplicationContext context;
@Autowired
private MessageChannel input;
@TempDir
public static File workDir;
@BeforeAll
public static void setup() {
System.setProperty("workDir", workDir.getAbsolutePath());
}
@BeforeEach
public void cleanUp() {
cleanupDirectory(workDir);
}
@Test
public void unZipWithOneEntry() throws Exception {
final Resource resource = this.context.getResource("classpath:testzipdata/single.zip");
final InputStream is = resource.getInputStream();
byte[] zipdata = IOUtils.toByteArray(is);
is.close();
final Message<byte[]> message = MessageBuilder.withPayload(zipdata).build();
input.send(message);
assertThat(workDir.list()).hasSize(1);
File fileInWorkDir = workDir.listFiles()[0];
assertThat(fileInWorkDir).isFile();
assertThat(fileInWorkDir).hasName("single.txt");
}
@Test
public void unZipWithMultipleEntries() throws Exception {
final Resource resource = this.context.getResource("classpath:testzipdata/countries.zip");
final InputStream is = resource.getInputStream();
byte[] zipdata = IOUtils.toByteArray(is);
is.close();
final Message<byte[]> message = MessageBuilder.withPayload(zipdata).build();
input.send(message);
assertThat(workDir.list()).hasSize(4);
File[] files = workDir.listFiles();
boolean continents = false;
boolean de = false;
boolean fr = false;
boolean pl = false;
for (File file : files) {
if (file.getName().equals("continents")) {
continents = true;
assertThat(file).isDirectory();
assertThat(file.list()).hasSize(2);
}
if (file.getName().equals("de.txt")) {
de = true;
assertThat(file).isFile();
}
if (file.getName().equals("fr.txt")) {
fr = true;
assertThat(file).isFile();
}
if (file.getName().equals("pl.txt")) {
pl = true;
assertThat(file).isFile();
}
}
assertThat(continents).isTrue();
assertThat(de).isTrue();
assertThat(fr).isTrue();
assertThat(pl).isTrue();
}
@Test
public void unZipTraversal() throws Exception {
final Resource resource = this.context.getResource("classpath:testzipdata/zip-malicious-traversal.zip");
final InputStream is = resource.getInputStream();
byte[] zipdata = IOUtils.toByteArray(is);
final Message<byte[]> message = MessageBuilder.withPayload(zipdata).build();
assertThatExceptionOfType(MessageTransformationException.class)
.isThrownBy(() -> input.send(message))
.withRootCauseInstanceOf(ZipException.class)
.withStackTraceContaining("is trying to leave the target output directory");
}
private static void cleanupDirectory(File dir) {
for (File file: dir.listFiles()) {
if (file.isDirectory()) {
cleanupDirectory(file);
}
file.delete();
}
}
}

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-zip="http://www.springframework.org/schema/integration/zip"
xmlns:int-file="http://www.springframework.org/schema/integration/file"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/integration/file https://www.springframework.org/schema/integration/file/spring-integration-file.xsd
http://www.springframework.org/schema/integration/zip https://www.springframework.org/schema/integration/zip/spring-integration-zip.xsd
http://www.springframework.org/schema/integration https://www.springframework.org/schema/integration/spring-integration.xsd">
<context:property-placeholder/>
<int:channel id="input"/>
<int-zip:zip-transformer input-channel="input" output-channel="write-file" result-type="BYTE_ARRAY">
<int-zip:request-handler-advice-chain>
<int:retry-advice/>
</int-zip:request-handler-advice-chain>
</int-zip:zip-transformer>
<int-file:outbound-channel-adapter id="write-file" directory="${workDir}" auto-create-directory="true"/>
</beans>

View File

@@ -0,0 +1,158 @@
/*
* Copyright 2015-2023 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.integration.zip;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.io.FileUtils;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.file.FileHeaders;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import static org.assertj.core.api.Assertions.assertThat;
/**
*
* @author Gunnar Hillert
* @author Artem Bilan
*
* @since 6.1
*/
@SpringJUnitConfig
@DirtiesContext
public class Zip2FileTests {
@Autowired
private MessageChannel input;
@TempDir
public static File workDir;
@BeforeAll
public static void setup() {
System.setProperty("workDir", workDir.getAbsolutePath());
}
@BeforeEach
public void cleanUp() {
for (File file : workDir.listFiles()) {
file.delete();
}
}
@Test
public void zipStringWithDefaultFileName() {
final Message<String> message = MessageBuilder.withPayload("Zip me up.").build();
input.send(message);
assertThat(workDir.list()).hasSize(1);
File fileInWorkDir = workDir.listFiles()[0];
assertThat(fileInWorkDir.isFile()).isTrue();
assertThat(fileInWorkDir.getName()).contains(message.getHeaders().getId().toString());
assertThat(fileInWorkDir.getName()).endsWith(".zip");
}
@Test
public void zipStringWithExplicitFileName() {
input.send(MessageBuilder.withPayload("Zip me up.")
.setHeader(FileHeaders.FILENAME, "zipString.zip")
.build());
assertThat(workDir.list()).hasSize(1);
assertThat(workDir.list()[0]).isEqualTo("zipString.zip");
}
@Test
public void zipBytesWithExplicitFileName() {
input.send(MessageBuilder.withPayload("Zip me up.".getBytes())
.setHeader(FileHeaders.FILENAME, "zipString.zip")
.build());
assertThat(workDir.list()).hasSize(1);
assertThat(workDir.list()[0]).isEqualTo("zipString.zip");
}
@Test
public void zipFile() throws IOException {
File fileToCompress = File.createTempFile("test1", "tmp");
FileUtils.writeStringToFile(fileToCompress, "hello world", Charset.defaultCharset());
input.send(MessageBuilder.withPayload(fileToCompress).build());
assertThat(workDir.list()).hasSize(1);
assertThat(workDir.list()[0]).isEqualTo(fileToCompress.getName() + ".zip");
}
@Test
public void zipIterableWithMultipleStrings() {
String stringToCompress1 = "String1";
String stringToCompress2 = "String2";
String stringToCompress3 = "String3";
String stringToCompress4 = "String4";
List<String> stringsToCompress = new ArrayList<>(4);
stringsToCompress.add(stringToCompress1);
stringsToCompress.add(stringToCompress2);
stringsToCompress.add(stringToCompress3);
stringsToCompress.add(stringToCompress4);
input.send(MessageBuilder.withPayload(stringsToCompress)
.setHeader(FileHeaders.FILENAME, "zipWith4Strings.zip")
.build());
assertThat(workDir.list()).hasSize(1);
assertThat(workDir.list()[0]).isEqualTo("zipWith4Strings.zip");
}
@Test
public void zipIterableWithDifferentTypes() throws IOException {
String stringToCompress = "String1";
byte[] bytesToCompress = "String2".getBytes();
File fileToCompress = File.createTempFile("test2", "tmp");
FileUtils.writeStringToFile(fileToCompress, "hello world", Charset.defaultCharset());
final List<Object> objectsToCompress = new ArrayList<>(3);
objectsToCompress.add(stringToCompress);
objectsToCompress.add(bytesToCompress);
objectsToCompress.add(fileToCompress);
input.send(MessageBuilder.withPayload(objectsToCompress)
.setHeader(FileHeaders.FILENAME, "objects-to-compress.zip")
.build());
assertThat(workDir.list()).hasSize(1);
assertThat(workDir.list()[0]).isEqualTo("objects-to-compress.zip");
}
}

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-zip="http://www.springframework.org/schema/integration/zip"
xsi:schemaLocation="http://www.springframework.org/schema/integration https://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration/zip https://www.springframework.org/schema/integration/zip/spring-integration-zip.xsd">
<int:channel id="input"/>
<int:channel id="output"/>
<int-zip:unzip-transformer id="unzipTransformer"
delete-files="true" input-channel="input" output-channel="output"
result-type="FILE" expect-single-result="true"/>
<int-zip:unzip-transformer id="unzipTransformerWithDefaults"
input-channel="input" output-channel="output" />
</beans>

View File

@@ -0,0 +1,129 @@
/*
* Copyright 2015-2023 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.integration.zip.config.xml;
import java.io.File;
import java.nio.charset.Charset;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.integration.channel.AbstractMessageChannel;
import org.springframework.integration.endpoint.EventDrivenConsumer;
import org.springframework.integration.file.DefaultFileNameGenerator;
import org.springframework.integration.file.FileNameGenerator;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.integration.transformer.MessageTransformingHandler;
import org.springframework.integration.zip.transformer.UnZipTransformer;
import org.springframework.integration.zip.transformer.ZipResultType;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import static org.assertj.core.api.Assertions.assertThat;
/**
*
* @author Gunnar Hillert
* @author Artem Bilan
*
* @since 6.1
*/
@SpringJUnitConfig
@DirtiesContext
public class UnZipTransformerParserTests {
@Autowired
private ConfigurableApplicationContext context;
@Test
public void testUnZipTransformerParserWithDefaults() {
EventDrivenConsumer consumer = this.context.getBean("unzipTransformerWithDefaults", EventDrivenConsumer.class);
final AbstractMessageChannel inputChannel = TestUtils.getPropertyValue(consumer, "inputChannel", AbstractMessageChannel.class);
assertThat(inputChannel.getComponentName()).isEqualTo("input");
final MessageTransformingHandler handler = TestUtils.getPropertyValue(consumer, "handler", MessageTransformingHandler.class);
assertThat(TestUtils.getPropertyValue(handler, "outputChannelName")).isEqualTo("output");
final UnZipTransformer unZipTransformer = TestUtils.getPropertyValue(handler, "transformer", UnZipTransformer.class);
final Charset charset = TestUtils.getPropertyValue(unZipTransformer, "charset", Charset.class);
final FileNameGenerator fileNameGenerator = TestUtils.getPropertyValue(unZipTransformer, "fileNameGenerator", FileNameGenerator.class);
final ZipResultType zipResultType = TestUtils.getPropertyValue(unZipTransformer, "zipResultType", ZipResultType.class);
final File workDirectory = TestUtils.getPropertyValue(unZipTransformer, "workDirectory", File.class);
final Boolean deleteFiles = TestUtils.getPropertyValue(unZipTransformer, "deleteFiles", Boolean.class);
final Boolean expectSingleResult = TestUtils.getPropertyValue(unZipTransformer, "expectSingleResult", Boolean.class);
assertThat(charset).isNotNull();
assertThat(fileNameGenerator).isNotNull();
assertThat(zipResultType).isNotNull();
assertThat(workDirectory).isNotNull();
assertThat(deleteFiles).isNotNull();
assertThat(expectSingleResult).isNotNull();
assertThat(charset).isEqualTo(Charset.defaultCharset());
assertThat(fileNameGenerator).isInstanceOf(DefaultFileNameGenerator.class);
assertThat(zipResultType).isEqualTo(ZipResultType.FILE);
assertThat(workDirectory)
.isEqualTo(new File(System.getProperty("java.io.tmpdir") + File.separator + "ziptransformer"));
assertThat(workDirectory.exists()).isTrue();
assertThat(workDirectory.isDirectory()).isTrue();
assertThat(deleteFiles).isFalse();
assertThat(expectSingleResult).isFalse();
}
@Test
public void testUnZipTransformerParserWithExplicitSettings() {
EventDrivenConsumer consumer = this.context.getBean("unzipTransformer", EventDrivenConsumer.class);
final AbstractMessageChannel inputChannel = TestUtils.getPropertyValue(consumer, "inputChannel", AbstractMessageChannel.class);
assertThat(inputChannel.getComponentName()).isEqualTo("input");
final MessageTransformingHandler handler = TestUtils.getPropertyValue(consumer, "handler", MessageTransformingHandler.class);
assertThat(TestUtils.getPropertyValue(handler, "outputChannelName")).isEqualTo("output");
final UnZipTransformer unZipTransformer = TestUtils.getPropertyValue(handler, "transformer", UnZipTransformer.class);
final Charset charset = TestUtils.getPropertyValue(unZipTransformer, "charset", Charset.class);
final FileNameGenerator fileNameGenerator = TestUtils.getPropertyValue(unZipTransformer, "fileNameGenerator", FileNameGenerator.class);
final ZipResultType zipResultType = TestUtils.getPropertyValue(unZipTransformer, "zipResultType", ZipResultType.class);
final File workDirectory = TestUtils.getPropertyValue(unZipTransformer, "workDirectory", File.class);
final Boolean deleteFiles = TestUtils.getPropertyValue(unZipTransformer, "deleteFiles", Boolean.class);
final Boolean expectSingleResult = TestUtils.getPropertyValue(unZipTransformer, "expectSingleResult", Boolean.class);
assertThat(charset).isNotNull();
assertThat(fileNameGenerator).isNotNull();
assertThat(zipResultType).isNotNull();
assertThat(workDirectory).isNotNull();
assertThat(deleteFiles).isNotNull();
assertThat(expectSingleResult).isNotNull();
assertThat(charset).isEqualTo(Charset.defaultCharset());
assertThat(fileNameGenerator).isInstanceOf(DefaultFileNameGenerator.class);
assertThat(zipResultType).isEqualTo(ZipResultType.FILE);
assertThat(workDirectory)
.isEqualTo(new File(System.getProperty("java.io.tmpdir") + File.separator + "ziptransformer"));
assertThat(workDirectory.exists()).isTrue();
assertThat(workDirectory.isDirectory()).isTrue();
assertThat(deleteFiles).isTrue();
assertThat(expectSingleResult).isTrue();
}
}

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-zip="http://www.springframework.org/schema/integration/zip"
xsi:schemaLocation="http://www.springframework.org/schema/integration https://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration/zip https://www.springframework.org/schema/integration/zip/spring-integration-zip.xsd">
<int:channel id="input"/>
<int:channel id="output"/>
<int-zip:zip-transformer id="zipTransformer" compression-level="2"
delete-files="true" input-channel="input" output-channel="output"
result-type="BYTE_ARRAY"/>
<int-zip:zip-transformer id="zipTransformerWithDefaults"
input-channel="input" output-channel="output" />
</beans>

View File

@@ -0,0 +1,143 @@
/*
* Copyright 2015-2023 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.integration.zip.config.xml;
import java.io.File;
import java.nio.charset.Charset;
import java.util.zip.Deflater;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.integration.channel.AbstractMessageChannel;
import org.springframework.integration.endpoint.EventDrivenConsumer;
import org.springframework.integration.file.DefaultFileNameGenerator;
import org.springframework.integration.file.FileNameGenerator;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.integration.transformer.MessageTransformingHandler;
import org.springframework.integration.zip.transformer.ZipResultType;
import org.springframework.integration.zip.transformer.ZipTransformer;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* @author Gunnar Hillert
* @author Artem Bilan
*
* @since 6.1
*/
@SpringJUnitConfig
@DirtiesContext
public class ZipTransformerParserTests {
@Autowired
private ConfigurableApplicationContext context;
@Test
public void testZipTransformerParserWithDefaults() {
EventDrivenConsumer consumer = this.context.getBean("zipTransformerWithDefaults", EventDrivenConsumer.class);
final AbstractMessageChannel inputChannel = TestUtils.getPropertyValue(consumer, "inputChannel", AbstractMessageChannel.class);
assertThat(inputChannel.getComponentName()).isEqualTo("input");
final MessageTransformingHandler handler = TestUtils.getPropertyValue(consumer, "handler", MessageTransformingHandler.class);
assertThat(TestUtils.getPropertyValue(handler, "outputChannelName")).isEqualTo("output");
final ZipTransformer zipTransformer = TestUtils.getPropertyValue(handler, "transformer", ZipTransformer.class);
final Charset charset = TestUtils.getPropertyValue(zipTransformer, "charset", Charset.class);
final FileNameGenerator fileNameGenerator = TestUtils.getPropertyValue(zipTransformer, "fileNameGenerator", FileNameGenerator.class);
final ZipResultType zipResultType = TestUtils.getPropertyValue(zipTransformer, "zipResultType", ZipResultType.class);
final File workDirectory = TestUtils.getPropertyValue(zipTransformer, "workDirectory", File.class);
final Integer compressionLevel = TestUtils.getPropertyValue(zipTransformer, "compressionLevel", Integer.class);
final Boolean deleteFiles = TestUtils.getPropertyValue(zipTransformer, "deleteFiles", Boolean.class);
assertThat(charset).isNotNull();
assertThat(fileNameGenerator).isNotNull();
assertThat(zipResultType).isNotNull();
assertThat(workDirectory).isNotNull();
assertThat(deleteFiles).isNotNull();
assertThat(compressionLevel).isNotNull();
assertThat(charset).isEqualTo(Charset.defaultCharset());
assertThat(fileNameGenerator).isInstanceOf(DefaultFileNameGenerator.class);
assertThat(zipResultType).isEqualTo(ZipResultType.FILE);
assertThat(workDirectory)
.isEqualTo(new File(System.getProperty("java.io.tmpdir") + File.separator + "ziptransformer"));
assertThat(workDirectory.exists()).isTrue();
assertThat(workDirectory.isDirectory()).isTrue();
assertThat(deleteFiles).isFalse();
assertThat(compressionLevel).isEqualTo(Deflater.DEFAULT_COMPRESSION);
}
@Test
public void testZipTransformerParserWithExplicitSettings() {
EventDrivenConsumer consumer = this.context.getBean("zipTransformer", EventDrivenConsumer.class);
final AbstractMessageChannel inputChannel = TestUtils.getPropertyValue(consumer, "inputChannel", AbstractMessageChannel.class);
assertThat(inputChannel.getComponentName()).isEqualTo("input");
final MessageTransformingHandler handler = TestUtils.getPropertyValue(consumer, "handler", MessageTransformingHandler.class);
assertThat(TestUtils.getPropertyValue(handler, "outputChannelName")).isEqualTo("output");
final ZipTransformer zipTransformer = TestUtils.getPropertyValue(handler, "transformer", ZipTransformer.class);
final Charset charset = TestUtils.getPropertyValue(zipTransformer, "charset", Charset.class);
final FileNameGenerator fileNameGenerator = TestUtils.getPropertyValue(zipTransformer, "fileNameGenerator", FileNameGenerator.class);
final ZipResultType zipResultType = TestUtils.getPropertyValue(zipTransformer, "zipResultType", ZipResultType.class);
final File workDirectory = TestUtils.getPropertyValue(zipTransformer, "workDirectory", File.class);
final Integer compressionLevel = TestUtils.getPropertyValue(zipTransformer, "compressionLevel", Integer.class);
final Boolean deleteFiles = TestUtils.getPropertyValue(zipTransformer, "deleteFiles", Boolean.class);
assertThat(charset).isNotNull();
assertThat(fileNameGenerator).isNotNull();
assertThat(zipResultType).isNotNull();
assertThat(workDirectory).isNotNull();
assertThat(deleteFiles).isNotNull();
assertThat(compressionLevel).isNotNull();
assertThat(charset).isEqualTo(Charset.defaultCharset());
assertThat(fileNameGenerator).isInstanceOf(DefaultFileNameGenerator.class);
assertThat(zipResultType).isEqualTo(ZipResultType.BYTE_ARRAY);
assertThat(workDirectory)
.isEqualTo(new File(System.getProperty("java.io.tmpdir") + File.separator + "ziptransformer"));
assertThat(workDirectory.exists()).isTrue();
assertThat(workDirectory.isDirectory()).isTrue();
assertThat(compressionLevel).isEqualTo(2);
assertThat(deleteFiles).isTrue();
}
@Test
public void testZipTransformerParserWithIncorrectResultType() {
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(() ->
new ClassPathXmlApplicationContext("ZipTransformerParserTestsWithIncorrectResultType.xml",
getClass()))
.withMessageContaining("Failed to convert property value of type 'java.lang.String' " +
"to required type 'org.springframework.integration.zip.transformer.ZipResultType'");
}
}

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-zip="http://www.springframework.org/schema/integration/zip"
xsi:schemaLocation="http://www.springframework.org/schema/integration https://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration/zip https://www.springframework.org/schema/integration/zip/spring-integration-zip.xsd">
<int:channel id="input"/>
<int:channel id="output"/>
<int-zip:zip-transformer id="zipTransformer" compression-level="2"
delete-files="true" input-channel="input" output-channel="output"
result-type="INCORRECT"/>
</beans>

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration
https://www.springframework.org/schema/integration/spring-integration.xsd">
<int:channel id="input"/>
<int:splitter input-channel="input" output-channel="output">
<bean class="org.springframework.integration.zip.splitter.UnZipResultSplitter"/>
</int:splitter>
<int:channel id="output">
<int:queue/>
</int:channel>
</beans>

View File

@@ -0,0 +1,133 @@
/*
* Copyright 2016-2023 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.integration.zip.splitter;
import java.util.LinkedHashMap;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.file.FileHeaders;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.zip.ZipHeaders;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Andriy Kryvtsun
* @author Artem Bilan
*
* @since 6.1
*/
@SpringJUnitConfig
@DirtiesContext
public class UnZipResultSplitterTests {
private static final String DIR_1 = "dir1/";
private static final String DIR_2 = "dir2/";
private static final String FILE_1 = "file1";
private static final String FILE_2 = "file2";
private static final String DATA_1 = "data1";
private static final String DATA_2 = "data2";
private static final int TIMEOUT = 10000;
@Autowired
private MessageChannel input;
@Autowired
private QueueChannel output;
@Test
public void splitPreservingSourceMessageHeaderValues() {
final String headerName = "headerName";
final String headerValue = "headerValue";
Message<?> inMessage = MessageBuilder.withPayload(createPayload())
.setHeader(headerName, headerValue)
.build();
input.send(inMessage);
Message<?> message1 = output.receive(TIMEOUT);
checkMessageWithHeaderValue(message1, headerName, headerValue, DATA_1);
Message<?> message2 = output.receive(TIMEOUT);
checkMessageWithHeaderValue(message2, headerName, headerValue, DATA_2);
}
private static void checkMessageWithHeaderValue(Message<?> message, String headerName, String headerValue,
String payload) {
assertThat(message).isNotNull();
checkHeaderValue(message, headerName, headerValue);
checkPayload(message, payload);
}
@Test
public void splitPreservingServiceHeaderValues() {
Message<?> inMessage = MessageBuilder.withPayload(createPayload())
.setHeader(ZipHeaders.ZIP_ENTRY_PATH, "dir")
.setHeader(FileHeaders.FILENAME, "filename")
.build();
input.send(inMessage);
Message<?> message1 = output.receive(TIMEOUT);
checkMessageWithServiceHeaderValues(message1, DIR_1, FILE_1, DATA_1);
Message<?> message2 = output.receive(TIMEOUT);
checkMessageWithServiceHeaderValues(message2, DIR_2, FILE_2, DATA_2);
}
private static void checkMessageWithServiceHeaderValues(Message<?> message, String path, String filename,
String payload) {
assertThat(message).isNotNull();
checkHeaderValue(message, ZipHeaders.ZIP_ENTRY_PATH, path);
checkHeaderValue(message, FileHeaders.FILENAME, filename);
checkPayload(message, payload);
}
private static Map<String, Object> createPayload() {
Map<String, Object> payload = new LinkedHashMap<>();
payload.put(DIR_1 + FILE_1, DATA_1);
payload.put(DIR_2 + FILE_2, DATA_2);
return payload;
}
private static void checkPayload(Message<?> message, String payload) {
assertThat(message.getPayload()).isEqualTo(payload);
}
private static void checkHeaderValue(Message<?> message, String headerName, String headerValue) {
assertThat(message.getHeaders().get(headerName)).isEqualTo(headerValue);
}
}

View File

@@ -0,0 +1,247 @@
/*
* Copyright 2015-2023 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.integration.zip.transformer;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.Map;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.zeroturnaround.zip.ZipException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.transformer.MessageTransformationException;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessagingException;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
*
* @author Gunnar Hillert
* @author Artem Bilan
* @author Ingo Dueppe
*
* @since 6.1
*/
@SpringJUnitConfig
@DirtiesContext
public class UnZipTransformerTests {
@TempDir
public File workDir;
@Autowired
private ResourceLoader resourceLoader;
@Test
public void unzipFlatFileEntryZip() throws IOException {
final Resource zipResource = this.resourceLoader.getResource("classpath:testzipdata/flatfileentry.zip");
final InputStream is = zipResource.getInputStream();
final Message<InputStream> message = MessageBuilder.withPayload(is).build();
final UnZipTransformer unZipTransformer = new UnZipTransformer();
unZipTransformer.setZipResultType(ZipResultType.FILE);
unZipTransformer.afterPropertiesSet();
final Message<?> resultMessage = unZipTransformer.transform(message);
assertThat(resultMessage).isNotNull();
@SuppressWarnings("unchecked")
Map<String, byte[]> unzippedData = (Map<String, byte[]>) resultMessage.getPayload();
assertThat(unzippedData).isNotNull().hasSize(1);
}
@Test
public void unzipSingleFileAsInputStreamToByteArray() throws IOException {
final Resource resource = this.resourceLoader.getResource("classpath:testzipdata/single.zip");
final InputStream is = resource.getInputStream();
final Message<InputStream> message = MessageBuilder.withPayload(is).build();
final UnZipTransformer unZipTransformer = new UnZipTransformer();
unZipTransformer.setZipResultType(ZipResultType.BYTE_ARRAY);
unZipTransformer.afterPropertiesSet();
final Message<?> resultMessage = unZipTransformer.transform(message);
assertThat(resultMessage).isNotNull();
@SuppressWarnings("unchecked")
Map<String, byte[]> unzippedData = (Map<String, byte[]>) resultMessage.getPayload();
assertThat(unzippedData).isNotNull().hasSize(1);
assertThat(new String(unzippedData.values().iterator().next())).isEqualTo("Spring Integration Rocks!");
}
@Test
public void unzipSingleFileToByteArray() throws IOException {
final Resource resource = this.resourceLoader.getResource("classpath:testzipdata/single.zip");
final InputStream is = resource.getInputStream();
final File inputFile = new File(this.workDir, "unzipSingleFileToByteArray");
FileOutputStream out = new FileOutputStream(inputFile);
IOUtils.copy(is, out);
is.close();
out.close();
final Message<File> message = MessageBuilder.withPayload(inputFile).build();
final UnZipTransformer unZipTransformer = new UnZipTransformer();
unZipTransformer.setZipResultType(ZipResultType.BYTE_ARRAY);
unZipTransformer.afterPropertiesSet();
final Message<?> resultMessage = unZipTransformer.transform(message);
assertThat(resultMessage).isNotNull();
@SuppressWarnings("unchecked")
Map<String, byte[]> unzippedData = (Map<String, byte[]>) resultMessage.getPayload();
assertThat(unzippedData).isNotNull().hasSize(1);
assertThat(inputFile).exists();
assertThat(new String(unzippedData.values().iterator().next())).isEqualTo("Spring Integration Rocks!");
}
@Test
public void unzipSingleFileToByteArrayWithDeleteFilesTrue() throws IOException {
final Resource resource = this.resourceLoader.getResource("classpath:testzipdata/single.zip");
final InputStream is = resource.getInputStream();
final File inputFile = new File(this.workDir, "unzipSingleFileToByteArray");
FileOutputStream output = new FileOutputStream(inputFile);
IOUtils.copy(is, output);
is.close();
output.close();
final Message<File> message = MessageBuilder.withPayload(inputFile).build();
final UnZipTransformer unZipTransformer = new UnZipTransformer();
unZipTransformer.setZipResultType(ZipResultType.BYTE_ARRAY);
unZipTransformer.setDeleteFiles(true);
unZipTransformer.afterPropertiesSet();
final Message<?> resultMessage = unZipTransformer.transform(message);
assertThat(resultMessage).isNotNull();
@SuppressWarnings("unchecked")
Map<String, byte[]> unzippedData = (Map<String, byte[]>) resultMessage.getPayload();
assertThat(unzippedData).isNotNull().hasSize(1);
assertThat(inputFile).doesNotExist();
assertThat(new String(unzippedData.values().iterator().next())).isEqualTo("Spring Integration Rocks!");
}
@Test
public void unzipMultipleFilesAsInputStreamToByteArray() throws IOException {
final Resource resource = this.resourceLoader.getResource("classpath:testzipdata/countries.zip");
final InputStream is = resource.getInputStream();
final Message<InputStream> message = MessageBuilder.withPayload(is).build();
final UnZipTransformer unZipTransformer = new UnZipTransformer();
unZipTransformer.setZipResultType(ZipResultType.BYTE_ARRAY);
unZipTransformer.afterPropertiesSet();
final Message<?> resultMessage = unZipTransformer.transform(message);
assertThat(resultMessage).isNotNull();
@SuppressWarnings("unchecked")
Map<String, byte[]> unzippedData = (Map<String, byte[]>) resultMessage.getPayload();
assertThat(unzippedData).isNotNull().hasSize(5);
}
@Test
public void unzipMultipleFilesAsInputStreamWithExpectSingleResultTrue() throws IOException {
final Resource resource = this.resourceLoader.getResource("classpath:testzipdata/countries.zip");
final InputStream is = resource.getInputStream();
final Message<InputStream> message = MessageBuilder.withPayload(is).build();
final UnZipTransformer unZipTransformer = new UnZipTransformer();
unZipTransformer.setZipResultType(ZipResultType.BYTE_ARRAY);
unZipTransformer.setExpectSingleResult(true);
unZipTransformer.afterPropertiesSet();
assertThatExceptionOfType(MessagingException.class)
.isThrownBy(() -> unZipTransformer.transform(message))
.withStackTraceContaining("The UnZip operation extracted 5 result objects " +
"but expectSingleResult was 'true'.");
}
@Test
public void unzipInvalidZipFile() throws IOException {
File fileToUnzip = File.createTempFile("test1", "tmp");
FileUtils.writeStringToFile(fileToUnzip, "hello world", Charset.defaultCharset());
UnZipTransformer unZipTransformer = new UnZipTransformer();
unZipTransformer.setZipResultType(ZipResultType.BYTE_ARRAY);
unZipTransformer.setExpectSingleResult(true);
unZipTransformer.afterPropertiesSet();
Message<File> message = MessageBuilder.withPayload(fileToUnzip).build();
assertThatExceptionOfType(MessagingException.class)
.isThrownBy(() -> unZipTransformer.transform(message))
.withStackTraceContaining(String.format("Not a zip file: %s", fileToUnzip.getAbsolutePath()));
}
@Test
public void testUnzipMaliciousTraversalZipFile() throws IOException {
final Resource resource = this.resourceLoader.getResource("classpath:testzipdata/zip-malicious-traversal.zip");
final InputStream is = resource.getInputStream();
final Message<InputStream> message = MessageBuilder.withPayload(is).build();
final UnZipTransformer unZipTransformer = new UnZipTransformer();
unZipTransformer.afterPropertiesSet();
assertThatExceptionOfType(MessageTransformationException.class)
.isThrownBy(() -> unZipTransformer.transform(message))
.withRootCauseInstanceOf(ZipException.class)
.withStackTraceContaining("is trying to leave the target output directory");
}
@Configuration
public static class TestConfiguration {
}
}

View File

@@ -0,0 +1,246 @@
/*
* Copyright 2015-2023 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.integration.zip.transformer;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.zeroturnaround.zip.ZipUtil;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.zip.ZipHeaders;
import org.springframework.messaging.Message;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
*
* @author Gunnar Hillert
* @author Artem Bilan
*
* @since 6.1
*/
public class ZipTransformerTests {
@TempDir
public File workDir;
@Test
public void zipString() {
final ZipTransformer zipTransformer = new ZipTransformer();
zipTransformer.setBeanFactory(mock(BeanFactory.class));
zipTransformer.setZipResultType(ZipResultType.BYTE_ARRAY);
zipTransformer.afterPropertiesSet();
final String stringToCompress = "Hello World";
final Date fileDate = new Date();
final Message<String> message = MessageBuilder.withPayload(stringToCompress)
.setHeader(ZipHeaders.ZIP_ENTRY_FILE_NAME, "test.txt")
.setHeader(ZipHeaders.ZIP_ENTRY_LAST_MODIFIED_DATE, fileDate)
.build();
final Message<?> result = zipTransformer.transform(message);
Object resultPayload = result.getPayload();
assertThat(resultPayload).isInstanceOf(byte[].class);
ZipUtil.unpack(new ByteArrayInputStream((byte[]) resultPayload), this.workDir);
final File unzippedEntry = new File(this.workDir, "test.txt");
assertThat(unzippedEntry).exists().isFile();
//See https://stackoverflow.com/questions/3725662/what-is-the-earliest-timestamp-value-that-is-supported-in-zip-file-format
assertThat(unzippedEntry.lastModified())
.isGreaterThan(fileDate.getTime() - 3000)
.isLessThan(fileDate.getTime() + 3000);
}
@Test
public void zipStringCollection() {
final ZipTransformer zipTransformer = new ZipTransformer();
zipTransformer.setBeanFactory(mock(BeanFactory.class));
zipTransformer.setZipResultType(ZipResultType.BYTE_ARRAY);
zipTransformer.afterPropertiesSet();
final String string1ToCompress = "Cartman";
final String string2ToCompress = "Kenny";
final String string3ToCompress = "Butters";
final List<String> strings = new ArrayList<>(3);
strings.add(string1ToCompress);
strings.add(string2ToCompress);
strings.add(string3ToCompress);
final Date fileDate = new Date();
final Message<List<String>> message = MessageBuilder.withPayload(strings)
.setHeader(ZipHeaders.ZIP_ENTRY_FILE_NAME, "test.txt")
.setHeader(ZipHeaders.ZIP_ENTRY_LAST_MODIFIED_DATE, fileDate)
.build();
final Message<?> result = zipTransformer.transform(message);
Object resultPayload = result.getPayload();
assertThat(resultPayload).isInstanceOf(byte[].class);
ZipUtil.unpack(new ByteArrayInputStream((byte[]) resultPayload), this.workDir);
File[] files = this.workDir.listFiles();
assertThat(files).hasSizeGreaterThanOrEqualTo(3);
final Set<String> expectedFileNames = new HashSet<>();
expectedFileNames.add("test_1.txt");
expectedFileNames.add("test_2.txt");
expectedFileNames.add("test_3.txt");
for (File file : files) {
if (file.getName().startsWith("test")) {
assertThat(file).exists().isFile();
//See https://stackoverflow.com/questions/3725662/what-is-the-earliest-timestamp-value-that-is-supported-in-zip-file-format
assertThat(file.lastModified())
.isLessThan(fileDate.getTime() + 4000)
.isGreaterThan(fileDate.getTime() - 4000);
assertThat(file).hasExtension("txt");
assertThat(expectedFileNames).contains(file.getName());
}
}
}
@Test
public void zipStringToFile() {
final ZipTransformer zipTransformer = new ZipTransformer();
zipTransformer.setBeanFactory(mock(BeanFactory.class));
zipTransformer.afterPropertiesSet();
final String stringToCompress = "Hello World";
final String zipEntryFileName = "test.txt";
final Message<String> message = MessageBuilder.withPayload(stringToCompress)
.setHeader(ZipHeaders.ZIP_ENTRY_FILE_NAME, zipEntryFileName)
.build();
final Message<?> result = zipTransformer.transform(message);
assertThat(result.getPayload()).isInstanceOf(File.class);
final File payload = (File) result.getPayload();
assertThat(payload).hasName(message.getHeaders().getId().toString() + ".msg.zip");
assertThat((SpringZipUtils.isValid(payload))).isTrue();
final byte[] zipEntryData = ZipUtil.unpackEntry(payload, "test.txt");
assertThat(zipEntryData).isNotNull();
assertThat(new String(zipEntryData)).isEqualTo("Hello World");
}
@Test
public void zipFile() {
ZipTransformer zipTransformer = new ZipTransformer();
zipTransformer.setBeanFactory(mock(BeanFactory.class));
zipTransformer.setDeleteFiles(true);
zipTransformer.afterPropertiesSet();
final File testFile = createTestFile(10);
assertThat(testFile).exists();
final Message<File> message = MessageBuilder.withPayload(testFile).build();
final Message<?> result = zipTransformer.transform(message);
assertThat(result.getPayload()).isInstanceOf(File.class);
final File payload = (File) result.getPayload();
assertThat(payload).hasName(testFile.getName() + ".zip");
assertThat(SpringZipUtils.isValid(payload)).isTrue();
}
@Test
public void zipCollection() {
final File testFile1 = createTestFile(1);
final File testFile2 = createTestFile(2);
final File testFile3 = createTestFile(3);
final File testFile4 = createTestFile(4);
assertThat(testFile1).exists();
assertThat(testFile2).exists();
assertThat(testFile3).exists();
assertThat(testFile4).exists();
final Collection<File> files = new ArrayList<>();
files.add(testFile1);
files.add(testFile2);
files.add(testFile3);
files.add(testFile4);
final ZipTransformer zipTransformer = new ZipTransformer();
zipTransformer.setBeanFactory(mock(BeanFactory.class));
zipTransformer.afterPropertiesSet();
final Message<Collection<File>> message = MessageBuilder.withPayload(files).build();
final Message<?> result = zipTransformer.transform(message);
assertThat(result.getPayload()).isInstanceOf(File.class);
final File outputZipFile = (File) result.getPayload();
assertThat(outputZipFile).exists().isFile().hasExtension("zip");
assertThat(SpringZipUtils.isValid(outputZipFile)).isTrue();
}
private File createTestFile(int size) {
final File testFile = new File(this.workDir, "testdata" + UUID.randomUUID().toString() + ".data");
try (RandomAccessFile f = new RandomAccessFile(testFile, "rw")) {
f.setLength((long) size * 1024 * 1024);
}
catch (Exception e) {
// Ignore
}
return testFile;
}
}

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="STDOUT" target="SYSTEM_OUT">
<PatternLayout pattern="%d %p [%t] [%c] - %m%n" />
</Console>
</Appenders>
<Loggers>
<Logger name="org.springframework.integration" level="warn"/>
<Logger name="org.springframework.integration.zip" level="warn"/>
<Root level="warn">
<AppenderRef ref="STDOUT" />
</Root>
</Loggers>
</Configuration>

View File

@@ -0,0 +1 @@
Germany

View File

@@ -0,0 +1 @@
France

View File

@@ -0,0 +1 @@
Poland

View File

@@ -0,0 +1 @@
Spring Integration Rocks!

View File

@@ -0,0 +1 @@
Spring Integration Rocks!

View File

@@ -17,6 +17,11 @@ In general the project has been moved to the latest dependency versions.
[[x6.1-new-components]]
=== New Components
[[x6.1-zip]]
==== Zip Support
The Zip Spring Integration Extension project has been migrated as the `spring-integration-zip` module.
See <<./zip.adoc#zip,Zip Support>> for more information.
[[x6.1-general]]

View File

@@ -0,0 +1,272 @@
[[zip]]
== Zip Support
This Spring Integration module provides https://en.wikipedia.org/wiki/ZIP_(file_format)[Zip] (un-)compression support.
A zipping algorithm implementation is based on a https://github.com/zeroturnaround/zt-zip[ZeroTurnaround ZIP Library].
The following components are provided:
* <<zip-transformer,Zip Transformer>>
* <<zip-transformer,UnZip Transformer>>
* <<unzip-splitter,UnZip Result Splitter>>
You need to include this dependency into your project:
====
[source, xml, subs="normal", role="primary"]
.Maven
----
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-zip</artifactId>
<version>{project-version}</version>
</dependency>
----
[source, groovy, subs="normal", role="secondary"]
.Gradle
----
compile "org.springframework.integration:spring-integration-zip:{project-version}"
----
====
[[xpath-namespace-support]]
=== Namespace Support
All components within the Spring Integration Zip module provide namespace support.
In order to enable namespace support, you need to import the schema for the Spring Integration Zip Module.
The following example shows a typical setup:
====
[source,xml]
----
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-zip="http://www.springframework.org/schema/integration/zip"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration
https://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/zip
https://www.springframework.org/schema/integration/zip/spring-integration-zip.xsd">
</beans>
----
====
[[zip-transformer]]
=== (Un)Zip Transformer
The `ZipTransformer` implements a zipping functionality for these types of input `payload`: `File`, `String`, `byte[]` and `Iterable`.
In input data types can be mixed as part of an `Iterable`.
For example, it should be easily to compress a collection containing Strings, byte arrays and Files.
It is important to note that nested Iterables are *NOT SUPPORTED* at present time.
The `ZipTransformer` can be customized by setting several properties:
* `compressionLevel` - sets the compression level.
Default is `Deflater#DEFAULT_COMPRESSION`.
* `useFileAttributes` - specifies whether the name of the file shall be used for the zip entry.
For example to zip a simple `test.txt` file into a `test.txt.zip`, only this configuration is enough:
====
[source, java, role="primary"]
.Java DSL
----
@Bean
public IntegrationFlow zipFlow() {
return IntegrationFlow
.from("zipChannel")
.transform(new ZipTransformer())
.get();
}
----
[source, kotlin, role="secondary"]
.Kotlin DSL
----
@Bean
fun zipFlow() =
integrationFlow("zipChannel") {
transform(ZipTransformer())
}
----
[source, groovy, role="secondary"]
.Groovy DSL
----
@Bean
zipFlow() {
integrationFlow 'zipChannel',
{
transform new ZipTransformer()
}
}
----
[source, java, role="secondary"]
.Java
----
@Transfomer(inputChannel = "zipChannel")
@Bean
ZipTransformer zipTransformer() {
return new ZipTransformer();
}
----
[source, xml, role="secondary"]
.XML
----
<int-zip:zip-transformer input-channel="zipChannel"/>
----
====
See `ZipTransformer` Javadocs for more information.
An `UnZipTransformer` supports these of input `payload`: `File`, `byte[]` and `InputStream`.
When unzipping data, an `expectSingleResult` property can be specified.
If set to `true` and more than `1` zip entry were detected, a `MessagingException` will be raised.
This property also influences the return type of the payload.
If set to `false` (default), then the payload will be of type `SortedMap`, if `true`, however, the actual zip entry will be returned.
Other properties that can be set on the `UnZipTransformer`:
* `deleteFiles` - if the payload is an instance of `File`, this property specifies whether to delete the File after transformation.
Default is `false`.
* `ZipResultType` - defines the format of the data returned after transformation.
Available options are: `File`, `byte[]`.
* `workDirectory` - the work directory is used when a `ZipResultType` is set to `ZipResultType.FILE`.
By default, this property is set to the System temporary directory containing a subdirectory `ziptransformer`.
For example to zip a simple `test.zip` file into a map of its entries, only this configuration is enough:
====
[source, java, role="primary"]
.Java DSL
----
@Bean
public IntegrationFlow unzipFlow() {
return IntegrationFlow
.from("unzipChannel")
.transform(new UnZipTransformer())
.get();
}
----
[source, kotlin, role="secondary"]
.Kotlin DSL
----
@Bean
fun unzipFlow() =
integrationFlow("unzipChannel") {
transform(UnZipTransformer())
}
----
[source, groovy, role="secondary"]
.Groovy DSL
----
@Bean
unzipFlow() {
integrationFlow 'unzipChannel',
{
transform new UnZipTransformer()
}
}
----
[source, java, role="secondary"]
.Java
----
@Transfomer(inputChannel = "unzipChannel")
@Bean
UnZipTransformer unzipTransformer() {
return new UnZipTransformer();
}
----
[source, xml, role="secondary"]
.XML
----
<int-zip:unzip-transformer input-channel="unzipChannel"/>
----
====
[[unzip-splitter]]
=== Unzipped Splitter
The `UnZipResultSplitter` is useful in cases where zip files contain more than `1` entry.
Essentially it has to be used as the next step in the integration flow after the mentioned above `UnZipTransformer`.
It supports only a `Map` as an input data and emits every entry into an `outputChannel` with `FileHeaders.FILENAME` and `ZipHeaders.ZIP_ENTRY_PATH` headers.
The following example demonstrates a simple configuration for splitting unzipped result:
====
[source, java, role="primary"]
.Java DSL
----
@Bean
public IntegrationFlow unzipSplitFlow(Executor executor) {
return IntegrationFlow
.from("unzipChannel")
.transform(new UnZipTransformer())
.split(new UnZipResultSplitter())
.channel(c -> c.executor("entriesChannel", executor))
.get();
}
----
[source, kotlin, role="secondary"]
.Kotlin DSL
----
@Bean
fun unzipFlow(executor: Executor) =
integrationFlow("unzipChannel") {
transform(UnZipTransformer())
split(UnZipResultSplitter())
channel { executor("entriesChannel", executor) }
}
----
[source, groovy, role="secondary"]
.Groovy DSL
----
@Bean
unzipFlow(Executor executor) {
integrationFlow 'unzipChannel',
{
transform new UnZipTransformer()
split new UnZipResultSplitter()
channel { executor 'entriesChannel', executor }
}
}
----
[source, java, role="secondary"]
.Java
----
@Transfomer(inputChannel = "unzipChannel", outputChannel = "splitChannel")
@Bean
UnZipTransformer unzipTransformer() {
return new UnZipTransformer();
}
@Spitter(inputChannel = "splitChannel", outputChannel = "entriesChannel")
@Bean
UnZipResultSplitter unZipSplitter() {
return new UnZipResultSplitter();
}
@Bean
ExecutorChannel entriesChannel(Executor executor) {
return new ExecutorChannel(executor);
}
----
[source, xml, role="secondary"]
.XML
----
<int:chain input-channel="unzipChannel" output-channel="entriesChannel">
<int-zip:unzip-transformer/>
<int:splitter>
<bean class="org.springframework.integration.zip.splitter.UnZipResultSplitter"/>
</int:splitter>
</int:chain>
<int:channel id="entriesChannel">
<int:dispatcher task-executor="executor"/>
</int:channel>
----
====