View Javadoc
1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v1.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  package ch.qos.logback.core.rolling.helper;
15  
16  import java.io.BufferedInputStream;
17  import java.io.File;
18  import java.io.FileInputStream;
19  import java.io.FileOutputStream;
20  import java.util.concurrent.ExecutorService;
21  import java.util.concurrent.Future;
22  import java.util.zip.GZIPOutputStream;
23  import java.util.zip.ZipEntry;
24  import java.util.zip.ZipOutputStream;
25  
26  import ch.qos.logback.core.rolling.RolloverFailure;
27  import ch.qos.logback.core.spi.ContextAwareBase;
28  import ch.qos.logback.core.status.ErrorStatus;
29  import ch.qos.logback.core.status.WarnStatus;
30  import ch.qos.logback.core.util.FileUtil;
31  
32  /**
33   * The <code>Compression</code> class implements ZIP and GZ file
34   * compression/decompression methods.
35   *
36   * @author Ceki G&uuml;lc&uuml;
37   */
38  public class Compressor extends ContextAwareBase {
39  
40      final CompressionMode compressionMode;
41  
42      static final int BUFFER_SIZE = 8192;
43  
44      public Compressor(CompressionMode compressionMode) {
45          this.compressionMode = compressionMode;
46      }
47  
48      /**
49       * @param nameOfFile2Compress
50       * @param nameOfCompressedFile
51       * @param innerEntryName       The name of the file within the zip file. Use for
52       *                             ZIP compression.
53       */
54      public void compress(String nameOfFile2Compress, String nameOfCompressedFile, String innerEntryName) {
55          switch (compressionMode) {
56          case GZ:
57              gzCompress(nameOfFile2Compress, nameOfCompressedFile);
58              break;
59          case ZIP:
60              zipCompress(nameOfFile2Compress, nameOfCompressedFile, innerEntryName);
61              break;
62          case NONE:
63              throw new UnsupportedOperationException("compress method called in NONE compression mode");
64          }
65      }
66  
67      private void zipCompress(String nameOfFile2zip, String nameOfZippedFile, String innerEntryName) {
68          File file2zip = new File(nameOfFile2zip);
69  
70          if (!file2zip.exists()) {
71              addStatus(new WarnStatus("The file to compress named [" + nameOfFile2zip + "] does not exist.", this));
72  
73              return;
74          }
75  
76          if (innerEntryName == null) {
77              addStatus(new WarnStatus("The innerEntryName parameter cannot be null", this));
78              return;
79          }
80  
81          if (!nameOfZippedFile.endsWith(".zip")) {
82              nameOfZippedFile = nameOfZippedFile + ".zip";
83          }
84  
85          File zippedFile = new File(nameOfZippedFile);
86  
87          if (zippedFile.exists()) {
88              addStatus(
89                      new WarnStatus("The target compressed file named [" + nameOfZippedFile + "] exist already.", this));
90  
91              return;
92          }
93  
94          addInfo("ZIP compressing [" + file2zip + "] as [" + zippedFile + "]");
95          createMissingTargetDirsIfNecessary(zippedFile);
96  
97          try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(nameOfFile2zip));
98                  ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(nameOfZippedFile))) {
99  
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 GZ 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.getExecutorService();
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 }