/* (c) 2014 Open Source Geospatial Foundation - all rights reserved * (c) 2001 - 2013 OpenPlans * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.wps.ppio; import java.io.DataInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Collection; import java.util.logging.Level; import java.util.logging.Logger; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.filefilter.FileFilterUtils; import org.geoserver.data.util.IOUtils; import org.geotools.util.logging.Logging; /** * Handles input and output of feature collections as zipped files. * * @author "Alessio Fabiani - alessio.fabiani@geo-solutions.it" * @author Simone Giannecchini, GeoSolutions SAS */ public class ZipArchivePPIO extends BinaryPPIO { public static final String ZIP = "zip"; private final static Logger LOGGER = Logging.getLogger(ZipArchivePPIO.class); /** Parameter indicating the compression level to use */ private int compressionLevel; /** * Instantiates a new zip archive ppio. * * @param resources the resources */ public ZipArchivePPIO(int compressionLevel) { super(File.class, File.class, "application/zip"); if (compressionLevel < ZipOutputStream.STORED || compressionLevel > ZipOutputStream.DEFLATED) { throw new IllegalArgumentException("Invalid Compression Level: " + compressionLevel); } this.compressionLevel = compressionLevel; if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Using compression level " + compressionLevel); } } /** * Default constructor using ZipOutputStream.STORED compression level. * */ public ZipArchivePPIO() { this(ZipOutputStream.STORED); if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Using compression level " + ZipOutputStream.STORED); } } /** * Encodes the output file. * * @param output the output * @param os the os * @throws Exception the exception */ @SuppressWarnings("rawtypes") @Override public void encode(final Object output, OutputStream os) throws Exception { // avoid double zipping if (output instanceof File && isZpFile((File) output)) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "File is already a zip, we have only to copy it"); } FileUtils.copyFile((File) output, os); return; } ZipOutputStream zipout = new ZipOutputStream(os); zipout.setLevel(compressionLevel); // directory if (output instanceof File) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Zipping the file"); } final File file = ((File) output); if (file.isDirectory()) { IOUtils.zipDirectory(file, zipout, FileFilterUtils.trueFileFilter()); } else { // check if is a zip file already zipFile(file, zipout); } } else { // list of files if (output instanceof Collection) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Zipping the collection"); } // create temp dir final Collection collection = (Collection) output; for (Object obj : collection) { if (obj instanceof File) { // convert to file and add to zip final File file = ((File) obj); if (file.isDirectory()) { IOUtils.zipDirectory(file, zipout, FileFilterUtils.trueFileFilter()); } else { // check if is a zip file already zipFile(file, zipout); } } else { if (LOGGER.isLoggable(Level.INFO)) { LOGGER.info("Skipping object -->" + obj.toString()); } } } } else { // error throw new IllegalArgumentException("Unable to zip provided output. Output-->" + output != null ? output.getClass().getCanonicalName() : "null"); } } if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Finished to zip"); } zipout.finish(); } /** * Gets the file extension. * * @return the file extension */ @Override public String getFileExtension() { return ZIP; } /** * This method zip the provided file to the provided {@link ZipOutputStream}. * * <p> * It throws {@link IllegalArgumentException} in case the provided file does not exists or is not a readable file. * * @param file the {@link File} to zip * @param zipout the {@link ZipOutputStream} to write to * @throws IOException in case something bad happen */ public static void zipFile(File file, ZipOutputStream zipout) throws IOException { // copy file by reading 4k at a time (faster than buffered reading) byte[] buffer = new byte[4096]; zipFileInternal(file, zipout, buffer); } /** * This method tells us if the provided {@link File} is a Zip File. * * <p> * It throws {@link IllegalArgumentException} in case the provided file does not exists or is not a readable file. * * @param file the {@link File} to check for zip * @throws IOException in case something bad happen */ public static boolean isZpFile(File file) { if (file == null || !file.exists() || !file.canRead()) { throw new IllegalArgumentException( "Provided File is not valid and/or reqadable! --> File:" + file != null ? file .getAbsolutePath() : "null"); } // Check if the file is a directory if (file.isDirectory()) { return false; } // Check on the path length if (file.length() < 4) { return false; } // Check on the first Integer DataInputStream in = null; try { in = new DataInputStream(new FileInputStream(file)); int test = in.readInt(); return test == 0x504b0304; } catch (IOException e) { if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e); } return false; } finally { if (in != null) { org.apache.commons.io.IOUtils.closeQuietly(in); } } } /** * This method zip the provided file to the provided {@link ZipOutputStream}. * * <p> * It throws {@link IllegalArgumentException} in case the provided file does not exists or is not a readable file. * * @param file the {@link File} to zip * @param zipout the {@link ZipOutputStream} to write to * @param buffer the buffer to use for reading/writing * @throws IOException in case something bad happen */ private static void zipFileInternal(File file, ZipOutputStream zipout, byte[] buffer) throws IOException { if (file == null || !file.exists() || !file.canRead()) { throw new IllegalArgumentException( "Provided File is not valid and/or reqadable! --> File:" + file != null ? file .getAbsolutePath() : "null"); } final ZipEntry entry = new ZipEntry(FilenameUtils.getName(file.getAbsolutePath())); zipout.putNextEntry(entry); // copy over the file InputStream in = null; try { int c; in = new FileInputStream(file); while (-1 != (c = in.read(buffer))) { zipout.write(buffer, 0, c); } zipout.closeEntry(); } finally { // close the input stream if (in != null) { org.apache.commons.io.IOUtils.closeQuietly(in); } } zipout.flush(); } @Override public Object decode(InputStream input) throws Exception { throw new UnsupportedOperationException("Decode unsupported"); } }