001: /*
002: * ====================================================================
003: *
004: * The Apache Software License, Version 1.1
005: *
006: * Copyright (c) 1999-2001 The Apache Software Foundation. All rights
007: * reserved.
008: *
009: * Redistribution and use in source and binary forms, with or without
010: * modification, are permitted provided that the following conditions
011: * are met:
012: *
013: * 1. Redistributions of source code must retain the above copyright
014: * notice, this list of conditions and the following disclaimer.
015: *
016: * 2. Redistributions in binary form must reproduce the above copyright
017: * notice, this list of conditions and the following disclaimer in
018: * the documentation and/or other materials provided with the
019: * distribution.
020: *
021: * 3. The end-user documentation included with the redistribution, if
022: * any, must include the following acknowlegement:
023: * "This product includes software developed by the
024: * Apache Software Foundation (http://www.apache.org/)."
025: * Alternately, this acknowlegement may appear in the software itself,
026: * if and wherever such third-party acknowlegements normally appear.
027: *
028: * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
029: * Foundation" must not be used to endorse or promote products derived
030: * from this software without prior written permission. For written
031: * permission, please contact apache@apache.org.
032: *
033: * 5. Products derived from this software may not be called "Apache"
034: * nor may "Apache" appear in their names without prior written
035: * permission of the Apache Group.
036: *
037: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
038: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
039: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
040: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
041: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
042: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
043: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
044: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
045: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
046: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
047: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
048: * SUCH DAMAGE.
049: * ====================================================================
050: *
051: * This software consists of voluntary contributions made by many
052: * individuals on behalf of the Apache Software Foundation. For more
053: * information on the Apache Software Foundation, please see
054: * <http://www.apache.org/>.
055: *
056: * [Additional notices, if required by prior licensing conditions]
057: *
058: */
059:
060: package org.apache.catalina.valves;
061:
062: import java.io.File;
063: import java.io.FileWriter;
064: import java.io.IOException;
065: import java.io.PrintWriter;
066: import java.text.SimpleDateFormat;
067: import java.util.Date;
068: import java.util.TimeZone;
069: import javax.servlet.ServletException;
070: import javax.servlet.ServletRequest;
071: import javax.servlet.ServletResponse;
072: import javax.servlet.http.HttpServletRequest;
073: import javax.servlet.http.HttpServletResponse;
074: import org.apache.catalina.HttpResponse;
075: import org.apache.catalina.Lifecycle;
076: import org.apache.catalina.LifecycleEvent;
077: import org.apache.catalina.LifecycleException;
078: import org.apache.catalina.LifecycleListener;
079: import org.apache.catalina.Request;
080: import org.apache.catalina.Response;
081: import org.apache.catalina.ValveContext;
082: import org.apache.catalina.util.LifecycleSupport;
083: import org.apache.catalina.util.StringManager;
084:
085: /**
086: * <p>Implementation of the <b>Valve</b> interface that generates a web server
087: * access log with the detailed line contents matching a configurable pattern.
088: * The syntax of the available patterns is similar to that supported by the
089: * Apache <code>mod_log_config</code> module. As an additional feature,
090: * automatic rollover of log files when the date changes is also supported.</p>
091: *
092: * <p>Patterns for the logged message may include constant text or any of the
093: * following replacement strings, for which the corresponding information
094: * from the specified Response is substituted:</p>
095: * <ul>
096: * <li><b>%a</b> - Remote IP address
097: * <li><b>%A</b> - Local IP address
098: * <li><b>%b</b> - Bytes sent, excluding HTTP headers, or '-' if no bytes
099: * were sent
100: * <li><b>%B</b> - Bytes sent, excluding HTTP headers
101: * <li><b>%h</b> - Remote host name
102: * <li><b>%H</b> - Request protocol
103: * <li><b>%l</b> - Remote logical username from identd (always returns '-')
104: * <li><b>%m</b> - Request method
105: * <li><b>%p</b> - Local port
106: * <li><b>%q</b> - Query string (prepended with a '?' if it exists, otherwise
107: * an empty string
108: * <li><b>%r</b> - First line of the request
109: * <li><b>%s</b> - HTTP status code of the response
110: * <li><b>%S</b> - User session ID
111: * <li><b>%t</b> - Date and time, in Common Log Format format
112: * <li><b>%u</b> - Remote user that was authenticated
113: * <li><b>%U</b> - Requested URL path
114: * <li><b>%v</b> - Local server name
115: * </ul>
116: * <p>In addition, the caller can specify one of the following aliases for
117: * commonly utilized patterns:</p>
118: * <ul>
119: * <li><b>common</b> - <code>%h %l %u %t "%r" %s %b</code>
120: * <li><b>combined</b> -
121: * <code>%h %l %u %t "%r" %s %b "%{Referer}i" "%{User-Agent}i"</code>
122: * </ul>
123: *
124: * <p><b>FIXME</b> - Improve the parsing so that things like
125: * <code>%{xxx}i</code> can be implemented.</p>
126: *
127: * @author Craig R. McClanahan
128: * @author Jason Brittain
129: * @version $Revision: 1.14 $ $Date: 2002/06/09 02:19:44 $
130: */
131:
132: public final class AccessLogValve extends ValveBase implements
133: Lifecycle {
134:
135: // ----------------------------------------------------------- Constructors
136:
137: /**
138: * Construct a new instance of this class with default property values.
139: */
140: public AccessLogValve() {
141:
142: super ();
143: setPattern("common");
144:
145: }
146:
147: // ----------------------------------------------------- Instance Variables
148:
149: /**
150: * The as-of date for the currently open log file, or a zero-length
151: * string if there is no open log file.
152: */
153: private String dateStamp = "";
154:
155: /**
156: * The directory in which log files are created.
157: */
158: private String directory = "logs";
159:
160: /**
161: * The descriptive information about this implementation.
162: */
163: protected static final String info = "org.apache.catalina.valves.AccessLogValve/1.0";
164:
165: /**
166: * The lifecycle event support for this component.
167: */
168: protected LifecycleSupport lifecycle = new LifecycleSupport(this );
169:
170: /**
171: * The set of month abbreviations for log messages.
172: */
173: protected static final String months[] = { "Jan", "Feb", "Mar",
174: "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov",
175: "Dec" };
176:
177: /**
178: * If the current log pattern is the same as the common access log
179: * format pattern, then we'll set this variable to true and log in
180: * a more optimal and hard-coded way.
181: */
182: private boolean common = false;
183:
184: /**
185: * For the combined format (common, plus useragent and referer), we do
186: * the same
187: */
188: private boolean combined = false;
189:
190: /**
191: * The pattern used to format our access log lines.
192: */
193: private String pattern = null;
194:
195: /**
196: * The prefix that is added to log file filenames.
197: */
198: private String prefix = "access_log.";
199:
200: /**
201: * The string manager for this package.
202: */
203: private StringManager sm = StringManager
204: .getManager(Constants.Package);
205:
206: /**
207: * Has this component been started yet?
208: */
209: private boolean started = false;
210:
211: /**
212: * The suffix that is added to log file filenames.
213: */
214: private String suffix = "";
215:
216: /**
217: * The PrintWriter to which we are currently logging, if any.
218: */
219: private PrintWriter writer = null;
220:
221: /**
222: * A date formatter to format a Date into a date in the format
223: * "yyyy-MM-dd".
224: */
225: private SimpleDateFormat dateFormatter = null;
226:
227: /**
228: * A date formatter to format Dates into a day string in the format
229: * "dd".
230: */
231: private SimpleDateFormat dayFormatter = null;
232:
233: /**
234: * A date formatter to format a Date into a month string in the format
235: * "MM".
236: */
237: private SimpleDateFormat monthFormatter = null;
238:
239: /**
240: * A date formatter to format a Date into a year string in the format
241: * "yyyy".
242: */
243: private SimpleDateFormat yearFormatter = null;
244:
245: /**
246: * A date formatter to format a Date into a time in the format
247: * "kk:mm:ss" (kk is a 24-hour representation of the hour).
248: */
249: private SimpleDateFormat timeFormatter = null;
250:
251: /**
252: * The time zone relative to GMT.
253: */
254: private String timeZone = null;
255:
256: /**
257: * The system time when we last updated the Date that this valve
258: * uses for log lines.
259: */
260: private Date currentDate = null;
261:
262: /**
263: * When formatting log lines, we often use strings like this one (" ").
264: */
265: private String space = " ";
266:
267: /**
268: * Resolve hosts.
269: */
270: private boolean resolveHosts = false;
271:
272: /**
273: * Instant when the log daily rotation was last checked.
274: */
275: private long rotationLastChecked = 0L;
276:
277: // ------------------------------------------------------------- Properties
278:
279: /**
280: * Return the directory in which we create log files.
281: */
282: public String getDirectory() {
283:
284: return (directory);
285:
286: }
287:
288: /**
289: * Set the directory in which we create log files.
290: *
291: * @param directory The new log file directory
292: */
293: public void setDirectory(String directory) {
294:
295: this .directory = directory;
296:
297: }
298:
299: /**
300: * Return descriptive information about this implementation.
301: */
302: public String getInfo() {
303:
304: return (this .info);
305:
306: }
307:
308: /**
309: * Return the format pattern.
310: */
311: public String getPattern() {
312:
313: return (this .pattern);
314:
315: }
316:
317: /**
318: * Set the format pattern, first translating any recognized alias.
319: *
320: * @param pattern The new pattern
321: */
322: public void setPattern(String pattern) {
323:
324: if (pattern == null)
325: pattern = "";
326: if (pattern.equals(Constants.AccessLog.COMMON_ALIAS))
327: pattern = Constants.AccessLog.COMMON_PATTERN;
328: if (pattern.equals(Constants.AccessLog.COMBINED_ALIAS))
329: pattern = Constants.AccessLog.COMBINED_PATTERN;
330: this .pattern = pattern;
331:
332: if (this .pattern.equals(Constants.AccessLog.COMMON_PATTERN))
333: common = true;
334: else
335: common = false;
336:
337: if (this .pattern.equals(Constants.AccessLog.COMBINED_PATTERN))
338: combined = true;
339: else
340: combined = false;
341:
342: }
343:
344: /**
345: * Return the log file prefix.
346: */
347: public String getPrefix() {
348:
349: return (prefix);
350:
351: }
352:
353: /**
354: * Set the log file prefix.
355: *
356: * @param prefix The new log file prefix
357: */
358: public void setPrefix(String prefix) {
359:
360: this .prefix = prefix;
361:
362: }
363:
364: /**
365: * Return the log file suffix.
366: */
367: public String getSuffix() {
368:
369: return (suffix);
370:
371: }
372:
373: /**
374: * Set the log file suffix.
375: *
376: * @param suffix The new log file suffix
377: */
378: public void setSuffix(String suffix) {
379:
380: this .suffix = suffix;
381:
382: }
383:
384: /**
385: * Set the resolve hosts flag.
386: *
387: * @param resolveHosts The new resolve hosts value
388: */
389: public void setResolveHosts(boolean resolveHosts) {
390:
391: this .resolveHosts = resolveHosts;
392:
393: }
394:
395: /**
396: * Get the value of the resolve hosts flag.
397: */
398: public boolean isResolveHosts() {
399:
400: return resolveHosts;
401:
402: }
403:
404: // --------------------------------------------------------- Public Methods
405:
406: /**
407: * Log a message summarizing the specified request and response, according
408: * to the format specified by the <code>pattern</code> property.
409: *
410: * @param request Request being processed
411: * @param response Response being processed
412: * @param context The valve context used to invoke the next valve
413: * in the current processing pipeline
414: *
415: * @exception IOException if an input/output error has occurred
416: * @exception ServletException if a servlet error has occurred
417: */
418: public void invoke(Request request, Response response,
419: ValveContext context) throws IOException, ServletException {
420:
421: // Pass this request on to the next valve in our pipeline
422: context.invokeNext(request, response);
423:
424: Date date = getDate();
425: StringBuffer result = new StringBuffer();
426:
427: // Check to see if we should log using the "common" access log pattern
428: if (common || combined) {
429: String value = null;
430:
431: ServletRequest req = request.getRequest();
432: HttpServletRequest hreq = null;
433: if (req instanceof HttpServletRequest)
434: hreq = (HttpServletRequest) req;
435:
436: if (isResolveHosts())
437: result.append(req.getRemoteHost());
438: else
439: result.append(req.getRemoteAddr());
440:
441: result.append(" - ");
442:
443: if (hreq != null)
444: value = hreq.getRemoteUser();
445: if (value == null)
446: result.append("- ");
447: else {
448: result.append(value);
449: result.append(space);
450: }
451:
452: result.append("[");
453: result.append(dayFormatter.format(date)); // Day
454: result.append('/');
455: result.append(lookup(monthFormatter.format(date))); // Month
456: result.append('/');
457: result.append(yearFormatter.format(date)); // Year
458: result.append(':');
459: result.append(timeFormatter.format(date)); // Time
460: result.append(space);
461: result.append(timeZone); // Time Zone
462: result.append("] \"");
463:
464: result.append(hreq.getMethod());
465: result.append(space);
466: result.append(hreq.getRequestURI());
467: if (hreq.getQueryString() != null) {
468: result.append('?');
469: result.append(hreq.getQueryString());
470: }
471: result.append(space);
472: result.append(hreq.getProtocol());
473: result.append("\" ");
474:
475: result.append(((HttpResponse) response).getStatus());
476:
477: result.append(space);
478:
479: int length = response.getContentCount();
480:
481: if (length <= 0)
482: value = "-";
483: else
484: value = "" + length;
485: result.append(value);
486:
487: if (combined) {
488: result.append(space);
489: result.append("\"");
490: String referer = hreq.getHeader("referer");
491: if (referer != null)
492: result.append(referer);
493: else
494: result.append("-");
495: result.append("\"");
496:
497: result.append(space);
498: result.append("\"");
499: String ua = hreq.getHeader("user-agent");
500: if (ua != null)
501: result.append(ua);
502: else
503: result.append("-");
504: result.append("\"");
505: }
506:
507: } else {
508: // Generate a message based on the defined pattern
509: boolean replace = false;
510: for (int i = 0; i < pattern.length(); i++) {
511: char ch = pattern.charAt(i);
512: if (replace) {
513: result.append(replace(ch, date, request, response));
514: replace = false;
515: } else if (ch == '%') {
516: replace = true;
517: } else {
518: result.append(ch);
519: }
520: }
521: }
522: log(result.toString(), date);
523:
524: }
525:
526: // -------------------------------------------------------- Private Methods
527:
528: /**
529: * Close the currently open log file (if any)
530: */
531: private synchronized void close() {
532:
533: if (writer == null)
534: return;
535: writer.flush();
536: writer.close();
537: writer = null;
538: dateStamp = "";
539:
540: }
541:
542: /**
543: * Log the specified message to the log file, switching files if the date
544: * has changed since the previous log call.
545: *
546: * @param message Message to be logged
547: * @param date the current Date object (so this method doesn't need to
548: * create a new one)
549: */
550: public void log(String message, Date date) {
551:
552: // Only do a logfile switch check once a second, max.
553: long systime = System.currentTimeMillis();
554: if ((systime - rotationLastChecked) > 1000) {
555:
556: // We need a new currentDate
557: currentDate = new Date(systime);
558: rotationLastChecked = systime;
559:
560: // Check for a change of date
561: String tsDate = dateFormatter.format(currentDate);
562:
563: // If the date has changed, switch log files
564: if (!dateStamp.equals(tsDate)) {
565: synchronized (this ) {
566: if (!dateStamp.equals(tsDate)) {
567: close();
568: dateStamp = tsDate;
569: open();
570: }
571: }
572: }
573:
574: }
575:
576: // Log this message
577: if (writer != null) {
578: writer.println(message);
579: }
580:
581: }
582:
583: /**
584: * Return the month abbreviation for the specified month, which must
585: * be a two-digit String.
586: *
587: * @param month Month number ("01" .. "12").
588: */
589: private String lookup(String month) {
590:
591: int index;
592: try {
593: index = Integer.parseInt(month) - 1;
594: } catch (Throwable t) {
595: index = 0; // Can not happen, in theory
596: }
597: return (months[index]);
598:
599: }
600:
601: /**
602: * Open the new log file for the date specified by <code>dateStamp</code>.
603: */
604: private synchronized void open() {
605:
606: // Create the directory if necessary
607: File dir = new File(directory);
608: if (!dir.isAbsolute())
609: dir = new File(System.getProperty("catalina.base"),
610: directory);
611: dir.mkdirs();
612:
613: // Open the current log file
614: try {
615: String pathname = dir.getAbsolutePath() + File.separator
616: + prefix + dateStamp + suffix;
617: writer = new PrintWriter(new FileWriter(pathname, true),
618: true);
619: } catch (IOException e) {
620: writer = null;
621: }
622:
623: }
624:
625: /**
626: * Return the replacement text for the specified pattern character.
627: *
628: * @param pattern Pattern character identifying the desired text
629: * @param date the current Date so that this method doesn't need to
630: * create one
631: * @param request Request being processed
632: * @param response Response being processed
633: */
634: private String replace(char pattern, Date date, Request request,
635: Response response) {
636:
637: String value = null;
638:
639: ServletRequest req = request.getRequest();
640: HttpServletRequest hreq = null;
641: if (req instanceof HttpServletRequest)
642: hreq = (HttpServletRequest) req;
643: ServletResponse res = response.getResponse();
644: HttpServletResponse hres = null;
645: if (res instanceof HttpServletResponse)
646: hres = (HttpServletResponse) res;
647:
648: if (pattern == 'a') {
649: value = req.getRemoteAddr();
650: } else if (pattern == 'A') {
651: value = "127.0.0.1"; // FIXME
652: } else if (pattern == 'b') {
653: int length = response.getContentCount();
654: if (length <= 0)
655: value = "-";
656: else
657: value = "" + length;
658: } else if (pattern == 'B') {
659: value = "" + response.getContentLength();
660: } else if (pattern == 'h') {
661: value = req.getRemoteHost();
662: } else if (pattern == 'H') {
663: value = req.getProtocol();
664: } else if (pattern == 'l') {
665: value = "-";
666: } else if (pattern == 'm') {
667: if (hreq != null)
668: value = hreq.getMethod();
669: else
670: value = "";
671: } else if (pattern == 'p') {
672: value = "" + req.getServerPort();
673: } else if (pattern == 'q') {
674: String query = null;
675: if (hreq != null)
676: query = hreq.getQueryString();
677: if (query != null)
678: value = "?" + query;
679: else
680: value = "";
681: } else if (pattern == 'r') {
682: StringBuffer sb = new StringBuffer();
683: if (hreq != null) {
684: sb.append(hreq.getMethod());
685: sb.append(space);
686: sb.append(hreq.getRequestURI());
687: if (hreq.getQueryString() != null) {
688: sb.append('?');
689: sb.append(hreq.getQueryString());
690: }
691: sb.append(space);
692: sb.append(hreq.getProtocol());
693: } else {
694: sb.append("- - ");
695: sb.append(req.getProtocol());
696: }
697: value = sb.toString();
698: } else if (pattern == 'S') {
699: if (hreq != null)
700: if (hreq.getSession(false) != null)
701: value = hreq.getSession(false).getId();
702: else
703: value = "-";
704: else
705: value = "-";
706: } else if (pattern == 's') {
707: if (hres != null)
708: value = "" + ((HttpResponse) response).getStatus();
709: else
710: value = "-";
711: } else if (pattern == 't') {
712: StringBuffer temp = new StringBuffer("[");
713: temp.append(dayFormatter.format(date)); // Day
714: temp.append('/');
715: temp.append(lookup(monthFormatter.format(date))); // Month
716: temp.append('/');
717: temp.append(yearFormatter.format(date)); // Year
718: temp.append(':');
719: temp.append(timeFormatter.format(date)); // Time
720: temp.append(' ');
721: temp.append(timeZone); // Timezone
722: temp.append(']');
723: value = temp.toString();
724: } else if (pattern == 'u') {
725: if (hreq != null)
726: value = hreq.getRemoteUser();
727: if (value == null)
728: value = "-";
729: } else if (pattern == 'U') {
730: if (hreq != null)
731: value = hreq.getRequestURI();
732: else
733: value = "-";
734: } else if (pattern == 'v') {
735: value = req.getServerName();
736: } else {
737: value = "???" + pattern + "???";
738: }
739:
740: if (value == null)
741: return ("");
742: else
743: return (value);
744:
745: }
746:
747: /**
748: * This method returns a Date object that is accurate to within one
749: * second. If a thread calls this method to get a Date and it's been
750: * less than 1 second since a new Date was created, this method
751: * simply gives out the same Date again so that the system doesn't
752: * spend time creating Date objects unnecessarily.
753: */
754: private Date getDate() {
755:
756: // Only create a new Date once per second, max.
757: long systime = System.currentTimeMillis();
758: if ((systime - currentDate.getTime()) > 1000) {
759: currentDate = new Date(systime);
760: }
761:
762: return currentDate;
763:
764: }
765:
766: // ------------------------------------------------------ Lifecycle Methods
767:
768: /**
769: * Add a lifecycle event listener to this component.
770: *
771: * @param listener The listener to add
772: */
773: public void addLifecycleListener(LifecycleListener listener) {
774:
775: lifecycle.addLifecycleListener(listener);
776:
777: }
778:
779: /**
780: * Get the lifecycle listeners associated with this lifecycle. If this
781: * Lifecycle has no listeners registered, a zero-length array is returned.
782: */
783: public LifecycleListener[] findLifecycleListeners() {
784:
785: return lifecycle.findLifecycleListeners();
786:
787: }
788:
789: /**
790: * Remove a lifecycle event listener from this component.
791: *
792: * @param listener The listener to add
793: */
794: public void removeLifecycleListener(LifecycleListener listener) {
795:
796: lifecycle.removeLifecycleListener(listener);
797:
798: }
799:
800: /**
801: * Prepare for the beginning of active use of the public methods of this
802: * component. This method should be called after <code>configure()</code>,
803: * and before any of the public methods of the component are utilized.
804: *
805: * @exception LifecycleException if this component detects a fatal error
806: * that prevents this component from being used
807: */
808: public void start() throws LifecycleException {
809:
810: // Validate and update our current component state
811: if (started)
812: throw new LifecycleException(sm
813: .getString("accessLogValve.alreadyStarted"));
814: lifecycle.fireLifecycleEvent(START_EVENT, null);
815: started = true;
816:
817: // Initialize the timeZone, Date formatters, and currentDate
818: TimeZone tz = TimeZone.getDefault();
819: timeZone = "" + (tz.getRawOffset() / (60 * 60 * 10));
820: if (timeZone.length() < 5)
821: timeZone = timeZone.substring(0, 1) + "0"
822: + timeZone.substring(1, timeZone.length());
823: dateFormatter = new SimpleDateFormat("yyyy-MM-dd");
824: dateFormatter.setTimeZone(tz);
825: dayFormatter = new SimpleDateFormat("dd");
826: dayFormatter.setTimeZone(tz);
827: monthFormatter = new SimpleDateFormat("MM");
828: monthFormatter.setTimeZone(tz);
829: yearFormatter = new SimpleDateFormat("yyyy");
830: yearFormatter.setTimeZone(tz);
831: timeFormatter = new SimpleDateFormat("HH:mm:ss");
832: timeFormatter.setTimeZone(tz);
833: currentDate = new Date();
834: dateStamp = dateFormatter.format(currentDate);
835:
836: open();
837:
838: }
839:
840: /**
841: * Gracefully terminate the active use of the public methods of this
842: * component. This method should be the last one called on a given
843: * instance of this component.
844: *
845: * @exception LifecycleException if this component detects a fatal error
846: * that needs to be reported
847: */
848: public void stop() throws LifecycleException {
849:
850: // Validate and update our current component state
851: if (!started)
852: throw new LifecycleException(sm
853: .getString("accessLogValve.notStarted"));
854: lifecycle.fireLifecycleEvent(STOP_EVENT, null);
855: started = false;
856:
857: close();
858:
859: }
860:
861: }
|