View Javadoc
1   package net.logAnalyzer.handlers;
2   
3   import java.io.FileNotFoundException;
4   import java.io.IOException;
5   import java.util.Hashtable;
6   import java.util.Vector;
7   
8   import net.logAnalyzer.analysis.LAAnalyzer;
9   import net.logAnalyzer.converters.LAConverter;
10  import net.logAnalyzer.converters.UnknownOrLiteralConverterException;
11  import net.logAnalyzer.patternParser.PatternParser;
12  import net.logAnalyzer.patternParser.PatternParsingException;
13  import net.logAnalyzer.utils.FilesSetReader;
14  
15  /***
16   * This class defines a template for log file handlers. A log file handler is
17   * used to apply filters and analyzers on a log file. Filters dans analyzers can
18   * be added before and/or after log file parsing.
19   * 
20   * @author Karim REFEYTON
21   * @version 0.1
22   */
23  public abstract class AbstractLogHandler implements LALogHandler {
24      /***
25       * Analyzers to apply.
26       */
27      private LAAnalyzer[] analyzers = null;
28  
29      /***
30       * Converters defined in the log4j pattern used to parse the
31       * {@link FilesSetReader} returned by {@link #getReader()}.
32       */
33      private LAConverter[] converters = null;
34  
35      /***
36       * Index of converters. The hashtable keys are the
37       * {@link LAConverter#getLiteral()}, as "%p" for a log4j priority column.
38       */
39      private Hashtable convertersIndex = null;
40  
41      /***
42       * Message converter.
43       * <p>
44       * Message converter is a special converter used to concat the current
45       * messageLabel text with the extra lines unparsed.
46       * 
47       * @see #parse()
48       * @see LAMessage#extendsMessage(String)
49       */
50      private LAConverter messageConverter = null;
51  
52      /***
53       * Number of lines to read from the log file to parse a log messageLabel.
54       */
55      private int linesToRead = 1;
56  
57      /***
58       * Reader from input.
59       */
60      private FilesSetReader reader = null;
61  
62      /***
63       * Number of records to parse; <tt>0</tt> for no limitation.
64       */
65      private int maxRecords = 0;
66  
67      /***
68       * <tt>true</tt> if the handler is currently parsing the log.
69       */
70      private boolean parsing = false;
71  
72      /***
73       * Last exception thrown by the parsing or analysis processes.
74       */
75      private Exception lastException = null;
76  
77      /***
78       * Constructs a log handler for the specified input files parsed with the
79       * specified converters.
80       * 
81       * @param input
82       *            Log file(s) to parse.
83       * @param pattern
84       *            Pattern used to parse file.
85       * @throws NoConverterException
86       *             If no converter is specified.
87       * @throws FileNotFoundException
88       *             If file handled by <tt>input</tt> not found.
89       */
90      public AbstractLogHandler(FilesSetReader input, String pattern)
91              throws FileNotFoundException, NoConverterException {
92          this.reader = input;
93          this.converters = parsePattern(pattern);
94  
95          if (this.converters == null || this.converters.length == 0) {
96              throw new NoConverterException();
97          }
98          // Saves index for non literal columns for quick access
99          this.convertersIndex = new Hashtable();
100         for (int i = 0; i < this.converters.length; i++) {
101             if (!this.converters[i].isLiteral()) {
102                 this.convertersIndex.put(this.converters[i].getLiteral(),
103                         new Integer(i));
104             }
105             if (this.converters[i].isMessage()) {
106                 messageConverter = this.converters[i];
107             }
108         }
109     }
110 
111     /***
112      * Adds new analyzers to the log handler.
113      * 
114      * @param newAnalyzers
115      *            Analyzers to add.
116      */
117     public final void setAnalyzers(LAAnalyzer[] newAnalyzers) {
118         int nbAnalyzers = 0;
119         if (analyzers == null) {
120             // Instanciates analyzers array
121             analyzers = new LAAnalyzer[newAnalyzers.length];
122         } else {
123             // Extends analyzers array
124             LAAnalyzer[] oldAnalyzers = analyzers;
125             nbAnalyzers = oldAnalyzers.length;
126             analyzers = new LAAnalyzer[nbAnalyzers + newAnalyzers.length];
127             for (int i = 0; i < nbAnalyzers; i++) {
128                 analyzers[i] = oldAnalyzers[i];
129             }
130         }
131         // Adds new analyzers
132         for (int i = 0; i < newAnalyzers.length; i++) {
133             analyzers[i + nbAnalyzers] = newAnalyzers[i];
134         }
135     }
136 
137     /***
138      * Returns log analyzers added to the handler with
139      * {@link #setAnalyzers(LAAnalyzer[])}.
140      * 
141      * @return Log analyzers.
142      */
143     public final LAAnalyzer[] getAnalyzers() {
144         return this.analyzers;
145     }
146 
147     /***
148      * Returns pattern converters.
149      * 
150      * @return Pattern converters.
151      */
152     public final LAConverter[] getConverters() {
153         return this.converters;
154     }
155 
156     /***
157      * Returns the converter at the specified index.
158      * 
159      * @param index
160      *            Converter index.
161      * @return Converter or <tt>null</tt> if unknown.
162      */
163     public final LAConverter getConverter(int index) {
164         return this.converters[index];
165     }
166 
167     /***
168      * Returns the converter identified by the specified literal.
169      * 
170      * @param literal
171      *            Converter literal.
172      * @return Converter or <tt>null</tt> if unknown.
173      * @see #getConverter(int)
174      * @see #getConverterIndex(String)
175      * @throws UnknownOrLiteralConverterException
176      *             If the literal does not identify a non literal converter.
177      */
178     public final LAConverter getConverter(String literal)
179             throws UnknownOrLiteralConverterException {
180         return getConverter(getConverterIndex(literal));
181     }
182 
183     /***
184      * Returns the index of the converter identified by the specified literal.
185      * 
186      * @param literal
187      *            Converter literal.
188      * @return Converter or <tt>null</tt> if unknown.
189      * @see #getConverter(int)
190      * @throws UnknownOrLiteralConverterException
191      *             If the literal does not identify a non literal converter.
192      */
193     public final int getConverterIndex(String literal)
194             throws UnknownOrLiteralConverterException {
195         Integer index = (Integer) this.convertersIndex.get(literal);
196         if (index == null) {
197             throw new UnknownOrLiteralConverterException(literal);
198         }
199         return index.intValue();
200     }
201 
202     /***
203      * Returns the current messageLabel converter used to extend messageLabel text with
204      * unparsed lines.
205      * 
206      * @return Message converter.
207      * @see #parse()
208      * @see LAMessage#extendsMessage(String)
209      */
210     public final LAConverter getMessageConverter() {
211         return messageConverter;
212     }
213 
214     /***
215      * Returns the last exception thrown by the parsing process.
216      * 
217      * @return Last exception or <tt>null</tt> parsing was succesfull.
218      */
219     public final Exception getLastException() {
220         return lastException;
221     }
222 
223     /***
224      * Returns the length of the parsed file.
225      * 
226      * @return Length.
227      * @see FilesSetReader#length()
228      */
229     protected final long getFileLength() {
230         long length = 0;
231         if (reader != null) {
232             length = reader.length();
233         }
234         return length;
235     }
236 
237     /***
238      * Returns the current position in the parsed file.
239      * 
240      * @return Pointer.
241      */
242     protected final long getFilePointer() {
243         long pointer = 0;
244         if (reader != null) {
245             try {
246                 pointer = reader.getFilePointer();
247             } catch (IOException e) {
248                 pointer = 0;
249             }
250         }
251         return pointer;
252     }
253 
254     /***
255      * Returns the number of records to parse; <tt>0</tt> for no limitation.
256      * 
257      * @return maxRecords Number of records.
258      */
259     public final int getMaxRecords() {
260         return this.maxRecords;
261     }
262 
263     /***
264      * Number of records to parse; <tt>0</tt> for no limitation.
265      * 
266      * @param maxRecords
267      *            Number of records.
268      */
269     public final void setMaxRecords(int maxRecords) {
270         if (maxRecords < 0) {
271             this.maxRecords = 0;
272         } else {
273             this.maxRecords = maxRecords;
274         }
275     }
276 
277     /***
278      * Returns the done percentage of the parsed file. If the handler is not
279      * parsing, returns <tt>0</tt>.
280      * 
281      * @return Done percentage.
282      */
283     public final int getPercentDone() {
284         int percentage = 0;
285         final int total = 100;
286         if (isParsing()) {
287             if (maxRecords != 0) {
288                 percentage = total * getSize() / maxRecords;
289             } else {
290                 percentage = (int) (total * getFilePointer() / getFileLength());
291             }
292             if (percentage > total) {
293                 percentage = total;
294             }
295         }
296         return percentage;
297     }
298 
299     /***
300      * Returns the messageLabel at the specified position.
301      * 
302      * @param index
303      *            Physical index of the messageLabel in the log file.
304      * @return Message.
305      */
306     public final LAMessage getMessage(int index) {
307         return loadMessage(index);
308     }
309 
310     /***
311      * Returns the messages between specified positions. Uses
312      * {@link #getMessage(int)}to read messageLabel from the log file.
313      * 
314      * @param start
315      *            start physicial index (included in the result).
316      * @param end
317      *            end physical index (included in the result).
318      * @return messages.
319      */
320     public final LAMessage[] getMessages(int start, int end) {
321         int size = getSize();
322         // The start physical indx must be greater or equal than zero
323         if (start < 0) {
324             // Starts at the first messageLabel
325             start = 0;
326         }
327         // The end physical index must be lower than the size of the log.
328         if (end >= size) {
329             // Stops at the last messageLabel
330             end = size - 1;
331         }
332         // An empty interval returns an empty result
333         if (end < start) {
334             return null;
335         }
336         LAMessage[] messages = new LAMessage[end - start];
337         for (int i = start; i <= end; i++) {
338             messages[i - start] = loadMessage(i);
339         }
340         return messages;
341     }
342 
343     /***
344      * Returns the current reader.
345      * 
346      * @return Current reader.
347      */
348     protected final FilesSetReader getReader() {
349         return reader;
350     }
351 
352     /***
353      * Returns the real number of messages in the log, not only in cache in case
354      * of a load on demand parsed log.
355      * 
356      * @return physical number of messages.
357      */
358     public abstract int getSize();
359 
360     /***
361      * Returns <tt>true</tt> if the handler is currently parsing the log.
362      * 
363      * @return <tt>true</tt> if parsing; <tt>false</tt> otherwise.
364      */
365     public final boolean isParsing() {
366         return this.parsing;
367     }
368 
369     /***
370      * Loads the messageLabel at the specified index.
371      * 
372      * @param index
373      *            Index of the messageLabel to load.
374      * @return Message.
375      */
376     protected abstract LAMessage loadMessage(int index);
377 
378     /***
379      * Parses the log content of the {@link FilesSetReader} returned by
380      * {@link #getReader()}. Calls, in order :
381      * <ul>
382      * <li>{@link #initParsing()}</li>
383      * <li>{@link #readNextLine()}</li>
384      * <li>{@link #parseMessage(String, LAConverter[])}</li>
385      * <li>{@link #saveMessage(LAMessage, long)}</li>
386      * <li>{@link LAAnalyzer#analyze(LAMessage, LALogHandler)}on each
387      * {@link LAAnalyzer};</li>
388      * <li>{@link #releaseParsing()}</li>
389      * </ul>
390      * <p>
391      * Because a {@link LALogHandler} is a {@link Runnable}, for a threaded
392      * parsing you must start it in a new {@link Thread} :
393      * 
394      * <pre>
395      * handler.addLogHandlerListener(myLALogHandlerListener);
396      * Thread handlerThread = new Thread(handler);
397      * handlerThread.start();
398      * </pre>
399      * 
400      * </p>
401      * 
402      * @throws ParsingException
403      *             If can't parse messageLabel.
404      * @see LALogHandler#parse()
405      */
406     public final void parse() throws ParsingException {
407         try {
408             // Makes intializations
409             initParsing();
410             // Parses messageLabel
411             String unparsedmessage = null;
412             LAMessage message = null;
413             LAMessage previousMessage = null;
414             int count = 0;
415             long pointer = 0;
416             long previousPointer = 0;
417             // Loop on messages for parsing
418             // count must reach maxRecords to take care of multi-line messages
419             pointer = reader.getFilePointer();
420 
421             int percent = 0;
422             int newPercent = 0;
423             while ((maxRecords <= 0 || count < maxRecords)
424                     && (unparsedmessage = readNextLine()) != null) {
425                 // Read the next lines if needed
426                 String extraLine = null;
427                 for (int i = 1; i < linesToRead
428                         && (extraLine = readNextLine()) != null; i++) {
429                     unparsedmessage += '\n' + extraLine;
430                 }
431                 // Computes percent done and fires event if needed
432                 newPercent = getPercentDone();
433                 if (newPercent - percent >= 1) {
434                     percent = newPercent;
435                     fireStepParsing();
436                 }
437                 // Parses messageLabel
438                 try {
439                     message = parseMessage(unparsedmessage, converters);
440                     // Does not save the messageLabel but the previous to allow
441                     // messageLabel extension when a line content can't be parsed
442                     // (like when an exception stakc is traced).
443                     if (previousMessage != null
444                             && (maxRecords <= 0 || count < maxRecords)) {
445                         // Saves messageLabel only if is not exceeding the max
446                         processMessage(previousMessage, previousPointer);
447                         count++;
448                     }
449                     // A new messageLabel is added
450                     previousMessage = message;
451                     previousPointer = pointer;
452                 } catch (ParsingException pe) {
453                     // The first messageLabel is ignored if it can not be parsed
454                     if (count > 0 && previousMessage != null) {
455                         // If the message can't be parsed, perhaps because the
456                         // messageLabel is multi-line, so the first unparsed line
457                         // must be
458                         // added to the previous messageLabel
459                         reader.seek(pointer);
460                         previousMessage.extendsMessage(readNextLine());
461                     }
462                 }
463                 pointer = reader.getFilePointer();
464             }
465             // If only one messageLabel was read, saves the messageLabel
466             if (previousMessage == null && message != null) {
467                 processMessage(message, pointer);
468             }
469         } catch (Exception e) {
470             lastException = e;
471             throw new ParsingException(e);
472         } finally {
473             // Makes releases
474             try {
475                 releaseParsing();
476             } catch (IOException ioe) {
477                 // NOP
478                 parsing = false;
479             }
480         }
481     }
482 
483     /***
484      * Produces a converter array parsing the specified pattern.
485      * 
486      * @param pattern
487      *            Pattern to parse.
488      * @return Converters resulting to the parsing.
489      */
490     private LAConverter[] parsePattern(String pattern) {
491         try {
492             PatternParser parser = new PatternParser(pattern);
493             LAConverter[] parserConverters = parser.parse();
494             linesToRead = parser.getLinesToRead();
495             return parserConverters;
496         } catch (PatternParsingException ppe) {
497             ppe.printStackTrace(System.err);
498             System.exit(1);
499             return null;
500         }
501     }
502 
503     /***
504      * Apply analyzers on the messageLabel and saves it if required.
505      * 
506      * @param message
507      *            Message to analysis and to save.
508      * @param pointer
509      *            File pointer at the start of the messageLabel.
510      */
511     private void processMessage(LAMessage message, long pointer) {
512         // Applies analyzers
513         if (analyzers != null && analyzers.length > 0) {
514             for (int i = 0; i < analyzers.length; i++) {
515                 analyzers[i].analyze(message, this);
516             }
517         }
518         // Saves messageLabel
519         saveMessage(message, pointer);
520     }
521 
522     /***
523      * Parses a string messageLabel in a {@link LAMessage}using
524      * {@link LAConverter#parse(StringBuffer, LALogHandler)}method.
525      * 
526      * @param message Messate to parse.
527      * @param convertersToUse Converters used to parse the messageLabel.
528      * @return Parsed messageLabel.
529      * @throws ParsingException
530      *             If can't parse messageLabel.
531      */
532     protected final LAMessage parseMessage(String message,
533             LAConverter[] convertersToUse) throws ParsingException {
534         // Creates columns and values
535         Object[] values = new Object[convertersToUse.length];
536 
537         // Parses messageLabel
538         StringBuffer toParse = new StringBuffer(message);
539         for (int i = 0; i < convertersToUse.length; i++) {
540             values[i] = convertersToUse[i].parse(toParse, this);
541         }
542         return new LAMessage(this, values);
543     }
544 
545     /***
546      * Reads a line form the input. Could be called many times by
547      * {@link LAConverter#parse(StringBuffer, LALogHandler)}to read the messageLabel
548      * and the next lines. This is usefull when the messageLabel is written on more
549      * than one line (for example when the result of
550      * {@link Throwable#printStackTrace()}is added to the logged messageLabel}.
551      * 
552      * @return Next line from the handled log.
553      * @throws IOException If an error occures.
554      */
555     protected final String readNextLine() throws IOException {
556         String line = null;
557         if (maxRecords == 0 || getSize() <= maxRecords) {
558             line = reader.readLine();
559         }
560         return line;
561     }
562 
563     /***
564      * Saves the messageLabel after parsing.
565      * 
566      * @param message
567      *            Parsed messageLabel.
568      * @param pointer
569      *            File pointer to the start of the messageLabel.
570      */
571     protected abstract void saveMessage(LAMessage message, long pointer);
572 
573     /***
574      * Makes the intialization before parsing. Calls
575      * {@link LAAnalyzer#initAnalyze(LALogHandler)} on each analyzer. If
576      * overwritten, you must call it with <tt>super.initParsing()</tt>. Calls
577      * {@link #fireStartParsing()}.
578      * @throws IOException If an error occures.
579      */
580     protected void initParsing() throws IOException {
581         // Resets parsing info
582         parsing = true;
583         lastException = null;
584         reader.seek(0);
585         // Initializes analyzers
586         if (analyzers != null && analyzers.length > 0) {
587             for (int i = 0; i < analyzers.length; i++) {
588                 analyzers[i].initAnalyze(this);
589             }
590         }
591         fireStartParsing();
592     }
593 
594     /***
595      * Makes the releases after parsing. Calls
596      * {@link LAAnalyzer#releaseAnalyze(LALogHandler)}on each analyzer.If
597      * overwritten, you must call it with <tt>super.releaseParsing()</tt>.
598      * Calls {@link #fireEndParsing()}.
599      * @throws IOException If an error occures.
600      */
601     protected void releaseParsing() throws IOException {
602         // releases analyzers
603         if (analyzers != null && analyzers.length > 0) {
604             for (int i = 0; i < analyzers.length; i++) {
605                 analyzers[i].releaseAnalyze(this);
606             }
607         }
608         // Sets parsing info before close the reader because closing the reader
609         // might throw an exception.
610         parsing = false;
611         fireEndParsing();
612     }
613 
614     /***
615      * Called by running the handler in a thread. Calls {@link #parse()}.<br>
616      * Example :
617      * 
618      * <pre>
619      * Thread thread = new Thread(handler);
620      * thread.start();
621      * </pre>
622      * 
623      * @see java.lang.Runnable#run()
624      */
625     public final void run() {
626         try {
627             parse();
628         } catch (ParsingException pe) {
629             lastException = pe;
630             // Stop parsing
631             try {
632                 releaseParsing();
633             } catch (IOException ioe) {
634                 parsing = false;
635             }
636         } finally {
637             parsing = false;
638         }
639     }
640 
641     /***
642      * Listeners for log handler events.
643      *  @see LALogHandlerListener
644      */
645     private Vector logHandlerListeners = new Vector();
646 
647     /***
648      * Adds a listener.
649      * 
650      * @param listener
651      *            Lister to add.
652      * @see LALogHandler#addLogHandlerListener(LALogHandlerListener)
653      */
654     public final void addLogHandlerListener(LALogHandlerListener listener) {
655         if (!logHandlerListeners.contains(listener)) {
656             logHandlerListeners.add(listener);
657         }
658     }
659 
660     /***
661      * Removes a listener.
662      * 
663      * @param listener
664      *            Lister to remove.
665      * @see LALogHandler#removeLogHandlerListener(LALogHandlerListener)
666      */
667     public final void removeLogHandlerListener(LALogHandlerListener listener) {
668         logHandlerListeners.remove(listener);
669     }
670 
671     /***
672      * Fires a start parsing event.
673      */
674     public final void fireStartParsing() {
675         for (int i = 0; i < logHandlerListeners.size(); i++) {
676             ((LALogHandlerListener) logHandlerListeners.get(i))
677                     .startParsing(this);
678         }
679     }
680 
681     /***
682      * Fires a step parsing event.
683      */
684     public final void fireStepParsing() {
685         for (int i = 0; i < logHandlerListeners.size(); i++) {
686             ((LALogHandlerListener) logHandlerListeners.get(i))
687                     .stepParsing(this);
688         }
689     }
690 
691     /***
692      * Fires an end parsing event.
693      */
694     public final void fireEndParsing() {
695         for (int i = 0; i < logHandlerListeners.size(); i++) {
696             ((LALogHandlerListener) logHandlerListeners.get(i))
697                     .endParsing(this);
698         }
699     }
700 }