/* * $Id$ * * Copyright 2006, The jCoderZ.org Project. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials * provided with the distribution. * * Neither the name of the jCoderZ.org Project nor the names of * its contributors may be used to endorse or promote products * derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.jcoderz.commons.logging; import java.text.ParseException; import java.util.HashMap; import java.util.Map; import org.apache.commons.pool.ObjectPool; import org.apache.commons.pool.PoolableObjectFactory; import org.apache.commons.pool.impl.StackObjectPool; /** * This class encapsulates the information of a log record being read from the * log file. * Note: This is not thread safe, one instance of this must not be used by * several threads in parallel. * */ public class LogFileEntry extends LogItem { private static final ObjectPool POOL = new StackObjectPool(); /* Sets the current entry level. Always the entry cursor of the root entry should be used .*/ private LogFileEntry mEntryCursor; private LogFileEntry mRootEntry = null; private final Map mFormatMap = new HashMap(); // Flag whether this stack of log file entries has already encountered a // stack trace line. This is set for the root element. private boolean mMetStackTraceLine = false; private final boolean mPooled; private static final class LogFileEntryFactory implements PoolableObjectFactory { /** * Hide default constructor. */ private LogFileEntryFactory () { // nop } /** {@inheritDoc} */ public Object makeObject () { return new LogFileEntry(true); } /** {@inheritDoc} */ public void destroyObject (Object arg0) { // nop } /** {@inheritDoc} */ public void activateObject (Object arg) { // nop } /** * Passivates an instance of LogFileEntry before it is put back into the * pool. * This is indirectly called by entry.release(), so do not call * entry.release() again, but release a nested entry. * * @param arg The LogFileEntry object to passivate. * * @see org.apache.commons.pool.PoolableObjectFactory#passivateObject(java.lang.Object) */ public void passivateObject (final Object arg) { final LogFileEntry entry = (LogFileEntry) arg; final LogFileEntry nested = (LogFileEntry) entry.getNestedItem(); entry.setNestedEntry(null); entry.reset(); if (nested != null) { nested.release(); } } /** * Validates the LogFileEntry instance, there is nothing to check. * * @param arg The LogFileEntry instance to validate. * * @return true * * @see org.apache.commons.pool.PoolableObjectFactory#validateObject(java.lang.Object) */ public boolean validateObject (Object arg) { return true; } } static { POOL.setFactory(new LogFileEntryFactory()); } protected LogFileEntry () { this(false); } /** * Creates a new instance of this. * * @param pooled Flag whether this instance is under pool control. */ private LogFileEntry (final boolean pooled) { mEntryCursor = this; mRootEntry = this; mPooled = pooled; } /** * Gets a new instance of LogFileEntry. The object being returned should * be reset by calling {@linkplain #release()} when it is not needed anymore. * * @return instance of this. Might be newly created or reused. * * @throws LoggingException if an error occurs. */ static LogFileEntry getLogFileEntry () { try { return (LogFileEntry) POOL.borrowObject(); } catch (Exception ex) { throw new LoggingException( "Error retrieving an instance of LogFileEntry", ex); } } /** * Adds a new line being read from the log file to this entry. The line is * parsed and checked whether it belongs to this entry or to a new entry. * * @param logLine The line to add to this. * * @return true, if the line actually belongs to this; false, if it belongs * to a new entry. * * @throws ParseException if an error occurs parsing the line. * @throws Exception if a generic error occurs. */ boolean addLogLine (final StringBuffer logLine) throws ParseException { boolean rc = false; LogLineFormat.LogLineType type = null; if (logLine.length() == 0) { rc = true; } else { type = LogLineFormat.getLogLineType(logLine.charAt(0)); final LogFileEntry entry = findEntry(type); if (entry != null) { entry.handleLogLine(type, logLine); rc = true; } } return rc; } /** * Adds a String containing the string representation of a stacktrace item * to the stacktrace stored by this. The stacktrace item could be a * 'caused by' line, an 'at ...' or an '... nnn more' line. * * @param stackTraceElement The String containing the string representation * of one element of the stack trace. */ void addToStackTrace (final StackTraceInfo stackTraceElement) { if (stackTraceElement.isCauseLine()) { // a nested element must exist in this case if (getNestedItem() == null) { throw new LoggingException("Found a 'caused-by' stack trace line, " + "but have not got a nested element: " + stackTraceElement.toString()); } setCurrentEntry((LogFileEntry) getNestedItem()); } else { getStackTraceLines().add(stackTraceElement); } } private LogFileEntry findEntry (final LogLineFormat.LogLineType type) { LogFileEntry entry = null; if (type != LogLineFormat.STACKTRACE_MESSAGE) { /* if not a stack trace line and his stack has already read such a * line, then it is for a new message. For test messages, where no * stack trace might exist, the type must not be set, otherwise * it is a new message. */ if (! mRootEntry.mMetStackTraceLine && ((getCurrentEntry().getType() == null) || (type != LogLineFormat.TRACE_MESSAGE && type != LogLineFormat.EXCEPTION_MESSAGE && type != LogLineFormat.LOG_MESSAGE && type != LogLineFormat.ERROR_MESSAGE))) { entry = getCurrentEntry(); } } else { if (! mRootEntry.mMetStackTraceLine) { // first stack trace line starts from root entry again. entry = mRootEntry; setCurrentEntry(entry); mRootEntry.mMetStackTraceLine = true; } else { entry = getCurrentEntry(); } } return entry; } private LogLineFormat getFormat (final LogLineFormat.LogLineType type) { LogLineFormat rc = (LogLineFormat) mFormatMap.get(type); if (rc == null) { rc = LogLineFormatFactory.create(type); mFormatMap .put(type, rc); } return rc; } private void handleLogLine ( final LogLineFormat.LogLineType type, final StringBuffer sb) throws ParseException { if ((type == LogLineFormat.TRACE_MESSAGE) || (type == LogLineFormat.LOG_MESSAGE) || (type == LogLineFormat.EXCEPTION_MESSAGE) || (type == LogLineFormat.ERROR_MESSAGE)) { setType(String.valueOf(type.getTypeSpecifier())); } if (type == LogLineFormat.NESTED_MESSAGE) { final LogFileEntry entry = LogFileEntry.getLogFileEntry(); setCurrentEntry(entry); setNestedEntry(entry); getFormat(type).parse(sb, entry); } else { getFormat(type).parse(sb, this); } } /** * Used for resetting this to an initial state so that this object could be * reused again. * Releases the nested entry, if there is such. * * @throws LoggingException if an error occurs. */ public void reset () throws LoggingException { final LogFileEntry nested = (LogFileEntry) getNestedItem(); super.reset(); mEntryCursor = this; mMetStackTraceLine = false; mRootEntry = this; if (nested != null) { nested.release(); } } /** * By calling this a client signals he has finished using this instance. This * should be called for each instance not in use anymore. * Releases the nested entry as well, if there is such. * * @throws LoggingException if an error occurs. */ void release () throws LoggingException { try { reset(); if (mPooled) { POOL.returnObject(this); } } catch (Exception ex) { throw new LoggingException("Error releasing this " + this, ex); } } /** * Sets the supplied entry as nested entry for this and this as parent for * the supplied entry. * * @param nestedEntry The LogFileEntry to set as nested entry for this. */ private void setNestedEntry (final LogFileEntry nestedEntry) { nestedEntry.mRootEntry = this.mRootEntry; setNestedItem(nestedEntry); } private LogFileEntry getCurrentEntry () { return mRootEntry.mEntryCursor; } private void setCurrentEntry (final LogFileEntry entry) { mRootEntry.mEntryCursor = entry; } }