package fr.acxio.tools.agia.tasks; /* * Copyright 2014 Acxio * * 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 * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import java.io.File; import java.io.FileFilter; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.Map; import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; import org.apache.commons.compress.utils.IOUtils; import org.apache.commons.io.filefilter.FileFileFilter; import org.apache.commons.io.filefilter.TrueFileFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.batch.core.StepContribution; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.core.step.tasklet.Tasklet; import org.springframework.batch.repeat.RepeatStatus; import org.springframework.beans.factory.InitializingBean; import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.Resource; import org.springframework.util.Assert; import fr.acxio.tools.agia.io.ResourceFactory; import fr.acxio.tools.agia.io.ResourceFactoryConstants; import fr.acxio.tools.agia.io.ResourcesFactory; public class ZipFilesTasklet implements Tasklet, InitializingBean { private static final Logger LOGGER = LoggerFactory.getLogger(ZipFilesTasklet.class); protected Resource sourceBaseDirectory; protected ResourcesFactory sourceFactory; protected ResourceFactory destinationFactory; protected boolean recursive = true; protected String sourceBaseDirectoryPath; public void setSourceBaseDirectory(Resource sSourceBaseDirectory) { // FIXME : Root base dir like C:\ will not work correctly because the last \ is not stripped by File.getCanonicalPath() sourceBaseDirectory = sSourceBaseDirectory; } public void setSourceFactory(ResourcesFactory sSourceFactory) { sourceFactory = sSourceFactory; } public void setDestinationFactory(ResourceFactory sDestinationFactory) { destinationFactory = sDestinationFactory; } public boolean isRecursive() { return recursive; } public void setRecursive(boolean sRecursive) { recursive = sRecursive; } public void afterPropertiesSet() { Assert.notNull(sourceBaseDirectory, "Source base directory must be set. It is available has BASEDIR in the SourceFactory parameters."); Assert.notNull(sourceFactory, "SourceFactory must be set"); Assert.notNull(destinationFactory, "DestinationFactory must be set"); } @Override public RepeatStatus execute(StepContribution sContribution, ChunkContext sChunkContext) throws Exception { // 1. Destination exists // a. Overwrite => default behaviour // b. Update => copy to temporary file, open, read entries, merge with new entries, write merged entries and stream // 2. New destination => default behaviour Map<String, Object> aSourceParams = new HashMap<String, Object>(); aSourceParams.put(ResourceFactoryConstants.PARAM_STEP_EXEC, ((sChunkContext != null) && (sChunkContext.getStepContext() != null)) ? sChunkContext .getStepContext().getStepExecution() : null); aSourceParams.put(ResourceFactoryConstants.PARAM_BASE_DIRECTORY, sourceBaseDirectory); Resource[] aSourceResources = sourceFactory.getResources(aSourceParams); Map<String, Object> aDestinationParams = new HashMap<String, Object>(); if (LOGGER.isInfoEnabled()) { LOGGER.info("{} file(s) to zip", aSourceResources.length); } if (aSourceResources.length > 0) { aDestinationParams.put(ResourceFactoryConstants.PARAM_BASE_DIRECTORY, sourceBaseDirectory); aDestinationParams.put(ResourceFactoryConstants.PARAM_STEP_EXEC, ((sChunkContext != null) && (sChunkContext.getStepContext() != null)) ? sChunkContext.getStepContext().getStepExecution() : null); Resource aDestination = destinationFactory.getResource(aDestinationParams); ZipArchiveOutputStream aZipArchiveOutputStream = null; try { aZipArchiveOutputStream = new ZipArchiveOutputStream(aDestination.getFile()); sourceBaseDirectoryPath = sourceBaseDirectory.getFile().getCanonicalPath(); for (Resource aSourceResource : aSourceResources) { zipResource(aSourceResource, aZipArchiveOutputStream, sContribution, sChunkContext); } } finally { if (aZipArchiveOutputStream != null) { aZipArchiveOutputStream.finish(); aZipArchiveOutputStream.close(); } } } return RepeatStatus.FINISHED; } protected void zipResource(Resource sSourceResource, ZipArchiveOutputStream sZipArchiveOutputStream, StepContribution sContribution, ChunkContext sChunkContext) throws IOException, ZipFilesException { // TODO : use a queue to reduce the callstack overhead if (sSourceResource.exists()) { File aSourceFile = sSourceResource.getFile(); String aSourcePath = aSourceFile.getCanonicalPath(); if (!aSourcePath.startsWith(sourceBaseDirectoryPath)) { throw new ZipFilesException("Source file " + aSourcePath + " does not match base directory " + sourceBaseDirectoryPath); } if (sContribution != null) { sContribution.incrementReadCount(); } String aZipEntryName = aSourcePath.substring(sourceBaseDirectoryPath.length() + 1); sZipArchiveOutputStream.putArchiveEntry(new ZipArchiveEntry(aZipEntryName)); if (LOGGER.isInfoEnabled()) { LOGGER.info("Zipping {} to {}", sSourceResource.getFile().getCanonicalPath(), aZipEntryName); } if (aSourceFile.isFile()) { InputStream aInputStream = sSourceResource.getInputStream(); IOUtils.copy(aInputStream, sZipArchiveOutputStream); aInputStream.close(); sZipArchiveOutputStream.closeArchiveEntry(); } else { sZipArchiveOutputStream.closeArchiveEntry(); for(File aFile : aSourceFile.listFiles((FileFilter)(recursive ? TrueFileFilter.TRUE : FileFileFilter.FILE))) { zipResource(new FileSystemResource(aFile), sZipArchiveOutputStream, sContribution, sChunkContext); } } if (sContribution != null) { sContribution.incrementWriteCount(1); } } else if (LOGGER.isInfoEnabled()) { LOGGER.info("{} does not exist", sSourceResource.getFilename()); } } }