001/** 002 * Logback: the reliable, generic, fast and flexible logging framework. 003 * Copyright (C) 1999-2015, QOS.ch. All rights reserved. 004 * 005 * This program and the accompanying materials are dual-licensed under 006 * either the terms of the Eclipse Public License v1.0 as published by 007 * the Eclipse Foundation 008 * 009 * or (per the licensee's choosing) 010 * 011 * under the terms of the GNU Lesser General Public License version 2.1 012 * as published by the Free Software Foundation. 013 */ 014package ch.qos.logback.core.rolling.helper; 015 016import java.io.BufferedInputStream; 017import java.io.File; 018import java.io.FileInputStream; 019import java.io.FileOutputStream; 020import java.util.concurrent.ExecutorService; 021import java.util.concurrent.Future; 022import java.util.zip.GZIPOutputStream; 023import java.util.zip.ZipEntry; 024import java.util.zip.ZipOutputStream; 025 026import ch.qos.logback.core.rolling.RolloverFailure; 027import ch.qos.logback.core.spi.ContextAwareBase; 028import ch.qos.logback.core.status.ErrorStatus; 029import ch.qos.logback.core.status.WarnStatus; 030import ch.qos.logback.core.util.FileUtil; 031 032/** 033 * The <code>Compression</code> class implements ZIP and GZ file 034 * compression/decompression methods. 035 * 036 * @author Ceki Gülcü 037 */ 038public class Compressor extends ContextAwareBase { 039 040 final CompressionMode compressionMode; 041 042 static final int BUFFER_SIZE = 8192; 043 044 public Compressor(CompressionMode compressionMode) { 045 this.compressionMode = compressionMode; 046 } 047 048 /** 049 * @param nameOfFile2Compress 050 * @param nameOfCompressedFile 051 * @param innerEntryName The name of the file within the zip file. Use for 052 * ZIP compression. 053 */ 054 public void compress(String nameOfFile2Compress, String nameOfCompressedFile, String innerEntryName) { 055 switch (compressionMode) { 056 case GZ: 057 gzCompress(nameOfFile2Compress, nameOfCompressedFile); 058 break; 059 case ZIP: 060 zipCompress(nameOfFile2Compress, nameOfCompressedFile, innerEntryName); 061 break; 062 case NONE: 063 throw new UnsupportedOperationException("compress method called in NONE compression mode"); 064 } 065 } 066 067 private void zipCompress(String nameOfFile2zip, String nameOfZippedFile, String innerEntryName) { 068 File file2zip = new File(nameOfFile2zip); 069 070 if (!file2zip.exists()) { 071 addStatus(new WarnStatus("The file to compress named [" + nameOfFile2zip + "] does not exist.", this)); 072 073 return; 074 } 075 076 if (innerEntryName == null) { 077 addStatus(new WarnStatus("The innerEntryName parameter cannot be null", this)); 078 return; 079 } 080 081 if (!nameOfZippedFile.endsWith(".zip")) { 082 nameOfZippedFile = nameOfZippedFile + ".zip"; 083 } 084 085 File zippedFile = new File(nameOfZippedFile); 086 087 if (zippedFile.exists()) { 088 addStatus( 089 new WarnStatus("The target compressed file named [" + nameOfZippedFile + "] exist already.", this)); 090 091 return; 092 } 093 094 addInfo("ZIP compressing [" + file2zip + "] as [" + zippedFile + "]"); 095 createMissingTargetDirsIfNecessary(zippedFile); 096 097 try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(nameOfFile2zip)); 098 ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(nameOfZippedFile))) { 099 100 ZipEntry zipEntry = computeZipEntry(innerEntryName); 101 zos.putNextEntry(zipEntry); 102 103 byte[] inbuf = new byte[BUFFER_SIZE]; 104 int n; 105 106 while ((n = bis.read(inbuf)) != -1) { 107 zos.write(inbuf, 0, n); 108 } 109 110 addInfo("Done ZIP compressing [" + file2zip + "] as [" + zippedFile + "]"); 111 } catch (Exception e) { 112 addStatus(new ErrorStatus( 113 "Error occurred while compressing [" + nameOfFile2zip + "] into [" + nameOfZippedFile + "].", this, 114 e)); 115 } 116 if (!file2zip.delete()) { 117 addStatus(new WarnStatus("Could not delete [" + nameOfFile2zip + "].", this)); 118 } 119 } 120 121 // http://jira.qos.ch/browse/LBCORE-98 122 // The name of the compressed file as nested within the zip archive 123 // 124 // Case 1: RawFile = null, Pattern = foo-%d.zip 125 // nestedFilename = foo-${current-date} 126 // 127 // Case 2: RawFile = hello.txt, Pattern = = foo-%d.zip 128 // nestedFilename = foo-${current-date} 129 // 130 // in both cases, the strategy consisting of removing the compression 131 // suffix of zip file works reasonably well. The alternative strategy 132 // whereby the nested file name was based on the value of the raw file name 133 // (applicable to case 2 only) has the disadvantage of the nested files 134 // all having the same name, which could make it harder for the user 135 // to unzip the file without collisions 136 ZipEntry computeZipEntry(File zippedFile) { 137 return computeZipEntry(zippedFile.getName()); 138 } 139 140 ZipEntry computeZipEntry(String filename) { 141 String nameOfFileNestedWithinArchive = computeFileNameStrWithoutCompSuffix(filename, compressionMode); 142 return new ZipEntry(nameOfFileNestedWithinArchive); 143 } 144 145 private void gzCompress(String nameOfFile2gz, String nameOfgzedFile) { 146 File file2gz = new File(nameOfFile2gz); 147 148 if (!file2gz.exists()) { 149 addStatus(new WarnStatus("The file to compress named [" + nameOfFile2gz + "] does not exist.", this)); 150 151 return; 152 } 153 154 if (!nameOfgzedFile.endsWith(".gz")) { 155 nameOfgzedFile = nameOfgzedFile + ".gz"; 156 } 157 158 File gzedFile = new File(nameOfgzedFile); 159 160 if (gzedFile.exists()) { 161 addWarn("The target compressed file named [" + nameOfgzedFile 162 + "] exist already. Aborting file compression."); 163 return; 164 } 165 166 addInfo("GZ compressing [" + file2gz + "] as [" + gzedFile + "]"); 167 createMissingTargetDirsIfNecessary(gzedFile); 168 169 try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(nameOfFile2gz)); 170 GZIPOutputStream gzos = new GZIPOutputStream(new FileOutputStream(nameOfgzedFile))) { 171 172 byte[] inbuf = new byte[BUFFER_SIZE]; 173 int n; 174 175 while ((n = bis.read(inbuf)) != -1) { 176 gzos.write(inbuf, 0, n); 177 } 178 179 addInfo("Done ZIP compressing [" + file2gz + "] as [" + gzedFile + "]"); 180 } catch (Exception e) { 181 addStatus(new ErrorStatus( 182 "Error occurred while compressing [" + nameOfFile2gz + "] into [" + nameOfgzedFile + "].", this, 183 e)); 184 } 185 186 if (!file2gz.delete()) { 187 addStatus(new WarnStatus("Could not delete [" + nameOfFile2gz + "].", this)); 188 } 189 190 } 191 192 static public String computeFileNameStrWithoutCompSuffix(String fileNamePatternStr, 193 CompressionMode compressionMode) { 194 int len = fileNamePatternStr.length(); 195 switch (compressionMode) { 196 case GZ: 197 if (fileNamePatternStr.endsWith(".gz")) 198 return fileNamePatternStr.substring(0, len - 3); 199 else 200 return fileNamePatternStr; 201 case ZIP: 202 if (fileNamePatternStr.endsWith(".zip")) 203 return fileNamePatternStr.substring(0, len - 4); 204 else 205 return fileNamePatternStr; 206 case NONE: 207 return fileNamePatternStr; 208 } 209 throw new IllegalStateException("Execution should not reach this point"); 210 } 211 212 void createMissingTargetDirsIfNecessary(File file) { 213 boolean result = FileUtil.createMissingParentDirectories(file); 214 if (!result) { 215 addError("Failed to create parent directories for [" + file.getAbsolutePath() + "]"); 216 } 217 } 218 219 @Override 220 public String toString() { 221 return this.getClass().getName(); 222 } 223 224 public Future<?> asyncCompress(String nameOfFile2Compress, String nameOfCompressedFile, String innerEntryName) 225 throws RolloverFailure { 226 CompressionRunnable runnable = new CompressionRunnable(nameOfFile2Compress, nameOfCompressedFile, 227 innerEntryName); 228 ExecutorService executorService = context.getScheduledExecutorService(); 229 Future<?> future = executorService.submit(runnable); 230 return future; 231 } 232 233 class CompressionRunnable implements Runnable { 234 final String nameOfFile2Compress; 235 final String nameOfCompressedFile; 236 final String innerEntryName; 237 238 public CompressionRunnable(String nameOfFile2Compress, String nameOfCompressedFile, String innerEntryName) { 239 this.nameOfFile2Compress = nameOfFile2Compress; 240 this.nameOfCompressedFile = nameOfCompressedFile; 241 this.innerEntryName = innerEntryName; 242 } 243 244 public void run() { 245 246 Compressor.this.compress(nameOfFile2Compress, nameOfCompressedFile, innerEntryName); 247 } 248 } 249 250}