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 
52       *            The name of the file within the zip file. Use for 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(new WarnStatus("The target compressed file named [" + nameOfZippedFile + "] exist already.", this));
89  
90              return;
91          }
92  
93          addInfo("ZIP compressing [" + file2zip + "] as [" + zippedFile + "]");
94          createMissingTargetDirsIfNecessary(zippedFile);
95  
96          try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(nameOfFile2zip));
97                          ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(nameOfZippedFile))) {
98  
99              ZipEntry zipEntry = computeZipEntry(innerEntryName);
100             zos.putNextEntry(zipEntry);
101 
102             byte[] inbuf = new byte[BUFFER_SIZE];
103             int n;
104 
105             while ((n = bis.read(inbuf)) != -1) {
106                 zos.write(inbuf, 0, n);
107             }
108 
109             addInfo("Done ZIP compressing [" + file2zip + "] as [" + zippedFile + "]");
110         } catch (Exception e) {
111             addStatus(new ErrorStatus("Error occurred while compressing [" + nameOfFile2zip + "] into [" + nameOfZippedFile + "].", this, e));
112         }
113         if (!file2zip.delete()) {
114             addStatus(new WarnStatus("Could not delete [" + nameOfFile2zip + "].", this));
115         }
116     }
117 
118     // http://jira.qos.ch/browse/LBCORE-98
119     // The name of the compressed file as nested within the zip archive
120     //
121     // Case 1: RawFile = null, Patern = foo-%d.zip
122     // nestedFilename = foo-${current-date}
123     //
124     // Case 2: RawFile = hello.txt, Pattern = = foo-%d.zip
125     // nestedFilename = foo-${current-date}
126     //
127     // in both cases, the strategy consisting of removing the compression
128     // suffix of zip file works reasonably well. The alternative strategy
129     // whereby the nested file name was based on the value of the raw file name
130     // (applicable to case 2 only) has the disadvantage of the nested files
131     // all having the same name, which could make it harder for the user
132     // to unzip the file without collisions
133     ZipEntry computeZipEntry(File zippedFile) {
134         return computeZipEntry(zippedFile.getName());
135     }
136 
137     ZipEntry computeZipEntry(String filename) {
138         String nameOfFileNestedWithinArchive = computeFileNameStrWithoutCompSuffix(filename, compressionMode);
139         return new ZipEntry(nameOfFileNestedWithinArchive);
140     }
141 
142     private void gzCompress(String nameOfFile2gz, String nameOfgzedFile) {
143         File file2gz = new File(nameOfFile2gz);
144 
145         if (!file2gz.exists()) {
146             addStatus(new WarnStatus("The file to compress named [" + nameOfFile2gz + "] does not exist.", this));
147 
148             return;
149         }
150 
151         if (!nameOfgzedFile.endsWith(".gz")) {
152             nameOfgzedFile = nameOfgzedFile + ".gz";
153         }
154 
155         File gzedFile = new File(nameOfgzedFile);
156 
157         if (gzedFile.exists()) {
158             addWarn("The target compressed file named [" + nameOfgzedFile + "] exist already. Aborting file compression.");
159             return;
160         }
161 
162         addInfo("GZ compressing [" + file2gz + "] as [" + gzedFile + "]");
163         createMissingTargetDirsIfNecessary(gzedFile);
164 
165         try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(nameOfFile2gz));
166                         GZIPOutputStream gzos = new GZIPOutputStream(new FileOutputStream(nameOfgzedFile))) {
167 
168             byte[] inbuf = new byte[BUFFER_SIZE];
169             int n;
170 
171             while ((n = bis.read(inbuf)) != -1) {
172                 gzos.write(inbuf, 0, n);
173             }
174 
175             addInfo("Done ZIP compressing [" + file2gz + "] as [" + gzedFile + "]");
176         } catch (Exception e) {
177             addStatus(new ErrorStatus("Error occurred while compressing [" + nameOfFile2gz + "] into [" + nameOfgzedFile + "].", this, e));
178         }
179 
180         if (!file2gz.delete()) {
181             addStatus(new WarnStatus("Could not delete [" + nameOfFile2gz + "].", this));
182         }
183 
184     }
185 
186     static public String computeFileNameStrWithoutCompSuffix(String fileNamePatternStr, CompressionMode compressionMode) {
187         int len = fileNamePatternStr.length();
188         switch (compressionMode) {
189         case GZ:
190             if (fileNamePatternStr.endsWith(".gz"))
191                 return fileNamePatternStr.substring(0, len - 3);
192             else
193                 return fileNamePatternStr;
194         case ZIP:
195             if (fileNamePatternStr.endsWith(".zip"))
196                 return fileNamePatternStr.substring(0, len - 4);
197             else
198                 return fileNamePatternStr;
199         case NONE:
200             return fileNamePatternStr;
201         }
202         throw new IllegalStateException("Execution should not reach this point");
203     }
204 
205     void createMissingTargetDirsIfNecessary(File file) {
206         boolean result = FileUtil.createMissingParentDirectories(file);
207         if (!result) {
208             addError("Failed to create parent directories for [" + file.getAbsolutePath() + "]");
209         }
210     }
211 
212     @Override
213     public String toString() {
214         return this.getClass().getName();
215     }
216 
217     public Future<?> asyncCompress(String nameOfFile2Compress, String nameOfCompressedFile, String innerEntryName) throws RolloverFailure {
218         CompressionRunnable runnable = new CompressionRunnable(nameOfFile2Compress, nameOfCompressedFile, innerEntryName);
219         ExecutorService executorService = context.getScheduledExecutorService();
220         Future<?> future = executorService.submit(runnable);
221         return future;
222     }
223 
224     class CompressionRunnable implements Runnable {
225         final String nameOfFile2Compress;
226         final String nameOfCompressedFile;
227         final String innerEntryName;
228 
229         public CompressionRunnable(String nameOfFile2Compress, String nameOfCompressedFile, String innerEntryName) {
230             this.nameOfFile2Compress = nameOfFile2Compress;
231             this.nameOfCompressedFile = nameOfCompressedFile;
232             this.innerEntryName = innerEntryName;
233         }
234 
235         public void run() {
236 
237             Compressor.this.compress(nameOfFile2Compress, nameOfCompressedFile, innerEntryName);
238         }
239     }
240 
241 }