/* * $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.io.PrintWriter; import java.math.BigInteger; import java.util.Calendar; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.TimeZone; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import org.apache.commons.pool.BaseKeyedPoolableObjectFactory; import org.apache.commons.pool.impl.StackKeyedObjectPool; import org.jcoderz.commons.util.Assert; /** * This printer formats the log messages into an Xml format and prints the log * messages as a sequence of xml elements. * */ public final class XmlPrinter extends LogPrinter { private final LogRecordTypesObjectPool mXmlObjectsPool; private final ObjectFactory mObjectFactory = new ObjectFactory(); private final JAXBContext mJaxbContext; private final Marshaller mMarshaller; private static final class LogRecordTypesObjectPool extends StackKeyedObjectPool { private LogRecordType borrowLogRecordType () throws LoggingException { try { return (LogRecordType) borrowObject(LogRecordType.class); } catch (Exception ex) { throw new LoggingException("Error borrowing LogRecordType", ex); } } private LogRecordType borrowLogRecord () throws LoggingException { try { return (LogRecordType) borrowObject(LogRecord.class); } catch (Exception ex) { throw new LoggingException("Error borrowing LogRecord", ex); } } private void returnLogRecord (final LogRecordType record) throws LoggingException { try { returnObject(LogRecordType.class, record); } catch (Exception ex) { throw new LoggingException("Error returning LogRecordType", ex); } } private FrameType borrowFrame () throws LoggingException { try { return (FrameType) borrowObject(FrameType.class); } catch (Exception ex) { throw new LoggingException("Error borrowing FrameType", ex); } } private void returnFrame (final FrameType frame) throws LoggingException { try { returnObject(FrameType.class, frame); } catch (Exception ex) { throw new LoggingException("Error returning FrameType", ex); } } private ParameterType borrowParameter () throws LoggingException { try { return (ParameterType) borrowObject(ParameterType.class); } catch (Exception ex) { throw new LoggingException("Error borrowing ParameterType", ex); } } private void returnParameter (final ParameterType parameter) throws LoggingException { try { returnObject(ParameterType.class, parameter); } catch (Exception ex) { throw new LoggingException("Error returning ParameterType", ex); } } private CauseType borrowCause () throws LoggingException { try { return (CauseType) borrowObject(CauseType.class); } catch (Exception ex) { throw new LoggingException("Error borrowing CauseType", ex); } } private void returnCause (final CauseType cause) throws LoggingException { try { returnObject(CauseType.class, cause); } catch (Exception ex) { throw new LoggingException("Error returning CauseType", ex); } } private ExceptionType borrowException () throws LoggingException { try { return (ExceptionType) borrowObject(ExceptionType.class); } catch (Exception ex) { throw new LoggingException("Error borrowing ExceptionType", ex); } } private void returnException (final ExceptionType ex) throws LoggingException { try { returnObject(ExceptionType.class, ex); } catch (Exception ex1) { throw new LoggingException("Error returning ExceptionType", ex1); } } private StacktraceType borrowStacktrace () throws LoggingException { try { return (StacktraceType) borrowObject(StacktraceType.class); } catch (Exception ex) { throw new LoggingException("Error borrowing StacktraceType", ex); } } private void returnStacktrace (final StacktraceType stacktrace) throws LoggingException { try { returnObject(StacktraceType.class, stacktrace); } catch (Exception ex) { throw new LoggingException("Error returning StacktraceType", ex); } } private Calendar borrowCalendar () throws LoggingException { try { return (Calendar) borrowObject(Calendar.class); } catch (Exception ex) { throw new LoggingException("Error borrowing Calendar", ex); } } private void returnCalendar (final Calendar cal) throws LoggingException { try { returnObject(Calendar.class, cal); } catch (Exception ex) { throw new LoggingException("Error returning Calendar", ex); } } } private static final class XmlObjectFactory extends BaseKeyedPoolableObjectFactory { private final LogRecordTypesObjectPool mPool; private final ObjectFactory mJaxbFactory; private XmlObjectFactory ( final ObjectFactory factory, final LogRecordTypesObjectPool pool) { mPool = pool; mJaxbFactory = factory; } /** * Makes an object for the key given by <code>key</code>. * This accepts as keys classes of the jaxb objects used by this. * * @param key The class of the requested object. * * @return Instance of specified class. * * @see org.apache.commons.pool.BaseKeyedPoolableObjectFactory#makeObject(java.lang.Object) */ public Object makeObject (Object key) throws JAXBException { final Object rc; if (key == LogRecordType.class) { rc = mJaxbFactory.createLogRecordType(); } else if (key == LogRecord.class) { rc = mJaxbFactory.createLogRecord(); } else if (key == FrameType.class) { rc = mJaxbFactory.createFrameType(); } else if (key == StacktraceType.class) { rc = mJaxbFactory.createStacktraceType(); } else if (key == ParameterType.class) { rc = mJaxbFactory.createParameterType(); } else if (key == ExceptionType.class) { rc = mJaxbFactory.createExceptionType(); } else if (key == CauseType.class) { rc = mJaxbFactory.createCauseType(); } else if (key == Calendar.class) { rc = Calendar.getInstance(TimeZone.getTimeZone("UTC")); } else { throw new IllegalArgumentException("Cannot make an object for " + key); } return rc; } /** * Sets all required sub type fields of the supplied object. * * @param key The key of the object to activate. * @param xmlObj The object to activate. * * @see org.apache.commons.pool.KeyedPoolableObjectFactory#activateObject(java.lang.Object, java.lang.Object) */ public void activateObject (Object key, Object xmlObj) throws LoggingException { if ((key == LogRecordType.class) || (key == LogRecord.class)) { final LogRecordType logRecord = (LogRecordType) xmlObj; // set all required sub typed elements // every log record has a source logRecord.setSource(mPool.borrowFrame()); // ... and a timestamp logRecord.setTimestamp(mPool.borrowCalendar()); } else if (key == FrameType.class) { // nop } else if (key == StacktraceType.class) { // nop } else if (key == ParameterType.class) { // nop } else if (key == ExceptionType.class) { // nop } else if (key == CauseType.class) { // nop } else if (key == Calendar.class) { // nop } else { throw new IllegalArgumentException("Cannot make an object for " + key); } } /** * Sets all elements of the object to passivate to null and puts back * into the pool all sub typed objects. * * @param key The key of the object, which is the type of the object. * @param jaxbObj The jaxb object to passivate * * @see org.apache.commons.pool.KeyedPoolableObjectFactory#passivateObject(java.lang.Object, java.lang.Object) */ public void passivateObject (Object key, Object jaxbObj) throws LoggingException { if ((key == LogRecordType.class) || (key == LogRecord.class)) { passivateLogRecord((LogRecordType) jaxbObj); } else if (key == FrameType.class) { passivateFrame((FrameType) jaxbObj); } else if (key == StacktraceType.class) { passivateStackTrace((StacktraceType) jaxbObj); } else if (key == ParameterType.class) { passivateParameter((ParameterType) jaxbObj); } else if (key == ExceptionType.class) { passivateException((ExceptionType) jaxbObj); } else if (key == CauseType.class) { passivateCause((CauseType) jaxbObj); } else if (key == Calendar.class) { // nop } else { throw new IllegalArgumentException("Cannot make an object for " + key); } } /** * Passivates an object of type <code>CauseType</code>. * * @param cause The CauseType object to passivate * * @throws Exception thrown by called methods. */ private void passivateCause (final CauseType cause) throws LoggingException { final ExceptionType ex = cause.getException(); cause.setException(null); final LogRecordType record = cause.getNestedRecord(); cause.setNestedRecord(null); if (ex != null) { mPool.returnException(ex); } if (record != null) { mPool.returnLogRecord(record); } } /** * Passivates an object of type <code>ExceptionType</code>. * * @param ex The ExceptionType object to passivate * * @throws Exception thrown by called methods. */ private void passivateException (final ExceptionType ex) throws LoggingException { ex.setMessage(null); final CauseType cause = ex.getCause(); ex.setCause(null); final StacktraceType stacktrace = ex.getStacktrace(); ex.setStacktrace(null); if (cause != null) { mPool.returnCause(cause); } if (stacktrace != null) { mPool.returnStacktrace(stacktrace); } } /** * Passivates an object of type <code>ParameterType</code>. * * @param parameter The ParameterType object to passivate */ private void passivateParameter (final ParameterType parameter) { parameter.setName(null); parameter.getValue().clear(); } /** * Passivates an object of type <code>StacktraceType</code>. * * @param stacktrace The stacktrace object to passivate * * @throws Exception thrown by called methods. */ private void passivateStackTrace (final StacktraceType stacktrace) throws LoggingException { for (final Iterator iter = stacktrace.getStacktraceElement().iterator(); iter.hasNext(); ) { mPool.returnFrame((FrameType) iter.next()); } stacktrace.getStacktraceElement().clear(); } /** * Passivates an object of type <code>LogRecordType</code>. * * @param logRecord The LogRecordType object to passivate * * @throws Exception thrown by called methods. */ private void passivateLogRecord (final LogRecordType logRecord) throws LoggingException { logRecord.setBusinessImpact(null); logRecord.setCategory(null); logRecord.setInstanceId(null); logRecord.setLevel(null); logRecord.setMessage(null); logRecord.setNodeId(null); logRecord.setSolution(null); logRecord.setSymbol(null); logRecord.setTrackingNumber(null); final Calendar cal = logRecord.getTimestamp(); logRecord.setTimestamp(null); final StacktraceType stack = logRecord.getStacktrace(); logRecord.setStacktrace(null); final FrameType source = logRecord.getSource(); logRecord.setSource(null); final CauseType cause = logRecord.getCause(); logRecord.setCause(null); if (cal != null) { mPool.returnCalendar(cal); } if (stack != null) { mPool.returnStacktrace(stack); } if (source != null) { mPool.returnFrame(source); } if (cause != null) { mPool.returnCause(cause); } for (final Iterator iter = logRecord.getParameter().iterator(); iter.hasNext(); ) { mPool.returnParameter((ParameterType) iter.next()); } logRecord.getParameter().clear(); } /** * Passivates an object of type <code>FrameType</code>. * * @param frame The FrameType object to passivate * * @throws Exception thrown by called methods. */ private void passivateFrame (final FrameType frame) { frame.setSourceMethod(null); frame.setSourceClass(null); frame.setSourceLine(null); } } /** * Creates a new instance of this. Allocates object pools and jaxb resources. * * @throws InstantiationException if an error occurs allocating the * resources. */ public XmlPrinter () throws InstantiationException { super(); try { mJaxbContext = JAXBContext .newInstance("org.jcoderz.commons.logging"); mMarshaller = mJaxbContext.createMarshaller(); mMarshaller.setProperty("jaxb.formatted.output", Boolean.valueOf(true)); } catch (JAXBException ex) { final InstantiationException iex = new InstantiationException("Cannot initialize jaxb resources"); iex.initCause(ex); throw iex; } mXmlObjectsPool = new LogRecordTypesObjectPool(); mXmlObjectsPool.setFactory( new XmlObjectFactory(mObjectFactory, mXmlObjectsPool)); } /** * Prints the log data using the supplied print writer in xml format. * * @param printer The PrintWriter to use for printing the data. * @param logRecord The log data to format into xml and print using * <code>printer</code>. * * @see LogPrinter#print(PrintWriter, LogItem) */ public void print ( final PrintWriter printer, final LogItem logRecord) { try { Assert.notNull(printer, "Printer"); Assert.notNull(logRecord, "entry"); Assert.notNull(logRecord.getType(), "logRecord.getType()"); LogItem logEntry = logRecord; CauseType parentsCause = null; LogRecordType rootRecord = null; while (logEntry != null) { if (! logEntry.isExceptionItem()) { final LogRecordType currentRecord; if (rootRecord == null) { currentRecord = mXmlObjectsPool.borrowLogRecord(); rootRecord = currentRecord; } else { currentRecord = mXmlObjectsPool.borrowLogRecordType(); } fillLogRecord(currentRecord, logEntry); if (parentsCause != null) { parentsCause.setNestedRecord(currentRecord); } logEntry = logEntry.getNestedItem(); parentsCause = setCause(logEntry, currentRecord); } else { final ExceptionType currentException = mXmlObjectsPool.borrowException(); fillException(currentException, logEntry); if (parentsCause != null) { parentsCause.setException(currentException); } logEntry = logEntry.getNestedItem(); parentsCause = setCause(logEntry, currentException); } } if (rootRecord != null) { mMarshaller.marshal(rootRecord, printer); mXmlObjectsPool.returnLogRecord(rootRecord); } } catch (Exception ex) { System.err.println("Error formatting log file entry into xml: " + ex); ex.printStackTrace(); } } private void fillLogRecord ( final LogRecordType logRecord, final LogItem entry) { logRecord.setNodeId(entry.getNodeId()); logRecord.setInstanceId(entry.getInstanceId()); logRecord.setLevel(entry.getLoggerLevel().toString()); logRecord.setSymbol(entry.getSymbol()); logRecord.setSymbolId(entry.getSymbolId()); // the timestamp object is set when the log record is borrowed from the // pool logRecord.getTimestamp().setTime(entry.getTimestamp().toUtilDate()); logRecord.setTrackingNumber(entry.getTrackingNumber()); logRecord.getSource().setSourceClass( entry.getSourceClass()); logRecord.getSource().setSourceMethod( entry.getSourceMethod()); logRecord.setThread(entry.getThreadId()); logRecord.setThreadName(entry.getThreadName()); logRecord.setMessage(entry.getMessage()); logRecord.setSolution(entry.getSolution()); logRecord.setBusinessImpact( entry.getBusinessImpact().toString()); logRecord.setCategory( entry.getCategory().toString()); logRecord.setMessage(entry.getMessage()); final Set params = entry.getParameterNames(); for (final Iterator iter = params.iterator(); iter.hasNext(); ) { final String parameterName = (String) iter.next(); final ParameterType param = mXmlObjectsPool.borrowParameter(); param.setName(parameterName); final List values = entry.getParameterValues(parameterName); if (values != null) { for (final Iterator valueIter = values.iterator(); valueIter.hasNext(); ) { param.getValue().add(valueIter.next().toString()); } } logRecord.getParameter().add(param); } fillStacktrace(logRecord, entry); } /** * Fills the stack trace according to the display options. * * @param logRecord The jaxb log record object to be filled, * @param entry The log entry from which to get the information. */ private void fillStacktrace (LogRecordType logRecord, LogItem entry) { if (displayStackTrace(entry)) { logRecord.setStacktrace(getStackTrace(entry)); } } private void fillException ( final ExceptionType exception, final LogItem entry) { exception.setMessage(entry.getMessage()); if (displayStackTrace(entry)) { exception.setStacktrace(getStackTrace(entry)); } } private StacktraceType getStackTrace (final LogItem entry) { final StacktraceType rc; if (entry.getStackTraceLines().isEmpty()) { rc = null; } else { rc = mXmlObjectsPool.borrowStacktrace(); fillStackTrace(rc, entry); } return rc; } private void fillStackTrace ( final StacktraceType stack, final LogItem entry) { for (final Iterator iter = entry.getStackTraceLines().iterator(); iter.hasNext(); ) { final StackTraceInfo info = (StackTraceInfo) iter.next(); // not interested in lines, which contain the exception message, // this information is stored in other elements already. if (info.isLocationLine()) { final FrameType frame = mXmlObjectsPool.borrowFrame(); frame.setSourceClass(info.getClassName()); frame.setSourceMethod(info.getMethodName()); if (info.getLine() != 0) { frame.setSourceLine(BigInteger.valueOf(info.getLine())); } stack.getStacktraceElement().add(frame); } else if (info.isMoreLine()) { Assert.assertTrue(info + " must be last line of a StackTrace,", ! iter.hasNext()); fillMoreStackTrace(entry, stack, info); } } } /** * This fills the stack trace if a '...nnn more' line has been encountered. * According to the display settings it might be necessary to take the stack * trace lines of parent entries. * * @param entry The log file entry with the current stack trace line. * @param stack The jaxb stack trace object, into which to fill the stack * trace. * @param info The current stack trace info from <code>entry</code>. */ private void fillMoreStackTrace ( final LogItem entry, final StacktraceType stack, final StackTraceInfo info) { final LogItem stackTraceEntry = getEntryForMoreStackTrace(entry, info); if (stackTraceEntry == null) { throw new IllegalStateException("Did not find correct stack trace to " + "display for " + entry); } else if (entry == stackTraceEntry) { // in this case the stack trace has been displayed already and we can // display the more line again. final FrameType frame = mXmlObjectsPool.borrowFrame(); frame.setSourceClass(info.toString()); frame.setSourceMethod(""); stack.getStacktraceElement().add(frame); } else { fillStackTrace(stack, stackTraceEntry); } } private CauseType setCause ( final LogItem entry, final LogRecordType record) { final CauseType rc; if (entry != null) { rc = mXmlObjectsPool.borrowCause(); record.setCause(rc); } else { rc = null; } return rc; } private CauseType setCause ( final LogItem entry, final ExceptionType exception) { final CauseType rc; if (entry != null) { rc = mXmlObjectsPool.borrowCause(); exception.setCause(rc); } else { rc = null; } return rc; } }