This project has retired. For details please refer to its Attic page.
ChukwaDailyRollingFileAppender xref
View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  /* Portions copyright (C) The Apache Software Foundation. All rights reserved.
20   *
21   * This software is published under the terms of the Apache Software
22   * License version 1.1, a copy of which has been included with this
23   * distribution in the LICENSE.txt file.  */
24  
25  package org.apache.hadoop.chukwa.inputtools.log4j;
26  
27  
28  import java.io.File;
29  import java.io.FilenameFilter;
30  import java.io.IOException;
31  import java.text.SimpleDateFormat;
32  import java.util.ArrayList;
33  import java.util.Calendar;
34  import java.util.Collections;
35  import java.util.Date;
36  import java.util.GregorianCalendar;
37  import java.util.List;
38  import java.util.Locale;
39  import java.util.TimeZone;
40  import java.util.regex.Pattern;
41  
42  import org.apache.hadoop.chukwa.datacollection.controller.ChukwaAgentController;
43  import org.apache.hadoop.chukwa.datacollection.controller.ClientFinalizer;
44  import org.apache.hadoop.chukwa.util.AdaptorNamingUtils;
45  import org.apache.hadoop.chukwa.util.RecordConstants;
46  import org.apache.log4j.FileAppender;
47  import org.apache.log4j.Layout;
48  import org.apache.log4j.Logger;
49  import org.apache.log4j.helpers.LogLog;
50  import org.apache.log4j.spi.LoggingEvent;
51  
52  /**
53      ChukwaDailyRollingFileAppender is a slightly modified version of
54      DailyRollingFileAppender, with modified versions of its
55      <code>subAppend()</code> and <code>rollOver()</code> functions. 
56      We would have preferred to sub-class DailyRollingFileAppender but
57      its authors clearly did not intend that to be a viable option since
58      they made too much of the class private or package-private
59  
60      DailyRollingFileAppender extends {@link FileAppender} so that the
61      underlying file is rolled over at a user chosen frequency.
62  
63      <p>The rolling schedule is specified by the <b>DatePattern</b>
64      option. This pattern should follow the {@link SimpleDateFormat}
65      conventions. In particular, you <em>must</em> escape literal text
66      within a pair of single quotes. A formatted version of the date
67      pattern is used as the suffix for the rolled file name.</p>
68  
69      <p>For example, if the <b>File</b> option is set to
70      <code>/foo/bar.log</code> and the <b>DatePattern</b> set to
71      <code>'.'yyyy-MM-dd</code>, on 2001-02-16 at midnight, the logging
72      file <code>/foo/bar.log</code> will be copied to
73      <code>/foo/bar.log.2001-02-16</code> and logging for 2001-02-17
74      will continue in <code>/foo/bar.log</code> until it rolls over
75      the next day.</p>
76  
77      <p>Is is possible to specify monthly, weekly, half-daily, daily,
78      hourly, or minutely rollover schedules.</p>
79  
80      <table border="1" cellpadding="2" summary="">
81      <tr>
82      <th>DatePattern</th>
83      <th>Rollover schedule</th>
84      <th>Example</th></tr>
85  
86      <tr>
87      <td><code>'.'yyyy-MM</code>
88      <td>Rollover at the beginning of each month</td>
89  
90      <td>At midnight of May 31st, 2002 <code>/foo/bar.log</code> will be
91      copied to <code>/foo/bar.log.2002-05</code>. Logging for the month
92      of June will be output to <code>/foo/bar.log</code> until it is
93      also rolled over the next month.</td></tr>
94  
95      <tr>
96      <td><code>'.'yyyy-ww</code>
97  
98      <td>Rollover at the first day of each week. The first day of the
99      week depends on the locale.</td>
100 
101     <td>Assuming the first day of the week is Sunday, on Saturday
102     midnight, June 9th 2002, the file <i>/foo/bar.log</i> will be
103     copied to <i>/foo/bar.log.2002-23</i>.  Logging for the 24th week
104     of 2002 will be output to <code>/foo/bar.log</code> until it is
105     rolled over the next week.</td></tr>
106 
107     <tr>
108     <td><code>'.'yyyy-MM-dd</code>
109 
110     <td>Rollover at midnight each day.</td>
111 
112     <td>At midnight, on March 8th, 2002, <code>/foo/bar.log</code> will
113     be copied to <code>/foo/bar.log.2002-03-08</code>. Logging for the
114     9th day of March will be output to <code>/foo/bar.log</code> until
115     it is rolled over the next day.</td></tr>
116 
117     <tr>
118     <td><code>'.'yyyy-MM-dd-a</code>
119 
120     <td>Rollover at midnight and midday of each day.</td>
121 
122     <td>At noon, on March 9th, 2002, <code>/foo/bar.log</code> will be
123     copied to <code>/foo/bar.log.2002-03-09-AM</code>. Logging for the
124     afternoon of the 9th will be output to <code>/foo/bar.log</code>
125     until it is rolled over at midnight.</td></tr>
126 
127     <tr>
128     <td><code>'.'yyyy-MM-dd-HH</code>
129 
130     <td>Rollover at the top of every hour.</td>
131 
132     <td>At approximately 11:00.000 o'clock on March 9th, 2002,
133     <code>/foo/bar.log</code> will be copied to
134     <code>/foo/bar.log.2002-03-09-10</code>. Logging for the 11th hour
135     of the 9th of March will be output to <code>/foo/bar.log</code>
136     until it is rolled over at the beginning of the next hour.</td></tr>
137 
138 
139     <tr>
140     <td><code>'.'yyyy-MM-dd-HH-mm</code></td>
141 
142     <td>Rollover at the beginning of every minute.</td>
143 
144     <td>At approximately 11:23,000, on March 9th, 2001,
145     <code>/foo/bar.log</code> will be copied to
146     <code>/foo/bar.log.2001-03-09-10-22</code>. Logging for the minute
147     of 11:23 (9th of March) will be output to
148     <code>/foo/bar.log</code> until it is rolled over the next minute.</td></tr>
149     </table>
150 
151     <p>Do not use the colon ":" character in anywhere in the
152     <b>DatePattern</b> option. The text before the colon is interpeted
153     as the protocol specificaion of a URL which is probably not what
154     you want.</p> */
155 
156 public class ChukwaDailyRollingFileAppender extends FileAppender {
157 
158   static Logger log = Logger.getLogger(ChukwaDailyRollingFileAppender.class);
159   // The code assumes that the following constants are in a increasing
160   // sequence.
161   static final int TOP_OF_TROUBLE = -1;
162   static final int TOP_OF_MINUTE = 0;
163   static final int TOP_OF_HOUR = 1;
164   static final int HALF_DAY = 2;
165   static final int TOP_OF_DAY = 3;
166   static final int TOP_OF_WEEK = 4;
167   static final int TOP_OF_MONTH = 5;
168 
169   static final String adaptorType = ChukwaAgentController.CharFileTailUTF8NewLineEscaped;
170 
171   static final Object lock = new Object();
172   static String lastRotation = "";
173 
174   /**
175    * The date pattern. By default, the pattern is set to "'.'yyyy-MM-dd" meaning
176    * daily rollover.
177    */
178   private String datePattern = "'.'yyyy-MM-dd";
179 
180   /**
181    * The log file will be renamed to the value of the scheduledFilename variable
182    * when the next interval is entered. For example, if the rollover period is
183    * one hour, the log file will be renamed to the value of "scheduledFilename"
184    * at the beginning of the next hour.
185    * 
186    * The precise time when a rollover occurs depends on logging activity.
187    */
188   private String scheduledFilename;
189 
190   /**
191    * The next time we estimate a rollover should occur.
192    */
193   private long nextCheck = System.currentTimeMillis() - 1;
194 
195   /**
196    * Regex to select log files to be deleted
197    */
198   private String cleanUpRegex = null;
199 
200   /**
201    * Set the maximum number of backup files to keep around.
202    */
203   private int maxBackupIndex = 10;
204 
205   private ClientFinalizer clientFinalizer = null;
206   
207   boolean hasBeenActivated = false;
208   Date now = new Date();
209 
210   SimpleDateFormat sdf;
211 
212   RollingCalendar rc = new RollingCalendar();
213 
214   int checkPeriod = TOP_OF_TROUBLE;
215 
216   ChukwaAgentController chukwaClient;
217   boolean chukwaClientIsNull = true;
218   static final Object chukwaLock = new Object();
219 
220   String chukwaClientHostname;
221   int chukwaClientPortNum;
222   long chukwaClientConnectNumRetry;
223   long chukwaClientConnectRetryInterval;
224 
225   String recordType;
226 
227   // The gmtTimeZone is used only in computeCheckPeriod() method.
228   static final TimeZone gmtTimeZone = TimeZone.getTimeZone("GMT");
229 
230   /**
231    * The default constructor does nothing.
232    * @throws IOException if constructor initialization error
233    */
234   public ChukwaDailyRollingFileAppender() throws IOException {
235     super();
236   }
237 
238 /**
239      Instantiate a <code>DailyRollingFileAppender</code> and open the
240      file designated by <code>filename</code>. The opened filename will
241      become the output destination for this appender.
242  * @param layout is logging layout
243  * @param filename is the filename to write logs
244  * @param datePattern is the date pattern of log suffix
245  * @throws IOException if constructor initialization error
246 
247    */
248   public ChukwaDailyRollingFileAppender(Layout layout, String filename,
249                                         String datePattern) throws IOException {
250     super(layout, filename, true);
251     System.out
252         .println("Daily Rolling File Appender successfully registered file with agent: "
253             + filename);
254     this.datePattern = datePattern;
255   }
256 
257   /**
258    * The <b>DatePattern</b> takes a string in the same format as expected by
259    * {@link SimpleDateFormat}. This options determines the rollover schedule.
260    * @param pattern is date formatting pattern
261    */
262   public void setDatePattern(String pattern) {
263     datePattern = pattern;
264   }
265 
266   /** Returns the value of the <b>DatePattern</b> option. 
267    * @return date pattern
268    */
269   public String getDatePattern() {
270     return datePattern;
271   }
272 
273   public String getRecordType() {
274     if (recordType != null)
275       return recordType;
276     else
277       return "unknown";
278   }
279 
280   public void setRecordType(String recordType) {
281     this.recordType = recordType;
282   }
283 
284   public void activateOptions() {
285     
286     // Prevent early initialisation
287     if (!hasBeenActivated)
288     { return;}
289     
290     super.activateOptions();
291     if (datePattern != null && fileName != null) {
292       now.setTime(System.currentTimeMillis());
293       sdf = new SimpleDateFormat(datePattern);
294       int type = computeCheckPeriod();
295       printPeriodicity(type);
296       rc.setType(type);
297       File file = new File(fileName);
298       scheduledFilename = fileName + sdf.format(new Date(file.lastModified()));
299 
300     } else {
301       LogLog
302           .error("Either File or DatePattern options are not set for appender ["
303               + name + "].");
304     }
305   }
306 
307   void printPeriodicity(int type) {
308     switch (type) {
309     case TOP_OF_MINUTE:
310       LogLog.debug("Appender [" + name + "] to be rolled every minute.");
311       break;
312     case TOP_OF_HOUR:
313       LogLog
314           .debug("Appender [" + name + "] to be rolled on top of every hour.");
315       break;
316     case HALF_DAY:
317       LogLog.debug("Appender [" + name
318           + "] to be rolled at midday and midnight.");
319       break;
320     case TOP_OF_DAY:
321       LogLog.debug("Appender [" + name + "] to be rolled at midnight.");
322       break;
323     case TOP_OF_WEEK:
324       LogLog.debug("Appender [" + name + "] to be rolled at start of week.");
325       break;
326     case TOP_OF_MONTH:
327       LogLog.debug("Appender [" + name
328           + "] to be rolled at start of every month.");
329       break;
330     default:
331       LogLog.warn("Unknown periodicity for appender [" + name + "].");
332     }
333   }
334 
335   // This method computes the roll over period by looping over the
336   // periods, starting with the shortest, and stopping when the r0 is
337   // different from from r1, where r0 is the epoch formatted according
338   // the datePattern (supplied by the user) and r1 is the
339   // epoch+nextMillis(i) formatted according to datePattern. All date
340   // formatting is done in GMT and not local format because the test
341   // logic is based on comparisons relative to 1970-01-01 00:00:00
342   // GMT (the epoch).
343 
344   int computeCheckPeriod() {
345     RollingCalendar rollingCalendar = new RollingCalendar(gmtTimeZone,
346         Locale.ENGLISH);
347     // set sate to 1970-01-01 00:00:00 GMT
348     Date epoch = new Date(0);
349     if (datePattern != null) {
350       for (int i = TOP_OF_MINUTE; i <= TOP_OF_MONTH; i++) {
351         SimpleDateFormat simpleDateFormat = new SimpleDateFormat(datePattern);
352         simpleDateFormat.setTimeZone(gmtTimeZone); // do all date formatting in
353                                                    // GMT
354         String r0 = simpleDateFormat.format(epoch);
355         rollingCalendar.setType(i);
356         Date next = new Date(rollingCalendar.getNextCheckMillis(epoch));
357         String r1 = simpleDateFormat.format(next);
358         // System.out.println("Type = "+i+", r0 = "+r0+", r1 = "+r1);
359         if (r0 != null && r1 != null && !r0.equals(r1)) {
360           return i;
361         }
362       }
363     }
364     return TOP_OF_TROUBLE; // Deliberately head for trouble...
365   }
366 
367   /**
368    * Rollover the current file to a new file.
369    */
370   void rollOver() throws IOException {
371 
372     /* Compute filename, but only if datePattern is specified */
373     if (datePattern == null) {
374       errorHandler.error("Missing DatePattern option in rollOver().");
375       return;
376     }
377 
378     String datedFilename = fileName + sdf.format(now);
379     // It is too early to roll over because we are still within the
380     // bounds of the current interval. Rollover will occur once the
381     // next interval is reached.
382     if (scheduledFilename.equals(datedFilename)) {
383       return;
384     }
385 
386     // close current file, and rename it to datedFilename
387     this.closeFile();
388 
389     File target = new File(scheduledFilename);
390     if (target.exists()) {
391       if(!target.delete()) {
392         LogLog.warn("Unable to remove: "+target.getAbsolutePath());
393       };
394     }
395 
396     File file = new File(fileName);
397 
398     boolean result = file.renameTo(target);
399     if (result) {
400       LogLog.debug(fileName + " -> " + scheduledFilename);
401     } else {
402       LogLog.error("Failed to rename [" + fileName + "] to ["
403           + scheduledFilename + "].");
404     }
405 
406     try {
407       // This will also close the file. This is OK since multiple
408       // close operations are safe.
409       this.setFile(fileName, false, this.bufferedIO, this.bufferSize);
410     } catch (IOException e) {
411       errorHandler.error("setFile(" + fileName + ", false) call failed.");
412     }
413     scheduledFilename = datedFilename;
414     cleanUp();
415   }
416 
417   public synchronized String getCleanUpRegex() {
418     return cleanUpRegex;
419   }
420 
421   protected synchronized void setCleanUpRegex(String cleanUpRegex) {
422     this.cleanUpRegex = cleanUpRegex;
423   }
424 
425   public int getMaxBackupIndex() {
426     return maxBackupIndex;
427   }
428 
429   public void setMaxBackupIndex(int maxBackupIndex) {
430     this.maxBackupIndex = maxBackupIndex;
431   }
432 
433   protected synchronized void cleanUp() {
434     String regex = "";
435     try {
436       File actualFile = new File(fileName);
437 
438       String directoryName = actualFile.getParent();
439       String actualFileName = actualFile.getName();
440       File dirList = new File(directoryName);
441 
442       if (cleanUpRegex == null || !cleanUpRegex.contains("$fileName")) {
443         LogLog
444             .error("cleanUpRegex == null || !cleanUpRegex.contains(\"$fileName\")");
445         return;
446       }
447       regex = cleanUpRegex.replace("$fileName", actualFileName);
448       String[] dirFiles = dirList.list(new LogFilter(actualFileName, regex));
449 
450       List<String> files = new ArrayList<String>();
451       if(dirFiles!=null) {
452         for (String file : dirFiles) {
453           files.add(file);
454         }
455       }
456       Collections.sort(files);
457 
458       while (files.size() > maxBackupIndex) {
459         String file = files.remove(0);
460         File f = new File(directoryName + "/" + file);
461         if(!f.delete()) {
462           LogLog.warn("Cannot remove: " + file);
463         }
464       }
465     } catch (Exception e) {
466       errorHandler
467           .error("cleanUp(" + fileName + "," + regex + ") call failed.");
468     }
469   }
470 
471   private static class LogFilter implements FilenameFilter {
472     private Pattern p = null;
473     private String logFile = null;
474 
475     public LogFilter(String logFile, String regex) {
476       this.logFile = logFile;
477       p = Pattern.compile(regex);
478     }
479 
480     @Override
481     public boolean accept(File dir, String name) {
482       // ignore current log file
483       if (name.intern() == this.logFile.intern()) {
484         return false;
485       }
486       // ignore file without the same prefix
487       if (!name.startsWith(logFile)) {
488         return false;
489       }
490       return p.matcher(name).find();
491     }
492   }
493 
494   /**
495    * Fix for late-initialisation
496    */
497   @Override
498   protected boolean checkEntryConditions() {
499     synchronized(chukwaLock) {
500       if (!hasBeenActivated) {
501         hasBeenActivated = true;
502         activateOptions();
503       }
504     }
505     return super.checkEntryConditions();
506   }
507 
508   /**
509    * This method differentiates DailyRollingFileAppender from its super class.
510    * 
511    * <p>Before actually logging, this method will check whether it is time to do
512    * a rollover. If it is, it will schedule the next rollover time and then
513    * rollover.
514    * */
515   protected void subAppend(LoggingEvent event) {
516     try {
517       // we set up the chukwa adaptor here because this is the first
518       // point which is called after all setters have been called with
519       // their values from the log4j.properties file, in particular we
520       // needed to give setCukwaClientPortNum() and -Hostname() a shot
521 
522       // Make sure only one thread can do this
523       // and use the boolean to avoid the first level locking
524       if (chukwaClientIsNull) {
525         synchronized (chukwaLock) {
526 
527           String log4jFileName = getFile(); 
528           String recordType = getRecordType();
529 
530           long currentLength = 0L;
531           try {
532             File fooLog = new File(log4jFileName);
533             log4jFileName = fooLog.getAbsolutePath();
534             currentLength = fooLog.length();
535           } catch (Throwable e) {
536             log.warn("Exception while trying to get current file size for " + log4jFileName);
537             currentLength = 0L;
538           }
539 
540           if (chukwaClient == null) {
541             if (getChukwaClientHostname() != null
542                 && getChukwaClientPortNum() != 0) {
543               chukwaClient = new ChukwaAgentController(
544                   getChukwaClientHostname(), getChukwaClientPortNum());
545               log.debug("setup adaptor with hostname "
546                   + getChukwaClientHostname() + " and portnum "
547                   + getChukwaClientPortNum());
548             } else {
549               chukwaClient = new ChukwaAgentController();
550               log
551               .debug("setup adaptor with no args, which means it used its defaults");
552             }
553 
554             chukwaClientIsNull = false;
555 
556             // Watchdog is watching for ChukwaAgent only once every 5 minutes,
557             // so there's no point in retrying more than once every 5 mins.
558             // In practice, if the watchdog is not able to automatically restart
559             // the agent, it will take more than 20 minutes to get Ops to
560             // restart it.
561             // Also its a good to limit the number of communications between
562             // Hadoop and Chukwa, that's why 30 minutes.
563             long retryInterval = chukwaClientConnectRetryInterval;
564             if (retryInterval == 0) {
565               retryInterval = 1000 * 60 * 30;
566             }
567             long numRetries = chukwaClientConnectNumRetry;
568             if (numRetries == 0) {
569               numRetries = 48;
570             }
571 
572             String name = AdaptorNamingUtils.synthesizeAdaptorID
573               (ChukwaAgentController.CharFileTailUTF8NewLineEscaped, recordType, log4jFileName);
574             
575             String adaptorID = chukwaClient.addByName(name, ChukwaAgentController.CharFileTailUTF8NewLineEscaped,
576                 recordType,currentLength + " " + log4jFileName, currentLength,
577                 numRetries, retryInterval);
578 
579             // Setup a shutdownHook for the controller
580             clientFinalizer = new ClientFinalizer(chukwaClient);
581             Runtime.getRuntime().addShutdownHook(clientFinalizer);
582 
583             if (adaptorID != null) {
584               log.debug("Added file tailing adaptor to chukwa agent for file "
585                   + log4jFileName + ", adaptorId:" + adaptorID 
586                   + " using this recordType :" + recordType 
587                   + ", starting at offset:" + currentLength);
588             } else {
589               log.debug("Chukwa adaptor not added, addFile(" + log4jFileName
590                   + ") returned, current offset: " + currentLength);
591             }
592 
593           }
594         }
595       }
596 
597       long n = System.currentTimeMillis();
598       if (n >= nextCheck) {
599         now.setTime(n);
600         nextCheck = rc.getNextCheckMillis(now);
601         try {
602           rollOver();
603         } catch (IOException ioe) {
604           LogLog.error("rollOver() failed.", ioe);
605         }
606       }
607 
608       boolean written = false;
609       if(layout.ignoresThrowable()) {
610         String[] s = event.getThrowableStrRep();
611         if (s != null) {
612           int len = s.length;
613           StringBuilder sb = new StringBuilder();
614           sb.append(this.layout.format(event));
615           for(int i = 0; i < len; i++) {
616             sb.append(s[i]).append("\n");
617           }
618           //escape the newlines from record bodies, exception and then write this record to the log file
619           written = true;
620           this.qw.write(RecordConstants.escapeAllButLastRecordSeparator("\n",sb.toString()));
621         } 
622       }
623        
624       if (!written) {
625         //escape the newlines from record bodies and then write this record to the log file
626         this.qw.write(RecordConstants.escapeAllButLastRecordSeparator("\n",this.layout.format(event)));
627       }
628       
629       if (this.immediateFlush) {
630         this.qw.flush();
631       }
632     } catch (Throwable e) {
633       System.err.println("Exception in ChukwaRollingAppender: "
634           + e.getMessage());
635       e.printStackTrace();
636     }
637 
638   }
639 
640   public String getChukwaClientHostname() {
641     return chukwaClientHostname;
642   }
643 
644   public void setChukwaClientHostname(String chukwaClientHostname) {
645     this.chukwaClientHostname = chukwaClientHostname;
646   }
647 
648   public int getChukwaClientPortNum() {
649     return chukwaClientPortNum;
650   }
651 
652   public void setChukwaClientPortNum(int chukwaClientPortNum) {
653     this.chukwaClientPortNum = chukwaClientPortNum;
654   }
655 
656   public void setChukwaClientConnectNumRetry(int i) {
657     this.chukwaClientConnectNumRetry = i;
658   }
659 
660   public void setChukwaClientConnectRetryInterval(long i) {
661     this.chukwaClientConnectRetryInterval = i;
662   }
663 
664 }
665 
666 
667 /**
668  * RollingCalendar is a helper class to DailyRollingFileAppender. Given a
669  * periodicity type and the current time, it computes the start of the next
670  * interval.
671  * */
672 class RollingCalendar extends GregorianCalendar {
673 
674   /**
675 	 * 
676 	 */
677   private static final long serialVersionUID = 2153481574198792767L;
678   int type = ChukwaDailyRollingFileAppender.TOP_OF_TROUBLE;
679 
680   RollingCalendar() {
681     super();
682   }
683 
684   RollingCalendar(TimeZone tz, Locale locale) {
685     super(tz, locale);
686   }
687 
688   void setType(int type) {
689     this.type = type;
690   }
691 
692   public long getNextCheckMillis(Date now) {
693     return getNextCheckDate(now).getTime();
694   }
695 
696   public Date getNextCheckDate(Date now) {
697     this.setTime(now);
698 
699     switch (type) {
700     case ChukwaDailyRollingFileAppender.TOP_OF_MINUTE:
701       this.set(Calendar.SECOND, 0);
702       this.set(Calendar.MILLISECOND, 0);
703       this.add(Calendar.MINUTE, 1);
704       break;
705     case ChukwaDailyRollingFileAppender.TOP_OF_HOUR:
706       this.set(Calendar.MINUTE, 0);
707       this.set(Calendar.SECOND, 0);
708       this.set(Calendar.MILLISECOND, 0);
709       this.add(Calendar.HOUR_OF_DAY, 1);
710       break;
711     case ChukwaDailyRollingFileAppender.HALF_DAY:
712       this.set(Calendar.MINUTE, 0);
713       this.set(Calendar.SECOND, 0);
714       this.set(Calendar.MILLISECOND, 0);
715       int hour = get(Calendar.HOUR_OF_DAY);
716       if (hour < 12) {
717         this.set(Calendar.HOUR_OF_DAY, 12);
718       } else {
719         this.set(Calendar.HOUR_OF_DAY, 0);
720         this.add(Calendar.DAY_OF_MONTH, 1);
721       }
722       break;
723     case ChukwaDailyRollingFileAppender.TOP_OF_DAY:
724       this.set(Calendar.HOUR_OF_DAY, 0);
725       this.set(Calendar.MINUTE, 0);
726       this.set(Calendar.SECOND, 0);
727       this.set(Calendar.MILLISECOND, 0);
728       this.add(Calendar.DATE, 1);
729       break;
730     case ChukwaDailyRollingFileAppender.TOP_OF_WEEK:
731       this.set(Calendar.DAY_OF_WEEK, getFirstDayOfWeek());
732       this.set(Calendar.HOUR_OF_DAY, 0);
733       this.set(Calendar.SECOND, 0);
734       this.set(Calendar.MILLISECOND, 0);
735       this.add(Calendar.WEEK_OF_YEAR, 1);
736       break;
737     case ChukwaDailyRollingFileAppender.TOP_OF_MONTH:
738       this.set(Calendar.DATE, 1);
739       this.set(Calendar.HOUR_OF_DAY, 0);
740       this.set(Calendar.SECOND, 0);
741       this.set(Calendar.MILLISECOND, 0);
742       this.add(Calendar.MONTH, 1);
743       break;
744     default:
745       throw new IllegalStateException("Unknown periodicity type.");
746     }
747     return getTime();
748   }
749   
750   @Override
751   public boolean equals(Object o) {
752     return super.equals(o);
753   }
754   
755   @Override
756   public int hashCode() {
757     return super.hashCode();
758   }
759 }