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
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
121 analyzers = new LAAnalyzer[newAnalyzers.length];
122 } else {
123
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
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
323 if (start < 0) {
324
325 start = 0;
326 }
327
328 if (end >= size) {
329
330 end = size - 1;
331 }
332
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
409 initParsing();
410
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
418
419 pointer = reader.getFilePointer();
420
421 int percent = 0;
422 int newPercent = 0;
423 while ((maxRecords <= 0 || count < maxRecords)
424 && (unparsedmessage = readNextLine()) != null) {
425
426 String extraLine = null;
427 for (int i = 1; i < linesToRead
428 && (extraLine = readNextLine()) != null; i++) {
429 unparsedmessage += '\n' + extraLine;
430 }
431
432 newPercent = getPercentDone();
433 if (newPercent - percent >= 1) {
434 percent = newPercent;
435 fireStepParsing();
436 }
437
438 try {
439 message = parseMessage(unparsedmessage, converters);
440
441
442
443 if (previousMessage != null
444 && (maxRecords <= 0 || count < maxRecords)) {
445
446 processMessage(previousMessage, previousPointer);
447 count++;
448 }
449
450 previousMessage = message;
451 previousPointer = pointer;
452 } catch (ParsingException pe) {
453
454 if (count > 0 && previousMessage != null) {
455
456
457
458
459 reader.seek(pointer);
460 previousMessage.extendsMessage(readNextLine());
461 }
462 }
463 pointer = reader.getFilePointer();
464 }
465
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
474 try {
475 releaseParsing();
476 } catch (IOException ioe) {
477
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
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
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
535 Object[] values = new Object[convertersToUse.length];
536
537
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
582 parsing = true;
583 lastException = null;
584 reader.seek(0);
585
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
603 if (analyzers != null && analyzers.length > 0) {
604 for (int i = 0; i < analyzers.length; i++) {
605 analyzers[i].releaseAnalyze(this);
606 }
607 }
608
609
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
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 }