moved components to extras
git-svn-id: https://svn.apache.org/repos/asf/logging/log4j/companions/extras/trunk@1342917 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/pom.xml b/pom.xml
index 793aea4..1b1d473 100644
--- a/pom.xml
+++ b/pom.xml
@@ -328,6 +328,20 @@
</plugin>
</plugins>
</build>
+ <dependencies>
+ <dependency>
+ <groupId>hsqldb</groupId>
+ <artifactId>hsqldb</artifactId>
+ <version>1.8.0.7</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.geronimo.specs</groupId>
+ <artifactId>geronimo-jms_1.1_spec</artifactId>
+ <version>1.0</version>
+ <optional>true</optional>
+ </dependency>
+ </dependencies>
<distributionManagement>
<repository>
<id>logging.repo</id>
diff --git a/src/main/java/org/apache/log4j/component/LoggerRepositoryExImpl.java b/src/main/java/org/apache/log4j/component/LoggerRepositoryExImpl.java
new file mode 100644
index 0000000..c2f5aaf
--- /dev/null
+++ b/src/main/java/org/apache/log4j/component/LoggerRepositoryExImpl.java
@@ -0,0 +1,697 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j;
+
+import org.apache.log4j.helpers.LogLog;
+import org.apache.log4j.or.ObjectRenderer;
+import org.apache.log4j.or.RendererMap;
+import org.apache.log4j.plugins.Plugin;
+import org.apache.log4j.plugins.PluginRegistry;
+import org.apache.log4j.scheduler.Scheduler;
+import org.apache.log4j.spi.ErrorItem;
+import org.apache.log4j.spi.HierarchyEventListener;
+import org.apache.log4j.spi.LoggerEventListener;
+import org.apache.log4j.spi.LoggerFactory;
+import org.apache.log4j.spi.LoggerRepository;
+import org.apache.log4j.spi.LoggerRepositoryEventListener;
+import org.apache.log4j.spi.LoggerRepositoryEx;
+import org.apache.log4j.spi.RendererSupport;
+import org.apache.log4j.xml.UnrecognizedElementHandler;
+import org.apache.log4j.xml.DOMConfigurator;
+import org.w3c.dom.Element;
+
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Vector;
+
+
+/**
+ * This class implements LoggerRepositoryEx by
+ * wrapping an existing LoggerRepository implementation
+ * and implementing the newly added capabilities.
+*/
+public final class LoggerRepositoryExImpl
+ implements LoggerRepositoryEx,
+ RendererSupport,
+ UnrecognizedElementHandler {
+
+ /**
+ * Wrapped logger repository.
+ */
+ private final LoggerRepository repo;
+
+ /**
+ * Logger factory. Does not affect class of logger
+ * created by underlying repository.
+ */
+ private LoggerFactory loggerFactory;
+
+ /**
+ * Renderer support.
+ */
+ private final RendererSupport rendererSupport;
+
+ /**
+ * List of repository event listeners.
+ */
+ private final ArrayList repositoryEventListeners = new ArrayList();
+ /**
+ * Map of HierarchyEventListener keyed by LoggingEventListener.
+ */
+ private final Map loggerEventListeners = new HashMap();
+ /**
+ * Name of hierarchy.
+ */
+ private String name;
+ /**
+ * Plug in registry.
+ */
+ private PluginRegistry pluginRegistry;
+ /**
+ * Properties.
+ */
+ private final Map properties = new Hashtable();
+ /**
+ * Scheduler.
+ */
+ private Scheduler scheduler;
+
+ /** The repository can also be used as an object store
+ * for various objects used by log4j components.
+ */
+ private Map objectMap = new HashMap();
+
+
+ /**
+ * Error list.
+ */
+ private List errorList = new Vector();
+
+ /**
+ * True if hierarchy has not been modified.
+ */
+ private boolean pristine = true;
+
+ /**
+ Constructs a new logger hierarchy.
+
+ @param repository Base implementation of repository.
+
+ */
+ public LoggerRepositoryExImpl(final LoggerRepository repository) {
+ super();
+ if (repository == null) {
+ throw new NullPointerException("repository");
+ }
+ repo = repository;
+ if (repository instanceof RendererSupport) {
+ rendererSupport = (RendererSupport) repository;
+ } else {
+ rendererSupport = new RendererSupportImpl();
+ }
+ }
+
+
+ /**
+ Add a {@link LoggerRepositoryEventListener} to the repository. The
+ listener will be called when repository events occur.
+ @param listener listener
+ */
+ public void addLoggerRepositoryEventListener(
+ final LoggerRepositoryEventListener listener) {
+ synchronized (repositoryEventListeners) {
+ if (repositoryEventListeners.contains(listener)) {
+ LogLog.warn(
+ "Ignoring attempt to add a previously "
+ + "registered LoggerRepositoryEventListener.");
+ } else {
+ repositoryEventListeners.add(listener);
+ }
+ }
+ }
+
+
+ /**
+ Remove a {@link LoggerRepositoryEventListener} from the repository.
+ @param listener listener
+ */
+ public void removeLoggerRepositoryEventListener(
+ final LoggerRepositoryEventListener listener) {
+ synchronized (repositoryEventListeners) {
+ if (!repositoryEventListeners.contains(listener)) {
+ LogLog.warn(
+ "Ignoring attempt to remove a "
+ + "non-registered LoggerRepositoryEventListener.");
+ } else {
+ repositoryEventListeners.remove(listener);
+ }
+ }
+ }
+
+ /**
+ Add a {@link LoggerEventListener} to the repository. The listener
+ will be called when repository events occur.
+ @param listener listener
+ */
+ public void addLoggerEventListener(final LoggerEventListener listener) {
+ synchronized (loggerEventListeners) {
+ if (loggerEventListeners.get(listener) != null) {
+ LogLog.warn(
+ "Ignoring attempt to add a previously registerd LoggerEventListener.");
+ } else {
+ HierarchyEventListenerProxy proxy =
+ new HierarchyEventListenerProxy(listener);
+ loggerEventListeners.put(listener, proxy);
+ repo.addHierarchyEventListener(proxy);
+ }
+ }
+ }
+
+ /**
+ Add a {@link org.apache.log4j.spi.HierarchyEventListener}
+ event to the repository.
+ @param listener listener
+ @deprecated Superceded by addLoggerEventListener
+ */
+ public
+ void addHierarchyEventListener(final HierarchyEventListener listener) {
+ repo.addHierarchyEventListener(listener);
+ }
+
+
+ /**
+ Remove a {@link LoggerEventListener} from the repository.
+ @param listener listener to be removed
+ */
+ public void removeLoggerEventListener(final LoggerEventListener listener) {
+ synchronized (loggerEventListeners) {
+ HierarchyEventListenerProxy proxy =
+ (HierarchyEventListenerProxy) loggerEventListeners.get(listener);
+ if (proxy == null) {
+ LogLog.warn(
+ "Ignoring attempt to remove a non-registered LoggerEventListener.");
+ } else {
+ loggerEventListeners.remove(listener);
+ proxy.disable();
+ }
+ }
+ }
+
+ /**
+ * Issue warning that there are no appenders in hierarchy.
+ * @param cat logger, not currently used.
+ */
+ public void emitNoAppenderWarning(final Category cat) {
+ repo.emitNoAppenderWarning(cat);
+ }
+
+ /**
+ Check if the named logger exists in the hierarchy. If so return
+ its reference, otherwise returns <code>null</code>.
+
+ @param loggerName The name of the logger to search for.
+ @return true if logger exists.
+ */
+ public Logger exists(final String loggerName) {
+ return repo.exists(loggerName);
+ }
+
+ /**
+ * Return the name of this hierarchy.
+ * @return name of hierarchy
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Set the name of this repository.
+ *
+ * Note that once named, a repository cannot be rerenamed.
+ * @param repoName name of hierarchy
+ */
+ public void setName(final String repoName) {
+ if (name == null) {
+ name = repoName;
+ } else if (!name.equals(repoName)) {
+ throw new IllegalStateException(
+ "Repository [" + name + "] cannot be renamed as [" + repoName + "].");
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Map getProperties() {
+ return properties;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getProperty(final String key) {
+ return (String) properties.get(key);
+ }
+
+ /**
+ * Set a property by key and value. The property will be shared by all
+ * events in this repository.
+ * @param key property name
+ * @param value property value
+ */
+ public void setProperty(final String key,
+ final String value) {
+ properties.put(key, value);
+ }
+
+ /**
+ The string form of {@link #setThreshold(Level)}.
+ @param levelStr symbolic name for level
+ */
+ public void setThreshold(final String levelStr) {
+ repo.setThreshold(levelStr);
+ }
+
+ /**
+ Enable logging for logging requests with level <code>l</code> or
+ higher. By default all levels are enabled.
+
+ @param l The minimum level for which logging requests are sent to
+ their appenders. */
+ public void setThreshold(final Level l) {
+ repo.setThreshold(l);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public PluginRegistry getPluginRegistry() {
+ if (pluginRegistry == null) {
+ pluginRegistry = new PluginRegistry(this);
+ }
+ return pluginRegistry;
+ }
+
+
+ /**
+ Requests that a appender added event be sent to any registered
+ {@link LoggerEventListener}.
+ @param logger The logger to which the appender was added.
+ @param appender The appender added to the logger.
+ */
+ public void fireAddAppenderEvent(final Category logger,
+ final Appender appender) {
+ repo.fireAddAppenderEvent(logger, appender);
+ }
+
+
+ /**
+ Requests that a appender removed event be sent to any registered
+ {@link LoggerEventListener}.
+ @param logger The logger from which the appender was removed.
+ @param appender The appender removed from the logger.
+ */
+ public void fireRemoveAppenderEvent(final Category logger,
+ final Appender appender) {
+ if (repo instanceof Hierarchy) {
+ ((Hierarchy) repo).fireRemoveAppenderEvent(logger, appender);
+ }
+ }
+
+
+ /**
+ Requests that a level changed event be sent to any registered
+ {@link LoggerEventListener}.
+ @param logger The logger which changed levels.
+ */
+ public void fireLevelChangedEvent(final Logger logger) {
+ }
+
+ /**
+ *
+ * Requests that a configuration changed event be sent to any registered
+ * {@link LoggerRepositoryEventListener}.
+ *
+ */
+ public void fireConfigurationChangedEvent() {
+ }
+
+
+ /**
+ Returns the current threshold.
+ @return current threshold level
+
+ @since 1.2 */
+ public Level getThreshold() {
+ return repo.getThreshold();
+ }
+
+
+ /**
+ Return a new logger instance named as the first parameter using
+ the default factory.
+
+ <p>If a logger of that name already exists, then it will be
+ returned. Otherwise, a new logger will be instantiated and
+ then linked with its existing ancestors as well as children.
+
+ @param loggerName The name of the logger to retrieve.
+ @return logger
+
+ */
+ public Logger getLogger(final String loggerName) {
+ return repo.getLogger(loggerName);
+ }
+
+ /**
+ Return a new logger instance named as the first parameter using
+ <code>factory</code>.
+
+ <p>If a logger of that name already exists, then it will be
+ returned. Otherwise, a new logger will be instantiated by the
+ <code>factory</code> parameter and linked with its existing
+ ancestors as well as children.
+
+ @param loggerName The name of the logger to retrieve.
+ @param factory The factory that will make the new logger instance.
+ @return logger
+
+ */
+ public Logger getLogger(final String loggerName,
+ final LoggerFactory factory) {
+ return repo.getLogger(loggerName, factory);
+ }
+
+ /**
+ Returns all the currently defined categories in this hierarchy as
+ an {@link java.util.Enumeration Enumeration}.
+
+ <p>The root logger is <em>not</em> included in the returned
+ {@link Enumeration}.
+ @return enumerator of current loggers
+ */
+ public Enumeration getCurrentLoggers() {
+ return repo.getCurrentLoggers();
+ }
+
+ /**
+ * Return the the list of previously encoutered {@link ErrorItem error items}.
+ * @return list of errors
+ */
+ public List getErrorList() {
+ return errorList;
+ }
+
+ /**
+ * Add an error item to the list of previously encountered errors.
+ * @param errorItem error to add to list of errors.
+ */
+ public void addErrorItem(final ErrorItem errorItem) {
+ getErrorList().add(errorItem);
+ }
+
+ /**
+ * Get enumerator over current loggers.
+ * @return enumerator over current loggers
+ @deprecated Please use {@link #getCurrentLoggers} instead.
+ */
+ public Enumeration getCurrentCategories() {
+ return repo.getCurrentCategories();
+ }
+
+ /**
+ Get the renderer map for this hierarchy.
+ @return renderer map
+ */
+ public RendererMap getRendererMap() {
+ return rendererSupport.getRendererMap();
+ }
+
+ /**
+ Get the root of this hierarchy.
+
+ @since 0.9.0
+ @return root of hierarchy
+ */
+ public Logger getRootLogger() {
+ return repo.getRootLogger();
+ }
+
+ /**
+ This method will return <code>true</code> if this repository is
+ disabled for <code>level</code> value passed as parameter and
+ <code>false</code> otherwise. See also the {@link
+ #setThreshold(Level) threshold} method.
+ @param level numeric value for level.
+ @return true if disabled for specified level
+ */
+ public boolean isDisabled(final int level) {
+ return repo.isDisabled(level);
+ }
+
+ /**
+ Reset all values contained in this hierarchy instance to their
+ default. This removes all appenders from all categories, sets
+ the level of all non-root categories to <code>null</code>,
+ sets their additivity flag to <code>true</code> and sets the level
+ of the root logger to DEBUG. Moreover,
+ message disabling is set its default "off" value.
+
+ <p>Existing categories are not removed. They are just reset.
+
+ <p>This method should be used sparingly and with care as it will
+ block all logging until it is completed.</p>
+
+ @since 0.8.5 */
+ public void resetConfiguration() {
+ repo.resetConfiguration();
+ }
+
+ /**
+ Used by subclasses to add a renderer to the hierarchy passed as parameter.
+ @param renderedClass class
+ @param renderer object used to render class.
+ */
+ public void setRenderer(final Class renderedClass,
+ final ObjectRenderer renderer) {
+ rendererSupport.setRenderer(renderedClass, renderer);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean isPristine() {
+ return pristine;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setPristine(final boolean state) {
+ pristine = state;
+ }
+
+ /**
+ Shutting down a hierarchy will <em>safely</em> close and remove
+ all appenders in all categories including the root logger.
+
+ <p>Some appenders such as org.apache.log4j.net.SocketAppender
+ and AsyncAppender need to be closed before the
+ application exists. Otherwise, pending logging events might be
+ lost.
+
+ <p>The <code>shutdown</code> method is careful to close nested
+ appenders before closing regular appenders. This is allows
+ configurations where a regular appender is attached to a logger
+ and again to a nested appender.
+
+ @since 1.0 */
+ public void shutdown() {
+ repo.shutdown();
+ }
+
+
+ /**
+ * Return this repository's own scheduler.
+ * The scheduler is lazily instantiated.
+ * @return this repository's own scheduler.
+ */
+ public Scheduler getScheduler() {
+ if (scheduler == null) {
+ scheduler = new Scheduler();
+ scheduler.setDaemon(true);
+ scheduler.start();
+ }
+ return scheduler;
+ }
+
+ /**
+ * Puts object by key.
+ * @param key key, may not be null.
+ * @param value object to associate with key.
+ */
+ public void putObject(final String key,
+ final Object value) {
+ objectMap.put(key, value);
+ }
+
+ /**
+ * Get object by key.
+ * @param key key, may not be null.
+ * @return object associated with key or null.
+ */
+ public Object getObject(final String key) {
+ return objectMap.get(key);
+ }
+
+ /**
+ * Set logger factory.
+ * @param factory logger factory.
+ */
+ public void setLoggerFactory(final LoggerFactory factory) {
+ if (factory == null) {
+ throw new NullPointerException();
+ }
+ this.loggerFactory = factory;
+ }
+
+ /**
+ * Get logger factory.
+ * @return logger factory.
+ */
+ public LoggerFactory getLoggerFactory() {
+ return loggerFactory;
+ }
+
+ /** {@inheritDoc} */
+ public boolean parseUnrecognizedElement(
+ final Element element,
+ final Properties props) throws Exception {
+ if ("plugin".equals(element.getNodeName())) {
+ Object instance =
+ DOMConfigurator.parseElement(element, props, Plugin.class);
+ if (instance instanceof Plugin) {
+ Plugin plugin = (Plugin) instance;
+ String pluginName = DOMConfigurator.subst(element.getAttribute("name"), props);
+ if (pluginName.length() > 0) {
+ plugin.setName(pluginName);
+ }
+ getPluginRegistry().addPlugin(plugin);
+ plugin.setLoggerRepository(this);
+
+ LogLog.debug("Pushing plugin on to the object stack.");
+ plugin.activateOptions();
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+
+ /**
+ * Implementation of RendererSupportImpl if not
+ * provided by LoggerRepository.
+ */
+ private static final class RendererSupportImpl implements RendererSupport {
+ /**
+ * Renderer map.
+ */
+ private final RendererMap renderers = new RendererMap();
+
+ /**
+ * Create new instance.
+ */
+ public RendererSupportImpl() {
+ super();
+ }
+
+ /** {@inheritDoc} */
+ public RendererMap getRendererMap() {
+ return renderers;
+ }
+
+ /** {@inheritDoc} */
+ public void setRenderer(final Class renderedClass,
+ final ObjectRenderer renderer) {
+ renderers.put(renderedClass, renderer);
+ }
+ }
+
+ /**
+ * Proxy that implements HierarchyEventListener
+ * and delegates to LoggerEventListener.
+ */
+ private static final class HierarchyEventListenerProxy
+ implements HierarchyEventListener {
+ /**
+ * Wrapper listener.
+ */
+ private LoggerEventListener listener;
+
+ /**
+ * Creates new instance.
+ * @param l listener
+ */
+ public HierarchyEventListenerProxy(final LoggerEventListener l) {
+ super();
+ if (l == null) {
+ throw new NullPointerException("l");
+ }
+ listener = l;
+ }
+
+ /** {@inheritDoc} */
+ public void addAppenderEvent(final Category cat,
+ final Appender appender) {
+ if (isEnabled() && cat instanceof Logger) {
+ listener.appenderAddedEvent((Logger) cat, appender);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void removeAppenderEvent(final Category cat,
+ final Appender appender) {
+ if (isEnabled() && cat instanceof Logger) {
+ listener.appenderRemovedEvent((Logger) cat, appender);
+ }
+ }
+
+ /**
+ * Disable forwarding of notifications to
+ * simulate removal of listener.
+ */
+ public synchronized void disable() {
+ listener = null;
+ }
+
+ /**
+ * Gets whether proxy is enabled.
+ * @return true if proxy is enabled.
+ */
+ private synchronized boolean isEnabled() {
+ return listener != null;
+ }
+ }
+
+}
diff --git a/src/main/java/org/apache/log4j/component/ULogger.java b/src/main/java/org/apache/log4j/component/ULogger.java
new file mode 100644
index 0000000..f3bf1d1
--- /dev/null
+++ b/src/main/java/org/apache/log4j/component/ULogger.java
@@ -0,0 +1,203 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.component;
+
+
+/**
+ * A proxy for org.slf4j.ULogger. In slf4j implementing builds, this
+ * interface will extend org.slf4j.ULogger and add no additional methods.
+ *
+ * @author Ceki Gülcü
+ * @author Curt Arnold
+ */
+ public interface ULogger {
+
+
+ /**
+ * Is the logger instance enabled for the DEBUG level?
+ * @return true if debug is enabled.
+ */
+ boolean isDebugEnabled();
+
+ /**
+ * Log a message object with the DEBUG level.
+ * @param msg - the message object to be logged
+ */
+ void debug(Object msg);
+
+
+ /**
+ * Log a parameterized message object at the DEBUG level.
+ *
+ * <p>This form is useful in avoiding the superflous object creation
+ * problem when invoking this method while it is disabled.
+ * </p>
+ * @param parameterizedMsg - the parameterized message object
+ * @param param1 - the parameter
+ */
+ void debug(Object parameterizedMsg, Object param1);
+
+ /**
+ * Log a parameterized message object at the DEBUG level.
+ *
+ * <p>This form is useful in avoiding the superflous object creation
+ * problem when invoking this method while it is disabled.
+ * </p>
+ * @param parameterizedMsg - the parameterized message object
+ * @param param1 - the first parameter
+ * @param param2 - the second parameter
+ */
+ void debug(String parameterizedMsg, Object param1, Object param2);
+ /**
+ * Log a message object with the <code>DEBUG</code> level including the
+ * stack trace of the {@link Throwable}<code>t</code> passed as parameter.
+ *
+ *
+ * @param msg the message object to log.
+ * @param t the exception to log, including its stack trace.
+ */
+ void debug(Object msg, Throwable t);
+
+
+ /**
+ * Is the logger instance enabled for the INFO level?
+ * @return true if debug is enabled.
+ */
+ boolean isInfoEnabled();
+ /**
+ * Log a message object with the INFO level.
+ * @param msg - the message object to be logged
+ */
+ void info(Object msg);
+ /**
+ * Log a parameterized message object at the INFO level.
+ *
+ * <p>This form is useful in avoiding the superflous object creation
+ * problem when invoking this method while it is disabled.
+ * </p>
+ * @param parameterizedMsg - the parameterized message object
+ * @param param1 - the parameter
+ */
+ void info(Object parameterizedMsg, Object param1);
+ /**
+ * Log a parameterized message object at the INFO level.
+ *
+ * <p>This form is useful in avoiding the superflous object creation
+ * problem when invoking this method while it is disabled.
+ * </p>
+ * @param parameterizedMsg - the parameterized message object
+ * @param param1 - the first parameter
+ * @param param2 - the second parameter
+ */
+ void info(String parameterizedMsg, Object param1, Object param2);
+ /**
+ * Log a message object with the <code>INFO</code> level including the
+ * stack trace of the {@link Throwable}<code>t</code> passed as parameter.
+ *
+ *
+ * @param msg the message object to log.
+ * @param t the exception to log, including its stack trace.
+ */
+ void info(Object msg, Throwable t);
+
+
+ /**
+ * Is the logger instance enabled for the WARN level?
+ * @return true if debug is enabled.
+ */
+ boolean isWarnEnabled();
+ /**
+ * Log a message object with the WARN level.
+ * @param msg - the message object to be logged
+ */
+ void warn(Object msg);
+ /**
+ * Log a parameterized message object at the WARN level.
+ *
+ * <p>This form is useful in avoiding the superflous object creation
+ * problem when invoking this method while it is disabled.
+ * </p>
+ * @param parameterizedMsg - the parameterized message object
+ * @param param1 - the parameter
+ */
+ void warn(Object parameterizedMsg, Object param1);
+ /**
+ * Log a parameterized message object at the WARN level.
+ *
+ * <p>This form is useful in avoiding the superflous object creation
+ * problem when invoking this method while it is disabled.
+ * </p>
+ * @param parameterizedMsg - the parameterized message object
+ * @param param1 - the first parameter
+ * @param param2 - the second parameter
+ */
+ void warn(String parameterizedMsg, Object param1, Object param2);
+ /**
+ * Log a message object with the <code>WARN</code> level including the
+ * stack trace of the {@link Throwable}<code>t</code> passed as parameter.
+ *
+ *
+ * @param msg the message object to log.
+ * @param t the exception to log, including its stack trace.
+ */
+ void warn(Object msg, Throwable t);
+
+
+ /**
+ * Is the logger instance enabled for the ERROR level?
+ * @return true if debug is enabled.
+ */
+ boolean isErrorEnabled();
+ /**
+ * Log a message object with the ERROR level.
+ * @param msg - the message object to be logged
+ */
+ void error(Object msg);
+ /**
+ * Log a parameterized message object at the ERROR level.
+ *
+ * <p>This form is useful in avoiding the superflous object creation
+ * problem when invoking this method while it is disabled.
+ * </p>
+ * @param parameterizedMsg - the parameterized message object
+ * @param param1 - the parameter
+ */
+ void error(Object parameterizedMsg, Object param1);
+ /**
+ * Log a parameterized message object at the ERROR level.
+ *
+ * <p>This form is useful in avoiding the superflous object creation
+ * problem when invoking this method while it is disabled.
+ * </p>
+ * @param parameterizedMsg - the parameterized message object
+ * @param param1 - the first parameter
+ * @param param2 - the second parameter
+ */
+ void error(String parameterizedMsg, Object param1, Object param2);
+
+ /**
+ * Log a message object with the <code>ERROR</code> level including the
+ * stack trace of the {@link Throwable}<code>t</code> passed as parameter.
+ *
+ *
+ * @param msg the message object to log.
+ * @param t the exception to log, including its stack trace.
+ */
+ void error(Object msg, Throwable t);
+
+}
diff --git a/src/main/java/org/apache/log4j/component/helpers/Constants.java b/src/main/java/org/apache/log4j/component/helpers/Constants.java
new file mode 100644
index 0000000..53946b0
--- /dev/null
+++ b/src/main/java/org/apache/log4j/component/helpers/Constants.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.component.helpers;
+
+
+/**
+ * Constants used internally throughout log4j.
+ *
+ */
+public interface Constants {
+
+ /**
+ * log4j package name string literal.
+ */
+ String LOG4J_PACKAGE_NAME = "org.apache.log4j";
+
+ /**
+ * The name of the default repository is "default" (without the quotes).
+ */
+ String DEFAULT_REPOSITORY_NAME = "default";
+
+ /**
+ * application string literal.
+ */
+ String APPLICATION_KEY = "application";
+ /**
+ * hostname string literal.
+ */
+ String HOSTNAME_KEY = "hostname";
+ /**
+ * receiver string literal.
+ */
+ String RECEIVER_NAME_KEY = "receiver";
+ /**
+ * log4jid string literal.
+ */
+ String LOG4J_ID_KEY = "log4jid";
+ /**
+ * time stamp pattern string literal.
+ */
+ String TIMESTAMP_RULE_FORMAT = "yyyy/MM/dd HH:mm:ss";
+
+ /**
+ * The default property file name for automatic configuration.
+ */
+ String DEFAULT_CONFIGURATION_FILE = "log4j.properties";
+ /**
+ * The default XML configuration file name for automatic configuration.
+ */
+ String DEFAULT_XML_CONFIGURATION_FILE = "log4j.xml";
+ /**
+ * log4j.configuration string literal.
+ */
+ String DEFAULT_CONFIGURATION_KEY = "log4j.configuration";
+ /**
+ * log4j.configuratorClass string literal.
+ */
+ String CONFIGURATOR_CLASS_KEY = "log4j.configuratorClass";
+
+ /**
+ * JNDI context name string literal.
+ */
+ String JNDI_CONTEXT_NAME = "java:comp/env/log4j/context-name";
+
+ /**
+ * TEMP_LIST_APPENDER string literal.
+ */
+ String TEMP_LIST_APPENDER_NAME = "TEMP_LIST_APPENDER";
+ /**
+ * TEMP_CONSOLE_APPENDER string literal.
+ */
+ String TEMP_CONSOLE_APPENDER_NAME = "TEMP_CONSOLE_APPENDER";
+ /**
+ * Codes URL string literal.
+ */
+ String CODES_HREF =
+ "http://logging.apache.org/log4j/docs/codes.html";
+
+
+ /**
+ * ABSOLUTE string literal.
+ */
+ String ABSOLUTE_FORMAT = "ABSOLUTE";
+ /**
+ * SimpleTimePattern for ABSOLUTE.
+ */
+ String ABSOLUTE_TIME_PATTERN = "HH:mm:ss,SSS";
+
+ /**
+ * SimpleTimePattern for ABSOLUTE.
+ */
+ String SIMPLE_TIME_PATTERN = "HH:mm:ss";
+
+ /**
+ * DATE string literal.
+ */
+ String DATE_AND_TIME_FORMAT = "DATE";
+ /**
+ * SimpleTimePattern for DATE.
+ */
+ String DATE_AND_TIME_PATTERN = "dd MMM yyyy HH:mm:ss,SSS";
+
+ /**
+ * ISO8601 string literal.
+ */
+ String ISO8601_FORMAT = "ISO8601";
+ /**
+ * SimpleTimePattern for ISO8601.
+ */
+ String ISO8601_PATTERN = "yyyy-MM-dd HH:mm:ss,SSS";
+}
diff --git a/src/main/java/org/apache/log4j/component/helpers/MessageFormatter.java b/src/main/java/org/apache/log4j/component/helpers/MessageFormatter.java
new file mode 100644
index 0000000..cf84a97
--- /dev/null
+++ b/src/main/java/org/apache/log4j/component/helpers/MessageFormatter.java
@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.component.helpers;
+
+
+/**
+ * Formats messages according to very simple rules.
+ * See {@link #format(String,Object)} and
+ * {@link #format(String,Object,Object)} for more details.
+ *
+ * @author Ceki Gülcü
+ */
+public final class MessageFormatter {
+ /**
+ * Private formatter since all methods and members are static.
+ */
+ private MessageFormatter() {
+ super();
+ }
+
+ /**
+ * Start of replacement block.
+ */
+ private static final char DELIM_START = '{';
+ /**
+ * End of replacement block.
+ */
+ private static final char DELIM_STOP = '}';
+
+ /**
+ * Performs single argument substitution for the 'messagePattern' passed as
+ * parameter.
+ * <p/>
+ * For example, <code>MessageFormatter.format("Hi {}.", "there");</code>
+ * will return the string "Hi there.".
+ * <p/>
+ * The {} pair is called the formatting element. It serves to designate the
+ * location where the argument needs to be inserted within the pattern.
+ *
+ * @param messagePattern
+ * The message pattern which will be parsed and formatted
+ * @param argument
+ * The argument to be inserted instead of the formatting element
+ * @return The formatted message
+ */
+ public static String format(final String messagePattern,
+ final Object argument) {
+ int j = messagePattern.indexOf(DELIM_START);
+ int len = messagePattern.length();
+ char escape = 'x';
+
+ // if there are no { characters or { is the last character
+ // then we just return messagePattern
+ if (j == -1 || (j + 1 == len)) {
+ return messagePattern;
+ } else {
+ char delimStop = messagePattern.charAt(j + 1);
+ if (j > 0) {
+ escape = messagePattern.charAt(j - 1);
+ }
+ if ((delimStop != DELIM_STOP) || (escape == '\\')) {
+ // invalid DELIM_START/DELIM_STOP pair or espace character is
+ // present
+ return messagePattern;
+ } else {
+ StringBuffer sbuf = new StringBuffer(len + 20);
+ sbuf.append(messagePattern.substring(0, j));
+ sbuf.append(argument);
+ sbuf.append(messagePattern.substring(j + 2));
+ return sbuf.toString();
+ }
+ }
+ }
+
+ /**
+ * /**
+ * Performs a two argument substitution for the 'messagePattern' passed as
+ * parameter.
+ * <p/>
+ * For example, <code>MessageFormatter.format("Hi {}. My name is {}.",
+ * "there", "David");</code> will return the string
+ * "Hi there. My name is David.".
+ * <p/>
+ * The '{}' pair is called a formatting element. It serves to designate the
+ * location where the arguments need to be inserted within
+ * the message pattern.
+ *
+ * @param messagePattern
+ * The message pattern which will be parsed and formatted
+ * @param arg1
+ * The first argument to replace the first formatting element
+ * @param arg2
+ * The second argument to replace the second formatting element
+ * @return The formatted message
+ */
+ public static String format(final String messagePattern,
+ final Object arg1,
+ final Object arg2) {
+ int i = 0;
+ int len = messagePattern.length();
+
+ StringBuffer sbuf = new StringBuffer(messagePattern.length() + 50);
+
+ for (int l = 0; l < 2; l++) {
+ int j = messagePattern.indexOf(DELIM_START, i);
+
+ if (j == -1 || (j + 1 == len)) {
+ // no more variables
+ if (i == 0) { // this is a simple string
+ return messagePattern;
+ } else {
+ // add the tail string which contains no variables
+ // and return the result.
+ sbuf.append(messagePattern.substring(i,
+ messagePattern.length()));
+ return sbuf.toString();
+ }
+ } else {
+ char delimStop = messagePattern.charAt(j + 1);
+ if ((delimStop != DELIM_STOP)) {
+ // invalid DELIM_START/DELIM_STOP pair
+ sbuf.append(messagePattern.substring(i,
+ messagePattern.length()));
+ return sbuf.toString();
+ }
+ sbuf.append(messagePattern.substring(i, j));
+ if (l == 0) {
+ sbuf.append(arg1);
+ } else {
+ sbuf.append(arg2);
+ }
+ i = j + 2;
+ }
+ }
+ // append the characters following the second {} pair.
+ sbuf.append(messagePattern.substring(i, messagePattern.length()));
+ return sbuf.toString();
+ }
+}
diff --git a/src/main/java/org/apache/log4j/component/plugins/Pauseable.java b/src/main/java/org/apache/log4j/component/plugins/Pauseable.java
new file mode 100644
index 0000000..b9790bd
--- /dev/null
+++ b/src/main/java/org/apache/log4j/component/plugins/Pauseable.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.component.plugins;
+
+
+/**
+ * Instances of this interface can be paused, and resumed.
+ *
+ * @author Paul Smith (psmith@apache.org)
+ *
+ */
+public interface Pauseable {
+ /**
+ * Set paused state.
+ * @param paused new value
+ */
+ void setPaused(boolean paused);
+
+ /**
+ * Get paused state.
+ * @return paused state.
+ */
+ boolean isPaused();
+}
diff --git a/src/main/java/org/apache/log4j/component/plugins/Plugin.java b/src/main/java/org/apache/log4j/component/plugins/Plugin.java
new file mode 100644
index 0000000..7c38bdc
--- /dev/null
+++ b/src/main/java/org/apache/log4j/component/plugins/Plugin.java
@@ -0,0 +1,144 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.component.plugins;
+
+import org.apache.log4j.spi.LoggerRepository;
+import org.apache.log4j.spi.OptionHandler;
+
+import java.beans.PropertyChangeListener;
+
+
+/**
+ * Defines the required interface for all Plugin objects.
+ * <p/>
+ * <p>A plugin implements some specific functionality to extend
+ * the log4j framework. Each plugin is associated with a specific
+ * LoggerRepository, which it then uses/acts upon. The functionality
+ * of the plugin is up to the developer.
+ * <p/>
+ * <p>Examples of plugins are Receiver and Watchdog. Receiver plugins
+ * allow for remote logging events to be received and processed by
+ * a repository as if the event was sent locally. Watchdog plugins
+ * allow for a repository to be reconfigured when some "watched"
+ * configuration data changes.
+ *
+ * @author Mark Womack (mwomack@apache.org)
+ * @author Nicko Cadell
+ * @author Paul Smith (psmith@apache.org)
+ */
+public interface Plugin extends OptionHandler {
+ /**
+ * Gets the name of the plugin.
+ *
+ * @return String the name of the plugin.
+ */
+ String getName();
+
+ /**
+ * Sets the name of the plugin.
+ *
+ * @param name the name of the plugin.
+ */
+ void setName(String name);
+
+ /**
+ * Gets the logger repository for this plugin.
+ *
+ * @return the logger repository to which this plugin is attached.
+ */
+ LoggerRepository getLoggerRepository();
+
+ /**
+ * Sets the logger repository used by this plugin. This
+ * repository will be used by the plugin functionality.
+ *
+ * @param repository the logger repository to attach this plugin to.
+ */
+ void setLoggerRepository(LoggerRepository repository);
+
+ /**
+ * Adds a PropertyChangeListener to this instance which is
+ * notified only by changes of the property with name propertyName.
+ *
+ * @param propertyName
+ * the name of the property in standard JavaBean syntax
+ * (e.g. for setName(), property="name")
+ * @param l listener
+ */
+ void addPropertyChangeListener(
+ String propertyName, PropertyChangeListener l);
+
+ /**
+ * Adds a PropertyChangeListener that will be notified of all property
+ * changes.
+ *
+ * @param l The listener to add.
+ */
+ void addPropertyChangeListener(PropertyChangeListener l);
+
+ /**
+ * Removes a specific PropertyChangeListener from this instances
+ * registry that has been mapped to be notified of all property
+ * changes.
+ *
+ * @param l The listener to remove.
+ */
+ void removePropertyChangeListener(PropertyChangeListener l);
+
+ /**
+ * Removes a specific PropertyChangeListener from this instance's
+ * registry which has been previously registered to be notified
+ * of only a specific property change.
+ *
+ * @param propertyName property name, may not be null.
+ * @param l listener to be removed.
+ */
+ void removePropertyChangeListener(
+ String propertyName, PropertyChangeListener l);
+
+ /**
+ * True if the plugin is active and running.
+ *
+ * @return boolean true if the plugin is currently active.
+ */
+ boolean isActive();
+
+ /**
+ * Returns true if the testPlugin is considered to be "equivalent" to the
+ * this plugin. The equivalency test is at the discretion of the plugin
+ * implementation. The PluginRegistry will use this method when starting
+ * new plugins to see if a given plugin is considered equivalent to an
+ * already running plugin with the same name. If they are considered to
+ * be equivalent, the currently running plugin will be left in place, and
+ * the new plugin will not be started.
+ * <p/>
+ * It is possible to override the equals() method, however this has
+ * more meaning than is required for this simple test and would also
+ * require the overriding of the hashCode() method as well. All of this
+ * is more work than is needed, so this simple method is used instead.
+ *
+ * @param testPlugin The plugin to test equivalency against.
+ * @return Returns true if testPlugin is considered to be equivelent.
+ */
+ boolean isEquivalent(Plugin testPlugin);
+
+ /**
+ * Call when the plugin should be stopped.
+ */
+ void shutdown();
+}
diff --git a/src/main/java/org/apache/log4j/component/plugins/PluginEvent.java b/src/main/java/org/apache/log4j/component/plugins/PluginEvent.java
new file mode 100644
index 0000000..15fb6ab
--- /dev/null
+++ b/src/main/java/org/apache/log4j/component/plugins/PluginEvent.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.component.plugins;
+
+import java.util.EventObject;
+
+
+/**
+ * All Plugin events are encapsulated in this class, which
+ * simply contains the source Plugin, but may in future include more
+ * information.
+ *
+ * @author Paul Smith
+ */
+public class PluginEvent extends EventObject {
+ /**
+ * @param source The source plugin of the event
+ */
+ PluginEvent(final Plugin source) {
+ super(source);
+ }
+
+ /**
+ * Returns the source Plugin of this event, which is simple
+ * the getSource() method casted to Plugin for convenience.
+ *
+ * @return Plugin source of this event
+ */
+ public Plugin getPlugin() {
+ return (Plugin) getSource();
+ }
+}
diff --git a/src/main/java/org/apache/log4j/component/plugins/PluginListener.java b/src/main/java/org/apache/log4j/component/plugins/PluginListener.java
new file mode 100644
index 0000000..c566d48
--- /dev/null
+++ b/src/main/java/org/apache/log4j/component/plugins/PluginListener.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ */
+package org.apache.log4j.component.plugins;
+
+import java.util.EventListener;
+
+
+/**
+ * PluginListeners are notified when plugins are started or stopped
+ * by the PluginRegistry.
+ *
+ * @author Paul Smith (psmith@apache.org)
+ */
+public interface PluginListener extends EventListener {
+ /**
+ * Notification that plugin has started.
+ * @param e event
+ */
+ void pluginStarted(PluginEvent e);
+
+ /**
+ * Notification that plugin has stopped.
+ * @param e event
+ */
+ void pluginStopped(PluginEvent e);
+}
diff --git a/src/main/java/org/apache/log4j/component/plugins/PluginRegistry.java b/src/main/java/org/apache/log4j/component/plugins/PluginRegistry.java
new file mode 100644
index 0000000..ba75dfc
--- /dev/null
+++ b/src/main/java/org/apache/log4j/component/plugins/PluginRegistry.java
@@ -0,0 +1,299 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.log4j.component.plugins;
+
+import org.apache.log4j.component.spi.LoggerRepositoryEventListener;
+import org.apache.log4j.component.spi.LoggerRepositoryEx;
+import org.apache.log4j.spi.LoggerRepository;
+
+
+import java.util.*;
+
+
+/**
+ * This is a registry for Plugin instances. It provides methods to
+ * start and stop plugin objects individually and to stop all
+ * plugins for a repository.
+ *
+ * @author Mark Womack
+ * @author Paul Smith
+ */
+public final class PluginRegistry {
+ /**
+ * The pluginMap is keyed by plugin name and contains plugins as values.
+ * key=plugin.getName, value=plugin
+ */
+ private final Map pluginMap;
+ /**
+ * Logger repository.
+ */
+ private final LoggerRepositoryEx loggerRepository;
+
+ /**
+ * the listener used to listen for repository events.
+ */
+ private final RepositoryListener listener = new RepositoryListener();
+ /**
+ * List of listeners.
+ */
+ private final List listenerList =
+ Collections.synchronizedList(new ArrayList());
+
+ /**
+ * Creates a new instance.
+ * @param repository logger repository.
+ */
+ public PluginRegistry(final LoggerRepositoryEx repository) {
+ super();
+ pluginMap = new HashMap();
+ this.loggerRepository = repository;
+ this.loggerRepository.addLoggerRepositoryEventListener(listener);
+ }
+
+ /**
+ * Get logger repository.
+ * @return logger repository.
+ */
+ public LoggerRepositoryEx getLoggerRepository() {
+ return loggerRepository;
+ }
+
+
+ /**
+ * Returns true if the specified name is already taken by
+ * an existing Plugin registered within the scope of the specified
+ * LoggerRepository.
+ *
+ * @param name The name to check the repository for
+ * @return true if the name is already in use, otherwise false
+ */
+ public boolean pluginNameExists(final String name) {
+ synchronized (pluginMap) {
+ return pluginMap.containsKey(name);
+ }
+ }
+
+
+ /**
+ * Adds a plugin to the plugin registry.
+ * If a plugin with the same name exists
+ * already, it is shutdown and removed.
+ *
+ * @param plugin the plugin to add.
+ */
+ public void addPlugin(final Plugin plugin) {
+ // put plugin into the repository's reciever map
+ synchronized (pluginMap) {
+ String name = plugin.getName();
+
+ // make sure the plugin has reference to repository
+ plugin.setLoggerRepository(getLoggerRepository());
+
+ Plugin existingPlugin = (Plugin) pluginMap.get(name);
+ if (existingPlugin != null) {
+ existingPlugin.shutdown();
+ }
+
+ // put the new plugin into the map
+ pluginMap.put(name, plugin);
+ firePluginStarted(plugin);
+ }
+ }
+
+
+ /**
+ * Calls the pluginStarted method on every registered PluginListener.
+ *
+ * @param plugin The plugin that has been started.
+ */
+ private void firePluginStarted(final Plugin plugin) {
+ PluginEvent e = null;
+ synchronized (listenerList) {
+ for (Iterator iter = listenerList.iterator(); iter.hasNext();) {
+ PluginListener l = (PluginListener) iter.next();
+ if (e == null) {
+ e = new PluginEvent(plugin);
+ }
+ l.pluginStarted(e);
+ }
+ }
+ }
+
+
+ /**
+ * Calls the pluginStopped method for every registered PluginListner.
+ *
+ * @param plugin The plugin that has been stopped.
+ */
+ private void firePluginStopped(final Plugin plugin) {
+ PluginEvent e = null;
+ synchronized (listenerList) {
+ for (Iterator iter = listenerList.iterator(); iter.hasNext();) {
+ PluginListener l = (PluginListener) iter.next();
+ if (e == null) {
+ e = new PluginEvent(plugin);
+ }
+ l.pluginStopped(e);
+ }
+ }
+ }
+
+
+ /**
+ * Returns all the plugins for a given repository.
+ *
+ * @return List list of plugins from the repository.
+ */
+ public List getPlugins() {
+ synchronized (pluginMap) {
+ List pluginList = new ArrayList(pluginMap.size());
+ Iterator iter = pluginMap.values().iterator();
+
+ while (iter.hasNext()) {
+ pluginList.add(iter.next());
+ }
+ return pluginList;
+ }
+ }
+
+
+ /**
+ * Returns all the plugins for a given repository that are instances
+ * of a certain class.
+ *
+ * @param pluginClass the class the plugin must implement to be selected.
+ * @return List list of plugins from the repository.
+ */
+ public List getPlugins(final Class pluginClass) {
+ synchronized (pluginMap) {
+ List pluginList = new ArrayList(pluginMap.size());
+ Iterator iter = pluginMap.values().iterator();
+
+ while (iter.hasNext()) {
+ Object plugin = iter.next();
+
+ if (pluginClass.isInstance(plugin)) {
+ pluginList.add(plugin);
+ }
+ }
+ return pluginList;
+ }
+ }
+
+
+ /**
+ * Stops a plugin by plugin name and repository.
+ *
+ * @param pluginName the name of the plugin to stop.
+ * @return Plugin the plugin, if stopped, or null if the
+ * the plugin was not found in the registry.
+ */
+ public Plugin stopPlugin(final String pluginName) {
+ synchronized (pluginMap) {
+ Plugin plugin = (Plugin) pluginMap.get(pluginName);
+
+ if (plugin == null) {
+ return null;
+ }
+
+ // shutdown the plugin
+ plugin.shutdown();
+
+ // remove it from the plugin map
+ pluginMap.remove(pluginName);
+ firePluginStopped(plugin);
+
+ // return it for future use
+ return plugin;
+ }
+ }
+
+ /**
+ * Stops all plugins in the given logger repository.
+ */
+ public void stopAllPlugins() {
+ synchronized (pluginMap) {
+ // remove the listener for this repository
+ loggerRepository.removeLoggerRepositoryEventListener(listener);
+
+ Iterator iter = pluginMap.values().iterator();
+
+ while (iter.hasNext()) {
+ Plugin plugin = (Plugin) iter.next();
+ plugin.shutdown();
+ firePluginStopped(plugin);
+ }
+ }
+ }
+
+
+ /**
+ * Adds a PluginListener to this registry to be notified
+ * of PluginEvents.
+ *
+ * @param l PluginListener to add to this registry
+ */
+ public void addPluginListener(final PluginListener l) {
+ listenerList.add(l);
+ }
+
+
+ /**
+ * Removes a particular PluginListener from this registry
+ * such that it will no longer be notified of PluginEvents.
+ *
+ * @param l PluginListener to remove
+ */
+ public void removePluginListener(final PluginListener l) {
+ listenerList.remove(l);
+ }
+
+ /**
+ * Internal class used to handle listener events from repositories.
+ */
+ private class RepositoryListener implements LoggerRepositoryEventListener {
+ /**
+ * Stops all plugins associated with the repository being reset.
+ *
+ * @param repository the repository that was reset.
+ */
+ public void configurationResetEvent(final LoggerRepository repository) {
+ PluginRegistry.this.stopAllPlugins();
+ }
+
+
+ /**
+ * Called when the repository configuration is changed.
+ *
+ * @param repository the repository that was changed.
+ */
+ public void configurationChangedEvent(
+ final LoggerRepository repository) {
+ // do nothing with this event
+ }
+
+
+ /**
+ * Stops all plugins associated with the repository being shutdown.
+ *
+ * @param repository the repository being shutdown.
+ */
+ public void shutdownEvent(final LoggerRepository repository) {
+ PluginRegistry.this.stopAllPlugins();
+ }
+ }
+}
diff --git a/src/main/java/org/apache/log4j/component/plugins/PluginSkeleton.java b/src/main/java/org/apache/log4j/component/plugins/PluginSkeleton.java
new file mode 100644
index 0000000..77b496e
--- /dev/null
+++ b/src/main/java/org/apache/log4j/component/plugins/PluginSkeleton.java
@@ -0,0 +1,222 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.component.plugins;
+
+import org.apache.log4j.component.spi.ComponentBase;
+import org.apache.log4j.spi.LoggerRepository;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+
+
+/**
+ * A convienent abstract class for plugin subclasses that implements
+ * the basic methods of the Plugin interface. Subclasses are required
+ * to implement the isActive(), activateOptions(), and shutdown()
+ * methods.
+ * <p/>
+ * <p>Developers are not required to subclass PluginSkeleton to
+ * develop their own plugins (they are only required to implement the
+ * Plugin interface), but it provides a convenient base class to start
+ * from.
+ * <p/>
+ * Contributors: Nicko Cadell
+ *
+ * @author Mark Womack (mwomack@apache.org)
+ * @author Paul Smith (psmith@apache.org)
+ */
+public abstract class PluginSkeleton extends ComponentBase implements Plugin {
+ /**
+ * Name of this plugin.
+ */
+ protected String name = "plugin";
+
+ /**
+ * Active state of plugin.
+ */
+ protected boolean active;
+
+ /**
+ * This is a delegate that does all the PropertyChangeListener
+ * support.
+ */
+ private PropertyChangeSupport propertySupport =
+ new PropertyChangeSupport(this);
+
+ /**
+ * Construct new instance.
+ */
+ protected PluginSkeleton() {
+ super();
+ }
+
+ /**
+ * Gets the name of the plugin.
+ *
+ * @return String the name of the plugin.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Sets the name of the plugin and notifies
+ * PropertyChangeListeners of the change.
+ *
+ * @param newName the name of the plugin to set.
+ */
+ public void setName(final String newName) {
+ String oldName = this.name;
+ this.name = newName;
+ propertySupport.firePropertyChange("name", oldName, this.name);
+ }
+
+ /**
+ * Gets the logger repository for this plugin.
+ *
+ * @return LoggerRepository the logger repository this plugin will affect.
+ */
+ public LoggerRepository getLoggerRepository() {
+ return repository;
+ }
+
+ /**
+ * Sets the logger repository used by this plugin and notifies a
+ * relevant PropertyChangeListeners registered. This
+ * repository will be used by the plugin functionality.
+ *
+ * @param repository the logger repository that this plugin should affect.
+ */
+ public void setLoggerRepository(final LoggerRepository repository) {
+ Object oldValue = this.repository;
+ this.repository = repository;
+ firePropertyChange("loggerRepository", oldValue, this.repository);
+ }
+
+ /**
+ * Returns whether this plugin is Active or not.
+ *
+ * @return true/false
+ */
+ public synchronized boolean isActive() {
+ return active;
+ }
+
+ /**
+ * Returns true if the plugin has the same name and logger repository as the
+ * testPlugin passed in.
+ *
+ * @param testPlugin The plugin to test equivalency against.
+ * @return Returns true if testPlugin is considered to be equivalent.
+ */
+ public boolean isEquivalent(final Plugin testPlugin) {
+ return (repository == testPlugin.getLoggerRepository())
+ && ((this.name == null && testPlugin.getName() == null)
+ || (this.name != null
+ && name.equals(testPlugin.getName())))
+ && this.getClass().equals(testPlugin.getClass());
+ }
+
+ /**
+ * Add property change listener.
+ * @param listener listener.
+ */
+ public final void addPropertyChangeListener(
+ final PropertyChangeListener listener) {
+ propertySupport.addPropertyChangeListener(listener);
+ }
+
+ /**
+ * Add property change listener for one property only.
+ * @param propertyName property name.
+ * @param listener listener.
+ */
+ public final void addPropertyChangeListener(
+ final String propertyName,
+ final PropertyChangeListener listener) {
+ propertySupport.addPropertyChangeListener(propertyName, listener);
+ }
+
+ /**
+ * Remove property change listener.
+ * @param listener listener.
+ */
+ public final void removePropertyChangeListener(
+ final PropertyChangeListener listener) {
+ propertySupport.removePropertyChangeListener(listener);
+ }
+
+ /**
+ * Remove property change listener on a specific property.
+ * @param propertyName property name.
+ * @param listener listener.
+ */
+ public final void removePropertyChangeListener(
+ final String propertyName,
+ final PropertyChangeListener listener) {
+ propertySupport.removePropertyChangeListener(propertyName, listener);
+ }
+
+ /**
+ * Fire a property change event to appropriate listeners.
+ * @param evt change event.
+ */
+ protected final void firePropertyChange(
+ final PropertyChangeEvent evt) {
+ propertySupport.firePropertyChange(evt);
+ }
+
+ /**
+ * Fire property change event to appropriate listeners.
+ * @param propertyName property name.
+ * @param oldValue old value.
+ * @param newValue new value.
+ */
+ protected final void firePropertyChange(
+ final String propertyName,
+ final boolean oldValue,
+ final boolean newValue) {
+ propertySupport.firePropertyChange(propertyName, oldValue, newValue);
+ }
+
+ /**
+ * Fire property change event to appropriate listeners.
+ * @param propertyName property name.
+ * @param oldValue old value.
+ * @param newValue new value.
+ */
+ protected final void firePropertyChange(
+ final String propertyName,
+ final int oldValue, final int newValue) {
+ propertySupport.firePropertyChange(propertyName, oldValue, newValue);
+ }
+
+ /**
+ * Fire property change event to appropriate listeners.
+ * @param propertyName property name.
+ * @param oldValue old value.
+ * @param newValue new value.
+ */
+ protected final void firePropertyChange(
+ final String propertyName,
+ final Object oldValue,
+ final Object newValue) {
+ propertySupport.firePropertyChange(propertyName, oldValue, newValue);
+ }
+}
diff --git a/src/main/java/org/apache/log4j/component/plugins/Receiver.java b/src/main/java/org/apache/log4j/component/plugins/Receiver.java
new file mode 100644
index 0000000..3b762e1
--- /dev/null
+++ b/src/main/java/org/apache/log4j/component/plugins/Receiver.java
@@ -0,0 +1,131 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.component.plugins;
+
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.component.spi.Thresholdable;
+
+
+/**
+ * Defines the base class for Receiver plugins.
+ * <p/>
+ * <p>Just as Appenders send logging events outside of the log4j
+ * environment (to files, to smtp, to sockets, etc), Receivers bring
+ * logging events inside the log4j environment.
+ * <p/>
+ * <p>Receivers are meant to support the receiving of
+ * remote logging events from another process. For example,
+ * SocketAppender "appends" a logging event to a socket, configured
+ * for a specific host and port number. On the receiving side of
+ * the socket can be a SocketReceiver object. The SocketReceiver
+ * object receives the logging event, and then "posts" it to the
+ * log4j environment (LoggerRepository) on the receiving machine, to
+ * be handled by the configured appenders, etc. The various
+ * settings in this environment (Logger levels, Appender filters &
+ * thresholds) are applied to the received logging event.
+ * <p/>
+ * <p>Receivers can also be used to "import" log messages from other
+ * logging packages into the log4j environment.
+ * <p/>
+ * <p>Receivers can be configured to post events to a given
+ * LoggerRepository.
+ * <p/>
+ * <p>Subclasses of Receiver must implement the isActive(),
+ * activateOptions(), and shutdown() methods. The doPost() method
+ * is provided to standardize the "import" of remote events into
+ * the repository.
+ *
+ * @author Mark Womack
+ * @author Ceki Gülcü
+ * @author Paul Smith (psmith@apache.org)
+ */
+public abstract class Receiver extends PluginSkeleton implements Thresholdable {
+ /**
+ * Threshold level.
+ */
+ protected Level thresholdLevel;
+
+ /**
+ * Create new instance.
+ */
+ protected Receiver() {
+ super();
+ }
+
+ /**
+ * Sets the receiver theshold to the given level.
+ *
+ * @param level The threshold level events must equal or be greater
+ * than before further processing can be done.
+ */
+ public void setThreshold(final Level level) {
+ Level oldValue = this.thresholdLevel;
+ thresholdLevel = level;
+ firePropertyChange("threshold", oldValue, this.thresholdLevel);
+ }
+
+ /**
+ * Gets the current threshold setting of the receiver.
+ *
+ * @return Level The current threshold level of the receiver.
+ */
+ public Level getThreshold() {
+ return thresholdLevel;
+ }
+
+ /**
+ * Returns true if the given level is equals or greater than the current
+ * threshold value of the receiver.
+ *
+ * @param level The level to test against the receiver threshold.
+ * @return boolean True if level is equal or greater than the
+ * receiver threshold.
+ */
+ public boolean isAsSevereAsThreshold(final Level level) {
+ return ((thresholdLevel == null)
+ || level.isGreaterOrEqual(thresholdLevel));
+ }
+
+ /**
+ * Posts the logging event to a logger in the configured logger
+ * repository.
+ *
+ * @param event the log event to post to the local log4j environment.
+ */
+ public void doPost(final LoggingEvent event) {
+ // if event does not meet threshold, exit now
+ if (!isAsSevereAsThreshold(event.getLevel())) {
+ return;
+ }
+
+ // get the "local" logger for this event from the
+ // configured repository.
+ Logger localLogger =
+ getLoggerRepository().getLogger(event.getLoggerName());
+
+ // if the logger level is greater or equal to the level
+ // of the event, use the logger to append the event.
+ if (event.getLevel()
+ .isGreaterOrEqual(localLogger.getEffectiveLevel())) {
+ // call the loggers appenders to process the event
+ localLogger.callAppenders(event);
+ }
+ }
+}
diff --git a/src/main/java/org/apache/log4j/component/scheduler/Job.java b/src/main/java/org/apache/log4j/component/scheduler/Job.java
new file mode 100644
index 0000000..ad0e8eb
--- /dev/null
+++ b/src/main/java/org/apache/log4j/component/scheduler/Job.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.component.scheduler;
+
+
+/**
+ * Job is a very simple interface. It only has a single method {@link #execute}
+ * which is called by the {@link Scheduler} when a task is ready for execution.
+ * <p/>
+ * It is assumed that the execution context
+ * are contained within the implementing
+ * {@link Job} itself.
+ *
+ * @author Ceki Gülcü
+ */
+public interface Job {
+ /**
+ * Execute job.
+ */
+ void execute();
+}
diff --git a/src/main/java/org/apache/log4j/component/scheduler/Scheduler.java b/src/main/java/org/apache/log4j/component/scheduler/Scheduler.java
new file mode 100644
index 0000000..974afe7
--- /dev/null
+++ b/src/main/java/org/apache/log4j/component/scheduler/Scheduler.java
@@ -0,0 +1,307 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.component.scheduler;
+
+import java.util.List;
+import java.util.Vector;
+
+/**
+ * A simple but still useful implementation of a Scheduler (in memory only).
+ * <p/>
+ * This implementation will work very well when the number of scheduled job is
+ * small, say less than 100 jobs. If a larger number of events need to be
+ * scheduled, than a better adapted data structure for the jobList can give
+ * improved performance.
+ *
+ * @author Ceki
+ */
+public class Scheduler extends Thread {
+
+ /**
+ * Job list.
+ */
+ List jobList;
+ /**
+ * If set true, scheduler has or should shut down.
+ */
+ boolean shutdown = false;
+
+ /**
+ * Create new instance.
+ */
+ public Scheduler() {
+ super();
+ jobList = new Vector();
+ }
+
+ /**
+ * Find the index of a given job.
+ * @param job job
+ * @return -1 if the job could not be found.
+ */
+ int findIndex(final Job job) {
+ int size = jobList.size();
+ boolean found = false;
+
+ int i = 0;
+ for (; i < size; i++) {
+ ScheduledJobEntry se = (ScheduledJobEntry) jobList.get(i);
+ if (se.job == job) {
+ found = true;
+ break;
+ }
+ }
+ if (found) {
+ return i;
+ } else {
+ return -1;
+ }
+ }
+
+ /**
+ * Delete the given job.
+ * @param job job.
+ * @return true if the job could be deleted, and
+ * false if the job could not be found or if the Scheduler is about to
+ * shutdown in which case deletions are not permitted.
+ */
+ public synchronized boolean delete(final Job job) {
+ // if already shutdown in the process of shutdown, there is no
+ // need to remove Jobs as they will never be executed.
+ if (shutdown) {
+ return false;
+ }
+ int i = findIndex(job);
+ if (i != -1) {
+ ScheduledJobEntry se = (ScheduledJobEntry) jobList.remove(i);
+ if (se.job != job) { // this should never happen
+ new IllegalStateException("Internal programming error");
+ }
+ // if the job is the first on the list,
+ // then notify the scheduler thread to schedule a new job
+ if (i == 0) {
+ this.notifyAll();
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+
+ /**
+ * Schedule a {@link Job} for execution at system time given by
+ * the <code>desiredTime</code> parameter.
+ * @param job job to schedule.
+ * @param desiredTime desired time of execution.
+ */
+ public synchronized void schedule(final Job job,
+ final long desiredTime) {
+ schedule(new ScheduledJobEntry(job, desiredTime));
+ }
+
+ /**
+ * Schedule a {@link Job} for execution at system time given by
+ * the <code>desiredTime</code> parameter.
+ * <p/>
+ * The job will be rescheduled. It will execute with a frequency determined
+ * by the period parameter.
+ * @param job job to schedule.
+ * @param desiredTime desired time of execution.
+ * @param period repeat period.
+ */
+ public synchronized void schedule(final Job job,
+ final long desiredTime,
+ final long period) {
+ schedule(new ScheduledJobEntry(job, desiredTime, period));
+ }
+
+ /**
+ * Change the period of a job. The original job must exist for its period
+ * to be changed.
+ * <p/>
+ * The method returns true if the period could be changed, and false
+ * otherwise.
+ * @param job job.
+ * @param newPeriod new repeat period.
+ * @return true if period could be changed.
+ */
+ public synchronized boolean changePeriod(final Job job,
+ final long newPeriod) {
+ if (newPeriod <= 0) {
+ throw new IllegalArgumentException(
+ "Period must be an integer langer than zero");
+ }
+
+ int i = findIndex(job);
+ if (i == -1) {
+ return false;
+ } else {
+ ScheduledJobEntry se = (ScheduledJobEntry) jobList.get(i);
+ se.period = newPeriod;
+ return true;
+ }
+ }
+
+ /**
+ * Schedule a job.
+ * @param newSJE new job entry.
+ */
+ private synchronized void schedule(final ScheduledJobEntry newSJE) {
+ // disallow new jobs after shutdown
+ if (shutdown) {
+ return;
+ }
+ int max = jobList.size();
+ long desiredExecutionTime = newSJE.desiredExecutionTime;
+
+ // find the index i such that timeInMillis < jobList[i]
+ int i = 0;
+ for (; i < max; i++) {
+
+ ScheduledJobEntry sje = (ScheduledJobEntry) jobList.get(i);
+
+ if (desiredExecutionTime < sje.desiredExecutionTime) {
+ break;
+ }
+ }
+ jobList.add(i, newSJE);
+ // if the jobList was empty, then notify the scheduler thread
+ if (i == 0) {
+ this.notifyAll();
+ }
+ }
+
+ /**
+ * Shut down scheduler.
+ */
+ public synchronized void shutdown() {
+ shutdown = true;
+ }
+
+ /**
+ * Run scheduler.
+ */
+ public synchronized void run() {
+ while (!shutdown) {
+ if (jobList.isEmpty()) {
+ linger();
+ } else {
+ ScheduledJobEntry sje = (ScheduledJobEntry) jobList.get(0);
+ long now = System.currentTimeMillis();
+ if (now >= sje.desiredExecutionTime) {
+ executeInABox(sje.job);
+ jobList.remove(0);
+ if (sje.period > 0) {
+ sje.desiredExecutionTime = now + sje.period;
+ schedule(sje);
+ }
+ } else {
+ linger(sje.desiredExecutionTime - now);
+ }
+ }
+ }
+ // clear out the job list to facilitate garbage collection
+ jobList.clear();
+ jobList = null;
+ System.out.println("Leaving scheduler run method");
+ }
+
+ /**
+ * We do not want a single failure to affect the whole scheduler.
+ * @param job job to execute.
+ */
+ void executeInABox(final Job job) {
+ try {
+ job.execute();
+ } catch (Exception e) {
+ System.err.println("The execution of the job threw an exception");
+ e.printStackTrace(System.err);
+ }
+ }
+
+ /**
+ * Wait for notification.
+ */
+ void linger() {
+ try {
+ while (jobList.isEmpty() && !shutdown) {
+ this.wait();
+ }
+ } catch (InterruptedException ie) {
+ shutdown = true;
+ }
+ }
+
+ /**
+ * Wait for notification or time to elapse.
+ * @param timeToLinger time to linger.
+ */
+ void linger(final long timeToLinger) {
+ try {
+ this.wait(timeToLinger);
+ } catch (InterruptedException ie) {
+ shutdown = true;
+ }
+ }
+
+ /**
+ * Represents an entry in job scheduler.
+ */
+ static final class ScheduledJobEntry {
+ /**
+ * Desired execution time.
+ */
+ long desiredExecutionTime;
+ /**
+ * Job to run.
+ */
+ Job job;
+ /**
+ * Repeat period.
+ */
+ long period = 0;
+
+ /**
+ * Create new instance.
+ * @param job job
+ * @param desiredTime desired time.
+ */
+ ScheduledJobEntry(final Job job, final long desiredTime) {
+ this(job, desiredTime, 0);
+ }
+
+ /**
+ * Create new instance.
+ * @param job job
+ * @param desiredTime desired time
+ * @param period repeat period
+ */
+ ScheduledJobEntry(final Job job,
+ final long desiredTime,
+ final long period) {
+ super();
+ this.desiredExecutionTime = desiredTime;
+ this.job = job;
+ this.period = period;
+ }
+ }
+
+}
+
+
diff --git a/src/main/java/org/apache/log4j/component/spi/Component.java b/src/main/java/org/apache/log4j/component/spi/Component.java
new file mode 100644
index 0000000..820ec43
--- /dev/null
+++ b/src/main/java/org/apache/log4j/component/spi/Component.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.log4j.component.spi;
+
+
+import org.apache.log4j.spi.LoggerRepository;
+
+/**
+ * A common interface shared by log4j components.
+ *
+ * @author Ceki Gulcu
+ */
+public interface Component {
+
+
+ /**
+ * Set owning logger repository for this component. This operation can
+ * only be performed once.
+ * Once set, a subsequent attempt will throw an IllegalStateException.
+ *
+ * @param repository The repository where this appender is attached.
+ */
+ void setLoggerRepository(LoggerRepository repository);
+
+}
diff --git a/src/main/java/org/apache/log4j/component/spi/ComponentBase.java b/src/main/java/org/apache/log4j/component/spi/ComponentBase.java
new file mode 100644
index 0000000..e6302a9
--- /dev/null
+++ b/src/main/java/org/apache/log4j/component/spi/ComponentBase.java
@@ -0,0 +1,127 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.log4j.component.spi;
+
+import org.apache.log4j.Logger;
+import org.apache.log4j.component.ULogger;
+import org.apache.log4j.spi.LoggerRepository;
+
+
+/**
+ * Most log4j components derive from this class.
+ *
+ * @author Ceki Gulcu
+ */
+public class ComponentBase implements Component {
+
+ /**
+ * Error count limit.
+ */
+ private static final int ERROR_COUNT_LIMIT = 3;
+
+ /**
+ * Logger repository.
+ */
+ protected LoggerRepository repository;
+ /**
+ * Logger.
+ */
+ private ULogger logger;
+ /**
+ * Error count.
+ */
+ private int errorCount = 0;
+
+ /**
+ * Construct a new instance.
+ */
+ protected ComponentBase() {
+ super();
+ }
+
+
+ /**
+ * Called by derived classes when they deem that the component has recovered
+ * from an erroneous state.
+ */
+ protected void resetErrorCount() {
+ errorCount = 0;
+ }
+
+ /**
+ * Set the owning repository. The owning repository cannot be set more than
+ * once.
+ *
+ * @param repository repository
+ */
+ public void setLoggerRepository(final LoggerRepository repository) {
+ if (this.repository == null) {
+ this.repository = repository;
+ } else if (this.repository != repository) {
+ throw new IllegalStateException("Repository has been already set");
+ }
+ }
+
+ /**
+ * Return the LoggerRepository to which this component is attached.
+ *
+ * @return Owning LoggerRepository
+ */
+ protected LoggerRepository getLoggerRepository() {
+ return repository;
+ }
+
+ /**
+ * Return an instance specific logger to be used by the component itself.
+ * This logger is not intended to be accessed by the end-user, hence the
+ * protected keyword.
+ * <p/>
+ * <p>In case the repository for this component is not set,
+ * this implementations returns a {@link SimpleULogger} instance.
+ *
+ * @return A ULogger instance.
+ */
+ protected ULogger getLogger() {
+ if (logger == null) {
+ if (repository != null) {
+ Logger l = repository.getLogger(this.getClass().getName());
+ if (l instanceof ULogger) {
+ logger = (ULogger) l;
+ } else {
+ logger = new Log4JULogger(l);
+ }
+ } else {
+ logger = SimpleULogger.getLogger(this.getClass().getName());
+ }
+ }
+ return logger;
+ }
+
+ /**
+ * Frequently called methods in log4j components can invoke this method in
+ * order to avoid flooding the output when logging lasting error conditions.
+ *
+ * @return a regular logger, or a NOPLogger if called too frequently.
+ */
+ protected ULogger getNonFloodingLogger() {
+ if (errorCount++ >= ERROR_COUNT_LIMIT) {
+ return NOPULogger.NOP_LOGGER;
+ } else {
+ return getLogger();
+ }
+ }
+}
diff --git a/src/main/java/org/apache/log4j/component/spi/ErrorItem.java b/src/main/java/org/apache/log4j/component/spi/ErrorItem.java
new file mode 100644
index 0000000..972bf69
--- /dev/null
+++ b/src/main/java/org/apache/log4j/component/spi/ErrorItem.java
@@ -0,0 +1,172 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.component.spi;
+
+import java.io.PrintStream;
+
+/**
+ * Used to store special log4j errors which cannot be logged using internal
+ * logging. Such errors include those occurring during the initial phases
+ * of log4j configuration or errors emanating from core components such as
+ * Logger or Hierarchy.
+ *
+ * @author Ceki Gulcu
+ */
+public class ErrorItem {
+ /**
+ * Message.
+ */
+ String message;
+ /**
+ * Column.
+ */
+ int colNumber = -1;
+ /**
+ * Line number.
+ */
+ int lineNumber = -1;
+ /**
+ * Exception.
+ */
+ Throwable exception;
+
+ /**
+ * Create new instance.
+ * @param message message
+ * @param e exception
+ */
+ public ErrorItem(final String message, final Exception e) {
+ super();
+ this.message = message;
+ exception = e;
+ }
+
+ /**
+ * Creaet new instance.
+ * @param message message.
+ */
+ public ErrorItem(final String message) {
+ this(message, null);
+ }
+
+ /**
+ * Get column number.
+ * @return column number.
+ */
+ public int getColNumber() {
+ return colNumber;
+ }
+
+ /**
+ * Set column number.
+ * @param colNumber new column number.
+ */
+ public void setColNumber(int colNumber) {
+ this.colNumber = colNumber;
+ }
+
+ /**
+ * Get exception.
+ * @return exception.
+ */
+ public Throwable getException() {
+ return exception;
+ }
+
+ /**
+ * Set exception.
+ * @param exception exception
+ */
+ public void setException(final Throwable exception) {
+ this.exception = exception;
+ }
+
+ /**
+ * Get line number.
+ * @return line number.
+ */
+ public int getLineNumber() {
+ return lineNumber;
+ }
+
+ /**
+ * Set line number.
+ * @param lineNumber line number.
+ */
+ public void setLineNumber(final int lineNumber) {
+ this.lineNumber = lineNumber;
+ }
+
+ /**
+ * Get message.
+ * @return message.
+ */
+ public String getMessage() {
+ return message;
+ }
+
+ /**
+ * Set message.
+ * @param message message.
+ */
+ public void setMessage(final String message) {
+ this.message = message;
+ }
+
+ /**
+ * String representation of ErrorItem.
+ * @return string.
+ */
+ public String toString() {
+ String str =
+ "Reported error: \"" + message + "\"";
+
+ if (lineNumber != -1) {
+ str += " at line " + lineNumber + " column " + colNumber;
+ }
+ if (exception != null) {
+ str += (" with exception " + exception);
+ }
+ return str;
+ }
+
+ /**
+ * Dump the details of this ErrorItem to System.out.
+ */
+ public void dump() {
+ dump(System.out);
+ }
+
+ /**
+ * Dump the details of this ErrorItem on the specified {@link PrintStream}.
+ * @param ps print stream.
+ */
+ public void dump(final PrintStream ps) {
+ String str =
+ "Reported error: \"" + message + "\"";
+
+ if (lineNumber != -1) {
+ str += " at line " + lineNumber + " column " + colNumber;
+ }
+ ps.println(str);
+
+ if (exception != null) {
+ exception.printStackTrace(ps);
+ }
+ }
+}
diff --git a/src/main/java/org/apache/log4j/component/spi/Log4JULogger.java b/src/main/java/org/apache/log4j/component/spi/Log4JULogger.java
new file mode 100644
index 0000000..828857f
--- /dev/null
+++ b/src/main/java/org/apache/log4j/component/spi/Log4JULogger.java
@@ -0,0 +1,228 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.log4j.component.spi;
+
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.apache.log4j.component.ULogger;
+import org.apache.log4j.component.helpers.MessageFormatter;
+
+/**
+ * An implementation of ULogger on org.apache.log4j.Logger.
+ */
+public final class Log4JULogger implements ULogger {
+
+ /**
+ * Wrapped log4j logger.
+ */
+ private final Logger logger;
+
+ /**
+ * Create a new instance.
+ *
+ * @param l logger, may not be null.
+ */
+ public Log4JULogger(final Logger l) {
+ super();
+ if (l == null) {
+ throw new NullPointerException("l");
+ }
+ logger = l;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean isDebugEnabled() {
+ return logger.isDebugEnabled();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void debug(final Object msg) {
+ logger.debug(msg);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void debug(final Object parameterizedMsg,
+ final Object param1) {
+ if (logger.isDebugEnabled()) {
+ logger.debug(MessageFormatter.format(
+ parameterizedMsg.toString(), param1));
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void debug(final String parameterizedMsg,
+ final Object param1,
+ final Object param2) {
+ if (logger.isDebugEnabled()) {
+ logger.debug(MessageFormatter.format(
+ parameterizedMsg.toString(), param1, param2));
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void debug(final Object msg,
+ final Throwable t) {
+ logger.debug(msg, t);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean isInfoEnabled() {
+ return logger.isInfoEnabled();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void info(final Object msg) {
+ logger.info(msg);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void info(final Object parameterizedMsg,
+ final Object param1) {
+ if (logger.isInfoEnabled()) {
+ logger.info(MessageFormatter.format(
+ parameterizedMsg.toString(), param1));
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void info(final String parameterizedMsg,
+ final Object param1,
+ final Object param2) {
+ if (logger.isInfoEnabled()) {
+ logger.info(MessageFormatter.format(
+ parameterizedMsg.toString(),
+ param1,
+ param2));
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void info(final Object msg, final Throwable t) {
+ logger.info(msg, t);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean isWarnEnabled() {
+ return logger.isEnabledFor(Level.WARN);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void warn(final Object msg) {
+ logger.warn(msg);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void warn(final Object parameterizedMsg,
+ final Object param1) {
+ if (logger.isEnabledFor(Level.WARN)) {
+ logger.warn(MessageFormatter.format(
+ parameterizedMsg.toString(), param1));
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void warn(final String parameterizedMsg,
+ final Object param1,
+ final Object param2) {
+ if (logger.isEnabledFor(Level.WARN)) {
+ logger.warn(MessageFormatter.format(
+ parameterizedMsg.toString(), param1, param2));
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void warn(final Object msg, final Throwable t) {
+ logger.warn(msg, t);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean isErrorEnabled() {
+ return logger.isEnabledFor(Level.ERROR);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void error(final Object msg) {
+ logger.error(msg);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void error(final Object parameterizedMsg, final Object param1) {
+ if (logger.isEnabledFor(Level.ERROR)) {
+ logger.error(MessageFormatter.format(
+ parameterizedMsg.toString(), param1));
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void error(final String parameterizedMsg,
+ final Object param1,
+ final Object param2) {
+ if (logger.isEnabledFor(Level.ERROR)) {
+ logger.error(MessageFormatter.format(
+ parameterizedMsg.toString(), param1, param2));
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void error(final Object msg, final Throwable t) {
+ logger.error(msg, t);
+ }
+
+}
diff --git a/src/main/java/org/apache/log4j/component/spi/LoggerEventListener.java b/src/main/java/org/apache/log4j/component/spi/LoggerEventListener.java
new file mode 100644
index 0000000..ef4d9f1
--- /dev/null
+++ b/src/main/java/org/apache/log4j/component/spi/LoggerEventListener.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.component.spi;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.Logger;
+
+
+/**
+ Interface used to listen for Logger related events such as
+ add/remove appender or changing levels. Clients register an instance of
+ the interface and the instance is called back when the various events occur.
+
+ LoggerRepository provides methods for adding and removing
+ LoggerEventListener instances.
+
+ When implementing the methods of this interface, it is useful to remember
+ that the Logger can access the repository using its getRepository()
+ method.
+
+ @author Ceki Gülcü
+ @author Mark Womack
+*/
+public interface LoggerEventListener {
+ /**
+ Called when an appender is added to the logger.
+
+ @param logger The logger to which the appender was added.
+ @param appender The appender added to the logger. */
+ void appenderAddedEvent(Logger logger, Appender appender);
+
+ /**
+ Called when an appender is removed from the logger.
+
+ @param logger The logger from which the appender was removed.
+ @param appender The appender removed from the logger. */
+ void appenderRemovedEvent(Logger logger, Appender appender);
+
+ /**
+ Called when level changed on the logger.
+
+ @param logger The logger that changed levels. */
+ void levelChangedEvent(Logger logger);
+}
diff --git a/src/main/java/org/apache/log4j/component/spi/LoggerRepositoryEventListener.java b/src/main/java/org/apache/log4j/component/spi/LoggerRepositoryEventListener.java
new file mode 100644
index 0000000..97c9149
--- /dev/null
+++ b/src/main/java/org/apache/log4j/component/spi/LoggerRepositoryEventListener.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.component.spi;
+
+
+import org.apache.log4j.spi.LoggerRepository;
+
+/**
+ Interface used to listen for LoggerRepository related
+ events such as startup, reset, and shutdown. Clients register
+ an instance of the interface and the instance is called back
+ when the various events occur.
+
+ LoggerRepository provides methods for adding and removing
+ LoggerRepositoryEventListener instances.
+
+ @author Ceki Gülcü
+ @author Mark Womack
+*/
+public interface LoggerRepositoryEventListener {
+ /**
+ Called when the repository configuration is reset.
+ @param repository repository
+ */
+ void configurationResetEvent(LoggerRepository repository);
+
+ /**
+ Called when the repository configuration is changed.
+ @param repository repository
+ */
+ void configurationChangedEvent(LoggerRepository repository);
+
+ /**
+ Called when the repository is shutdown. When this method is
+ invoked, the repository is still valid (ie it has not been
+ shutdown, but will be after this method returns).
+ @param repository repository.
+ */
+ void shutdownEvent(LoggerRepository repository);
+}
diff --git a/src/main/java/org/apache/log4j/component/spi/LoggerRepositoryEx.java b/src/main/java/org/apache/log4j/component/spi/LoggerRepositoryEx.java
new file mode 100644
index 0000000..59509d4
--- /dev/null
+++ b/src/main/java/org/apache/log4j/component/spi/LoggerRepositoryEx.java
@@ -0,0 +1,201 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.component.spi;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.Category;
+import org.apache.log4j.Logger;
+import org.apache.log4j.component.plugins.PluginRegistry;
+import org.apache.log4j.component.scheduler.Scheduler;
+
+import org.apache.log4j.spi.LoggerFactory;
+import org.apache.log4j.spi.LoggerRepository;
+
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ A <code>LoggerRepository</code> is used to create and retrieve
+ <code>Loggers</code>. The relation between loggers in a repository
+ depends on the repository but typically loggers are arranged in a
+ named hierarchy.
+
+ <p>In addition to the creational methods, a
+ <code>LoggerRepository</code> can be queried for existing loggers,
+ can act as a point of registry for events related to loggers.
+
+ @author Ceki Gülcü
+ @author Mark Womack
+ @author Curt Arnold
+ */
+public interface LoggerRepositoryEx extends LoggerRepository {
+ /**
+ Add a {@link LoggerRepositoryEventListener} to the repository. The
+ listener will be called when repository events occur.
+ @param listener event listener, may not be null.
+ */
+ void addLoggerRepositoryEventListener(
+ LoggerRepositoryEventListener listener);
+
+ /**
+ Remove a {@link LoggerRepositoryEventListener} from the repository.
+ @param listener listener.
+ */
+ void removeLoggerRepositoryEventListener(
+ LoggerRepositoryEventListener listener);
+
+ /**
+ Add a {@link LoggerEventListener} to the repository. The listener
+ will be called when repository events occur.
+ @param listener listener, may not be null.
+ */
+ void addLoggerEventListener(LoggerEventListener listener);
+
+ /**
+ Remove a {@link LoggerEventListener} from the repository.
+ @param listener listener, may not be null.
+ */
+ void removeLoggerEventListener(LoggerEventListener listener);
+
+ /**
+ * Get the name of this logger repository.
+ * @return name, may not be null.
+ */
+ String getName();
+
+ /**
+ * A logger repository is a named entity.
+ * @param repoName new name, may not be null.
+ */
+ void setName(String repoName);
+
+ /**
+ * Is the current configuration of the repository in its original (pristine)
+ * state?
+ * @return true if repository is in original state.
+ *
+ */
+ boolean isPristine();
+
+ /**
+ * Set the pristine flag.
+ * @param state state
+ * @see #isPristine
+ */
+ void setPristine(boolean state);
+
+ /**
+ Requests that a appender removed event be sent to any registered
+ {@link LoggerEventListener}.
+ @param logger The logger from which the appender was removed.
+ @param appender The appender removed from the logger.
+ */
+ void fireRemoveAppenderEvent(Category logger, Appender appender);
+
+ /**
+ Requests that a level changed event be sent to any registered
+ {@link LoggerEventListener}.
+ @param logger The logger which changed levels.
+ */
+ void fireLevelChangedEvent(Logger logger);
+
+ /**
+ Requests that a configuration changed event be sent to any registered
+ {@link LoggerRepositoryEventListener}.
+ */
+ void fireConfigurationChangedEvent();
+
+ /**
+ * Return the PluginRegisty for this LoggerRepository.
+ * @return plug in registry.
+ */
+ PluginRegistry getPluginRegistry();
+
+ /**
+ * Return the {@link Scheduler} for this LoggerRepository.
+ * @return scheduler.
+ */
+ Scheduler getScheduler();
+
+ /**
+ * Get the properties specific for this repository.
+ * @return property map.
+ */
+ Map getProperties();
+
+ /**
+ * Get the property of this repository.
+ * @param key property key.
+ * @return key value or null if not set.
+ */
+ String getProperty(String key);
+
+ /**
+ * Set a property of this repository.
+ * @param key key, may not be null.
+ * @param value new value, if null, property will be removed.
+ */
+ void setProperty(String key, String value);
+
+ /**
+ * Errors which cannot be logged, go to the error list.
+ *
+ * @return List
+ */
+ List getErrorList();
+
+ /**
+ * Errors which cannot be logged, go to the error list.
+ *
+ * @param errorItem an ErrorItem to add to the error list
+ */
+ void addErrorItem(ErrorItem errorItem);
+
+ /**
+ * A LoggerRepository can also act as a store for various objects used
+ * by log4j components.
+ *
+ * @param key key, may not be null.
+ * @return The object stored under 'key'.
+ */
+ Object getObject(String key);
+
+ /**
+ * Store an object under 'key'. If no object can be found, null is returned.
+ *
+ * @param key key, may not be null.
+ * @param value value, may be null.
+ */
+ void putObject(String key, Object value);
+
+ /**
+ * Sets the logger factory used by LoggerRepository.getLogger(String).
+ * @param loggerFactory factory to use, may not be null
+ */
+ void setLoggerFactory(LoggerFactory loggerFactory);
+
+ /**
+ * Returns the logger factory used by
+ * LoggerRepository.getLogger(String).
+ *
+ * @return non-null factory
+ */
+ LoggerFactory getLoggerFactory();
+
+}
diff --git a/src/main/java/org/apache/log4j/component/spi/NOPULogger.java b/src/main/java/org/apache/log4j/component/spi/NOPULogger.java
new file mode 100644
index 0000000..c65aa8b
--- /dev/null
+++ b/src/main/java/org/apache/log4j/component/spi/NOPULogger.java
@@ -0,0 +1,200 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.log4j.component.spi;
+
+import org.apache.log4j.component.ULogger;
+
+
+/**
+ * A no operation (NOP) implementation of {@link ULogger}.
+ *
+ * @author Ceki Gülcü
+ */
+public final class NOPULogger implements ULogger {
+
+ /**
+ * The unique instance of NOPLogger.
+ */
+ public static final NOPULogger NOP_LOGGER = new NOPULogger();
+
+ /**
+ * There is no point in people creating multiple instances of NullLogger.
+ * Hence, the private access modifier.
+ */
+ private NOPULogger() {
+ super();
+ }
+
+ /**
+ * Get instance.
+ * @param name logger name.
+ * @return logger.
+ */
+ public static NOPULogger getLogger(final String name) {
+ return NOP_LOGGER;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean isDebugEnabled() {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void debug(final Object msg) {
+ // NOP
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void debug(final Object parameterizedMsg, final Object param1) {
+ // NOP
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void debug(final String parameterizedMsg,
+ final Object param1,
+ final Object param2) {
+ // NOP
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void debug(final Object msg, final Throwable t) {
+ // NOP
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean isInfoEnabled() {
+ // NOP
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void info(final Object msg) {
+ // NOP
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void info(final Object parameterizedMsg, final Object param1) {
+ // NOP
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void info(final String parameterizedMsg,
+ final Object param1, final Object param2) {
+ // NOP
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void info(final Object msg, final Throwable t) {
+ // NOP
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean isWarnEnabled() {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void warn(final Object msg) {
+ // NOP
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void warn(final Object parameterizedMsg,
+ final Object param1) {
+ // NOP
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void warn(final String parameterizedMsg,
+ final Object param1,
+ final Object param2) {
+ // NOP
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void warn(final Object msg, final Throwable t) {
+ // NOP
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean isErrorEnabled() {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void error(final Object msg) {
+ // NOP
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void error(final Object parameterizedMsg, final Object param1) {
+ // NOP
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void error(final String parameterizedMsg,
+ final Object param1,
+ final Object param2) {
+ // NOP
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void error(final Object msg, final Throwable t) {
+ // NOP
+ }
+
+}
diff --git a/src/main/java/org/apache/log4j/component/spi/SimpleULogger.java b/src/main/java/org/apache/log4j/component/spi/SimpleULogger.java
new file mode 100644
index 0000000..830014e
--- /dev/null
+++ b/src/main/java/org/apache/log4j/component/spi/SimpleULogger.java
@@ -0,0 +1,305 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.log4j.component.spi;
+
+import org.apache.log4j.component.ULogger;
+import org.apache.log4j.component.helpers.MessageFormatter;
+
+
+/**
+ * A simple implementation that logs messages of level INFO or higher on
+ * the console (<code>System.out</code>).
+ * <p>
+ * The output includes the relative time in milliseconds, thread name, level,
+ * logger name, and the message followed by the line separator for the host.
+ * In log4j terms it amounts to the "%r [%t] %level %logger - %m%n" pattern.
+ * <pre>
+176 [main] INFO examples.Sort - Populating an array of 2 elements in reverse.
+225 [main] INFO examples.SortAlgo - Entered the sort method.
+304 [main] INFO SortAlgo.DUMP - Dump of interger array:
+317 [main] INFO SortAlgo.DUMP - Element [0] = 0
+331 [main] INFO SortAlgo.DUMP - Element [1] = 1
+343 [main] INFO examples.Sort - The next log statement should be an error msg.
+346 [main] ERROR SortAlgo.DUMP - Tried to dump an uninitialized array.
+ at org.log4j.examples.SortAlgo.dump(SortAlgo.java:58)
+ at org.log4j.examples.Sort.main(Sort.java:64)
+467 [main] INFO examples.Sort - Exiting main method.
+</pre>
+ *
+ * @author Ceki Gülcü
+ */
+public final class SimpleULogger implements ULogger {
+
+ /**
+ * Logger name.
+ */
+ private final String loggerName;
+
+
+ /**
+ * Mark the time when this class gets loaded into memory.
+ */
+ private static long startTime = System.currentTimeMillis();
+
+ /**
+ * Line separator.
+ */
+ public static final String LINE_SEPARATOR
+ = System.getProperty("line.separator");
+
+ /**
+ * INFO string literal.
+ */
+ private static final String INFO_STR = "INFO";
+ /**
+ * WARN string literal.
+ */
+ private static final String WARN_STR = "WARN";
+ /**
+ * ERROR string literal.
+ */
+ private static final String ERROR_STR = "ERROR";
+
+ /**
+ * Constructor is private to force construction through getLogger.
+ * @param name logger name
+ */
+ private SimpleULogger(final String name) {
+ super();
+ this.loggerName = name;
+ }
+
+ /**
+ * Creates a new instance.
+ *
+ * @param name logger name
+ * @return logger.
+ */
+ public static SimpleULogger getLogger(final String name) {
+ return new SimpleULogger(name);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean isDebugEnabled() {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void debug(final Object msg) {
+ // NOP
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void debug(final Object parameterizedMsg, final Object param1) {
+ // NOP
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void debug(final String parameterizedMsg,
+ final Object param1,
+ final Object param2) {
+ // NOP
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void debug(final Object msg, final Throwable t) {
+ // NOP
+ }
+
+ /**
+ * This is our internal implementation for logging regular (non-parameterized)
+ * log messages.
+ *
+ * @param level level
+ * @param message message
+ * @param t throwable
+ */
+ private void log(final String level,
+ final String message,
+ final Throwable t) {
+ StringBuffer buf = new StringBuffer();
+
+ long millis = System.currentTimeMillis();
+ buf.append(millis - startTime);
+
+ buf.append(" [");
+ buf.append(Thread.currentThread().getName());
+ buf.append("] ");
+
+ buf.append(level);
+ buf.append(" ");
+
+ buf.append(loggerName);
+ buf.append(" - ");
+
+ buf.append(message);
+
+ buf.append(LINE_SEPARATOR);
+
+ System.out.print(buf.toString());
+ if (t != null) {
+ t.printStackTrace(System.out);
+ }
+ System.out.flush();
+ }
+ /**
+ * For parameterized messages, first substitute parameters and then log.
+ *
+ * @param level level
+ * @param parameterizedMsg message pattern
+ * @param param1 param1
+ * @param param2 param2
+ */
+ private void parameterizedLog(final String level,
+ final Object parameterizedMsg,
+ final Object param1,
+ final Object param2) {
+ if (parameterizedMsg instanceof String) {
+ String msgStr = (String) parameterizedMsg;
+ msgStr = MessageFormatter.format(msgStr, param1, param2);
+ log(level, msgStr, null);
+ } else {
+ // To be failsafe, we handle the case where 'messagePattern' is not
+ // a String. Unless the user makes a mistake, this should not happen.
+ log(level, parameterizedMsg.toString(), null);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean isInfoEnabled() {
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void info(final Object msg) {
+ log(INFO_STR, msg.toString(), null);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void info(final Object parameterizedMsg, final Object param1) {
+ parameterizedLog(INFO_STR, parameterizedMsg, param1, null);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void info(final String parameterizedMsg,
+ final Object param1,
+ final Object param2) {
+ parameterizedLog(INFO_STR, parameterizedMsg, param1, param2);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void info(final Object msg, final Throwable t) {
+ log(INFO_STR, msg.toString(), t);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean isWarnEnabled() {
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void warn(final Object msg) {
+ log(WARN_STR, msg.toString(), null);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void warn(final Object parameterizedMsg, final Object param1) {
+ parameterizedLog(WARN_STR, parameterizedMsg, param1, null);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void warn(final String parameterizedMsg,
+ final Object param1,
+ final Object param2) {
+ parameterizedLog(WARN_STR, parameterizedMsg, param1, param2);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void warn(final Object msg, final Throwable t) {
+ log(WARN_STR, msg.toString(), t);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean isErrorEnabled() {
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void error(final Object msg) {
+ log(ERROR_STR, msg.toString(), null);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void error(final Object parameterizedMsg, final Object param1) {
+ parameterizedLog(ERROR_STR, parameterizedMsg, param1, null);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void error(final String parameterizedMsg,
+ final Object param1,
+ final Object param2) {
+ parameterizedLog(ERROR_STR, parameterizedMsg, param1, param2);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void error(final Object msg, final Throwable t) {
+ log(ERROR_STR, msg.toString(), t);
+ }
+
+}
diff --git a/src/main/java/org/apache/log4j/component/spi/Thresholdable.java b/src/main/java/org/apache/log4j/component/spi/Thresholdable.java
new file mode 100644
index 0000000..b570555
--- /dev/null
+++ b/src/main/java/org/apache/log4j/component/spi/Thresholdable.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.component.spi;
+
+import org.apache.log4j.Level;
+
+/**
+ * An interface that defines the required methods for supporting the
+ * setting and getting of a level threshold. Components should implement
+ * this interface if logging events they process should meet a certain
+ * threshold before being processed further. Examples of this are
+ * Appenders and Receivers which will not process logging events unless
+ * the event level is at or greater than a set threshold level.
+ *
+ * @author Paul Smith (psmith@apache.org)
+ * @author Mark Womack
+ */
+public interface Thresholdable {
+ /**
+ * Sets the component theshold to the given level.
+ *
+ * @param level The threshold level events must equal or be greater
+ * than before further processing can be done.
+ */
+ void setThreshold(Level level);
+
+ /**
+ * Gets the current threshold setting of the component.
+ *
+ * @return Level The current threshold level of the component.
+ */
+ Level getThreshold();
+
+ /**
+ * Returns true if the given level is equals or greater than the current
+ * threshold value of the component.
+ *
+ * @param level The level to test against the component threshold.
+ * @return boolean True if level is equal or greater than the
+ * component threshold.
+ */
+ boolean isAsSevereAsThreshold(Level level);
+}
diff --git a/src/main/java/org/apache/log4j/receivers/db/ConnectionSource.java b/src/main/java/org/apache/log4j/receivers/db/ConnectionSource.java
new file mode 100644
index 0000000..c850a98
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/db/ConnectionSource.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.receivers.db;
+
+
+import org.apache.log4j.component.spi.Component;
+import org.apache.log4j.spi.OptionHandler;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+
+/**
+ * The <id>ConnectionSource</id> interface provides a pluggable means of
+ * transparently obtaining JDBC {@link java.sql.Connection}s for log4j classes
+ * that require the use of a {@link java.sql.Connection}.
+ *
+ * @author <a href="mailto:rdecampo@twcny.rr.com">Ray DeCampo</a>
+ */
+public interface ConnectionSource extends Component, OptionHandler {
+
+ final int UNKNOWN_DIALECT = 0;
+ final int POSTGRES_DIALECT = 1;
+ final int MYSQL_DIALECT = 2;
+ final int ORACLE_DIALECT = 3;
+ final int MSSQL_DIALECT = 4;
+ final int HSQL_DIALECT = 5;
+ /**
+ * Obtain a {@link java.sql.Connection} for use. The client is
+ * responsible for closing the {@link java.sql.Connection} when it is no
+ * longer required.
+ *
+ * @throws SQLException if a {@link java.sql.Connection} could not be
+ * obtained
+ */
+ Connection getConnection() throws SQLException;
+
+ /**
+ * Get the SQL dialect that should be used for this connection. Note that the
+ * dialect is not needed if the JDBC driver supports the getGeneratedKeys
+ * method.
+ */
+ int getSQLDialectCode();
+
+ /**
+ * If the connection supports the JDBC 3.0 getGeneratedKeys method, then
+ * we do not need any specific dialect support.
+ */
+ boolean supportsGetGeneratedKeys();
+
+ /**
+ * If the connection does not support batch updates, we will avoid using them.
+ */
+ public boolean supportsBatchUpdates();
+}
diff --git a/src/main/java/org/apache/log4j/receivers/db/ConnectionSourceSkeleton.java b/src/main/java/org/apache/log4j/receivers/db/ConnectionSourceSkeleton.java
new file mode 100644
index 0000000..3b7c9dd
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/db/ConnectionSourceSkeleton.java
@@ -0,0 +1,151 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.receivers.db;
+
+
+import org.apache.log4j.component.spi.ComponentBase;
+import org.apache.log4j.receivers.db.dialect.Util;
+
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.SQLException;
+
+
+/**
+ * @author Ceki Gülcü
+ */
+public abstract class ConnectionSourceSkeleton extends ComponentBase implements ConnectionSource {
+
+ private Boolean overriddenSupportsGetGeneratedKeys = null;
+
+ private String user = null;
+ private String password = null;
+
+ // initially we have an unkonw dialect
+ private int dialectCode = UNKNOWN_DIALECT;
+ private boolean supportsGetGeneratedKeys = false;
+ private boolean supportsBatchUpdates = false;
+
+
+ /**
+ * Learn relevant information about this connection source.
+ *
+ */
+ public void discoverConnnectionProperties() {
+ Connection connection = null;
+ try {
+ connection = getConnection();
+ if (connection == null) {
+ getLogger().warn("Could not get a conneciton");
+ return;
+ }
+ DatabaseMetaData meta = connection.getMetaData();
+ Util util = new Util();
+ util.setLoggerRepository(repository);
+ if (overriddenSupportsGetGeneratedKeys != null) {
+ supportsGetGeneratedKeys = overriddenSupportsGetGeneratedKeys
+ .booleanValue();
+ } else {
+ supportsGetGeneratedKeys = util.supportsGetGeneratedKeys(meta);
+ }
+ supportsBatchUpdates = util.supportsBatchUpdates(meta);
+ dialectCode = Util.discoverSQLDialect(meta);
+ } catch (SQLException se) {
+ getLogger().warn("Could not discover the dialect to use.", se);
+ } finally {
+ DBHelper.closeConnection(connection);
+ }
+ }
+
+ /**
+ * Does this connection support the JDBC Connection.getGeneratedKeys method?
+ */
+ public final boolean supportsGetGeneratedKeys() {
+ return supportsGetGeneratedKeys;
+ }
+
+ public final int getSQLDialectCode() {
+ return dialectCode;
+ }
+
+ /**
+ * Get the password for this connection source.
+ */
+ public final String getPassword() {
+ return password;
+ }
+
+ /**
+ * Sets the password.
+ * @param password The password to set
+ */
+ public final void setPassword(final String password) {
+ this.password = password;
+ }
+
+ /**
+ * Get the user for this connection source.
+ */
+ public final String getUser() {
+ return user;
+ }
+
+ /**
+ * Sets the username.
+ * @param username The username to set
+ */
+ public final void setUser(final String username) {
+ this.user = username;
+ }
+
+ /**
+ * Returns the "overridden" value of "supportsGetGeneratedKeys" property of
+ * the JDBC driver. In certain cases, getting (e.g. Oracle 10g) generated keys
+ * does not work because it returns the ROWID, not the value of the sequence.
+ *
+ * @return A non null string, with "true" or "false" value, if overridden,
+ * <code>null</code> if not overridden.
+ */
+ public String getOverriddenSupportsGetGeneratedKeys() {
+ return overriddenSupportsGetGeneratedKeys != null ? overriddenSupportsGetGeneratedKeys
+ .toString()
+ : null;
+ }
+
+ /**
+ * Sets the "overridden" value of "supportsGetGeneratedKeys" property of the
+ * JDBC driver. In certain cases, getting (e.g. Oracle 10g) generated keys
+ * does not work because it returns the ROWID, not the value of the sequence.
+ *
+ * @param overriddenSupportsGetGeneratedKeys
+ * A non null string, with "true" or "false" value, if overridden,
+ * <code>null</code> if not overridden.
+ */
+ public void setOverriddenSupportsGetGeneratedKeys(
+ String overriddenSupportsGetGeneratedKeys) {
+ this.overriddenSupportsGetGeneratedKeys = Boolean
+ .valueOf(overriddenSupportsGetGeneratedKeys);
+ }
+
+ /**
+ * Does this connection support batch updates?
+ */
+ public final boolean supportsBatchUpdates() {
+ return supportsBatchUpdates;
+ }
+}
diff --git a/src/main/java/org/apache/log4j/receivers/db/CustomSQLDBReceiver.java b/src/main/java/org/apache/log4j/receivers/db/CustomSQLDBReceiver.java
new file mode 100644
index 0000000..55efc72
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/db/CustomSQLDBReceiver.java
@@ -0,0 +1,469 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.receivers.db;
+
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.apache.log4j.component.plugins.Pauseable;
+import org.apache.log4j.component.plugins.Receiver;
+import org.apache.log4j.component.scheduler.Job;
+import org.apache.log4j.component.scheduler.Scheduler;
+import org.apache.log4j.component.spi.LoggerRepositoryEx;
+import org.apache.log4j.spi.LocationInfo;
+
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.spi.ThrowableInformation;
+import org.apache.log4j.xml.DOMConfigurator;
+import org.apache.log4j.xml.UnrecognizedElementHandler;
+import org.w3c.dom.Element;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Hashtable;
+import java.util.Properties;
+import java.util.StringTokenizer;
+
+/**
+ * Converts log data stored in a database into LoggingEvents.
+ * <p>
+ * <b>NOTE:</b> This receiver cannot yet be created through Chainsaw's receiver panel.
+ * It must be created through an XML configuration file.
+ * <p>
+ * This receiver supports database configuration via ConnectionSource, in the
+ * org.apache.log4j.db package: DriverManagerConnectionSource,
+ * DataSourceConnectionSource, JNDIConnectionSource
+ * <p>
+ * This database receiver differs from DBReceiver in that this receiver relies
+ * on custom SQL to retrieve logging event data, where DBReceiver requires the
+ * use of a log4j-defined schema.
+ * <p>
+ * A 'refreshMillis' int parameter controls SQL execution. If 'refreshMillis' is
+ * zero (the default), the receiver will run only one time. If it is set to any
+ * other numeric value, the SQL will be executed on a recurring basis every
+ * 'refreshMillis' milliseconds.
+ * <p>
+ * The receiver closes the connection and acquires a new connection on each
+ * execution of the SQL (use pooled connections if possible).
+ * <p>
+ * If the SQL will be executing on a recurring basis, specify the IDField param -
+ * the column name holding the unique identifier (int) representing the logging
+ * event.
+ * <p>
+ * As events are retrieved, the column represented by IDField is examined and
+ * the largest value is held and used by the next execution of the SQL statement
+ * to avoid retrieving previously processed events.
+ * <p>
+ * As an example, the IDField references a 'COUNTER' (int, auto-increment,
+ * unique) column. The first execution of the SQL statement returns 500 rows,
+ * with a final value in the COUNTER field of 500.
+ * <p>
+ * The SQL statement is manipulated prior to the next execution, adding ' WHERE
+ * COUNTER > 500' to the statement to avoid retrieval of previously processed
+ * events.
+ * <p>
+ * The select statement must provide ALL fields which define a LoggingEvent.
+ * <p>
+ * The SQL statement MUST include the columns: LOGGER, TIMESTAMP, LEVEL, THREAD,
+ * MESSAGE, NDC, MDC, CLASS, METHOD, FILE, LINE, PROPERTIES, THROWABLE
+ * <p>
+ * Use ' AS ' in the SQL statement to alias the SQL's column names to match your
+ * database schema. (see example below).
+ * <p>
+ * Include all fields in the SQL statement, even if you don't have data for the
+ * field (specify an empty string as the value for columns which you don't have
+ * data).
+ * <p>
+ * The TIMESTAMP column must be a datetime.
+ * <p>
+ * Both a PROPERTIES column and an MDC column are supported. These fields
+ * represent Maps on the logging event, but require the use of string
+ * concatenation database functions to hold the (possibly multiple) name/value
+ * pairs in the column.
+ * <p>
+ * For example, to include both 'userid' and 'lastname' properties in the
+ * logging event (from either the PROPERTIES or MDC columns), the name/value
+ * pairs must be concatenated together by your database.
+ * <p>
+ * The resulting PROPERTIES or MDC column must have data in this format: {{name,
+ * value, name2, value2}}
+ * <p>
+ * The resulting PROPERTIES column would contain this text: {{userid, someone,
+ * lastname, mylastname}}
+ * <p>
+ * Here is an example of concatenating a PROPERTIES or MDC column using MySQL's
+ * concat function, where the 'application' and 'hostname' parameters were fixed
+ * text, but the 'log4jid' key's value is the value of the COUNTER column:
+ * <p>
+ * concat("{{application,databaselogs,hostname,mymachine,log4jid,", COUNTER,
+ * "}}") as PROPERTIES
+ * <p>
+ * log4jid is a special property that is used by Chainsaw to represent an 'ID'
+ * field. Specify this property to ensure you can map events in Chainsaw to
+ * events in the database if you need to go back and view events at a later time
+ * or save the events to XML for later analysis.
+ * <p>
+ * Here is a complete MySQL SQL statement which can be used to provide events to
+ * Chainsaw (note how in the example below, there is no column in logtable representing the throwable, so an
+ * empty string is passed in and an ALIAS is still defined):
+ * <p>
+ * select myloggercolumn as LOGGER, mytimestampcolumn as TIMESTAMP, mylevelcolumn as LEVEL, mythreadcolumn as
+ * THREAD, mymessagecolumn as MESSAGE, myndccolumn as NDC, mymdccolumn as MDC, myclasscolumn as CLASS, mymethodcolumn as
+ * METHOD, myfilecolumn as FILE, mylinecolumn as LINE,
+ * concat("{{application,databaselogs,hostname,mymachine, log4jid,",
+ * COUNTER,"}}") as PROPERTIES, "" as THROWABLE from logtable
+ * <p>
+ * @author Scott Deboy <sdeboy@apache.org>
+ * <p>
+ */
+public class CustomSQLDBReceiver extends Receiver implements Pauseable, UnrecognizedElementHandler {
+
+ protected volatile Connection connection = null;
+
+ protected String sqlStatement = "";
+
+ /**
+ * By default we refresh data every 1000 milliseconds.
+ *
+ * @see #setRefreshMillis
+ */
+ static int DEFAULT_REFRESH_MILLIS = 1000;
+
+ int refreshMillis = DEFAULT_REFRESH_MILLIS;
+
+ protected String idField = null;
+
+ int lastID = -1;
+
+ private static final String WHERE_CLAUSE = " WHERE ";
+
+ private static final String AND_CLAUSE = " AND ";
+
+ private boolean whereExists = false;
+
+ private boolean paused = false;
+
+ private ConnectionSource connectionSource;
+
+ public static final String LOG4J_ID_KEY = "log4jid";
+
+ private CustomReceiverJob customReceiverJob;
+
+ public void activateOptions() {
+
+ if(connectionSource == null) {
+ throw new IllegalStateException(
+ "CustomSQLDBReceiver cannot function without a connection source");
+ }
+ whereExists = (sqlStatement.toUpperCase().indexOf(WHERE_CLAUSE) > -1);
+
+ customReceiverJob = new CustomReceiverJob();
+
+ if(this.repository == null) {
+ throw new IllegalStateException(
+ "CustomSQLDBReceiver cannot function without a reference to its owning repository");
+ }
+
+
+
+ if (repository instanceof LoggerRepositoryEx) {
+ Scheduler scheduler = ((LoggerRepositoryEx) repository).getScheduler();
+
+ scheduler.schedule(
+ customReceiverJob, System.currentTimeMillis() + 500, refreshMillis);
+ }
+
+ }
+
+ void closeConnection() {
+ if (connection != null) {
+ try {
+ // LogLog.warn("closing the connection. ", new Exception("x"));
+ connection.close();
+ } catch (SQLException sqle) {
+ // nothing we can do here
+ }
+ }
+ }
+
+ public void setRefreshMillis(int refreshMillis) {
+ this.refreshMillis = refreshMillis;
+ }
+
+ public int getRefreshMillis() {
+ return refreshMillis;
+ }
+
+ /**
+ * @return Returns the connectionSource.
+ */
+ public ConnectionSource getConnectionSource() {
+ return connectionSource;
+ }
+
+ /**
+ * @param connectionSource
+ * The connectionSource to set.
+ */
+ public void setConnectionSource(ConnectionSource connectionSource) {
+ this.connectionSource = connectionSource;
+ }
+
+ public void close() {
+ try {
+ if ((connection != null) && !connection.isClosed()) {
+ connection.close();
+ }
+ } catch (SQLException e) {
+ e.printStackTrace();
+ } finally {
+ connection = null;
+ }
+ }
+
+ public void finalize() throws Throwable {
+ super.finalize();
+ close();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.apache.log4j.plugins.Plugin#shutdown()
+ */
+ public void shutdown() {
+ getLogger().info("removing receiverJob from the Scheduler.");
+
+ if(this.repository instanceof LoggerRepositoryEx) {
+ Scheduler scheduler = ((LoggerRepositoryEx) repository).getScheduler();
+ scheduler.delete(customReceiverJob);
+ }
+
+ lastID = -1;
+ }
+
+ public void setSql(String s) {
+ sqlStatement = s;
+ }
+
+ public String getSql() {
+ return sqlStatement;
+ }
+
+ public void setIDField(String id) {
+ idField = id;
+ }
+
+ public String getIDField() {
+ return idField;
+ }
+
+ public synchronized void setPaused(boolean p) {
+ paused = p;
+ }
+
+ public synchronized boolean isPaused() {
+ return paused;
+ }
+
+ class CustomReceiverJob implements Job {
+ public void execute() {
+ int oldLastID = lastID;
+ try {
+ connection = connectionSource.getConnection();
+ Statement statement = connection.createStatement();
+
+ Logger eventLogger = null;
+ long timeStamp = 0L;
+ String level = null;
+ String threadName = null;
+ Object message = null;
+ String ndc = null;
+ Hashtable mdc = null;
+ String[] throwable = null;
+ String className = null;
+ String methodName = null;
+ String fileName = null;
+ String lineNumber = null;
+ Hashtable properties = null;
+
+ String currentSQLStatement = sqlStatement;
+ if (whereExists) {
+ currentSQLStatement = sqlStatement + AND_CLAUSE + idField
+ + " > " + lastID;
+ } else {
+ currentSQLStatement = sqlStatement + WHERE_CLAUSE + idField
+ + " > " + lastID;
+ }
+
+ ResultSet rs = statement.executeQuery(currentSQLStatement);
+
+ int i = 0;
+ while (rs.next()) {
+ // add a small break every 1000 received events
+ if (++i == 1000) {
+ synchronized (this) {
+ try {
+ // add a delay
+ wait(300);
+ } catch (InterruptedException ie) {
+ }
+ i = 0;
+ }
+ }
+ eventLogger = Logger.getLogger(rs.getString("LOGGER"));
+ timeStamp = rs.getTimestamp("TIMESTAMP").getTime();
+
+ level = rs.getString("LEVEL");
+ threadName = rs.getString("THREAD");
+ message = rs.getString("MESSAGE");
+ ndc = rs.getString("NDC");
+
+ String mdcString = rs.getString("MDC");
+ mdc = new Hashtable();
+
+ if (mdcString != null) {
+ // support MDC being wrapped in {{name, value}}
+ // or
+ // just name, value
+ if ((mdcString.indexOf("{{") > -1)
+ && (mdcString.indexOf("}}") > -1)) {
+ mdcString = mdcString
+ .substring(mdcString.indexOf("{{") + 2,
+ mdcString.indexOf("}}"));
+ }
+
+ StringTokenizer tok = new StringTokenizer(mdcString,
+ ",");
+
+ while (tok.countTokens() > 1) {
+ mdc.put(tok.nextToken(), tok.nextToken());
+ }
+ }
+
+ throwable = new String[] { rs.getString("THROWABLE") };
+ className = rs.getString("CLASS");
+ methodName = rs.getString("METHOD");
+ fileName = rs.getString("FILE");
+ lineNumber = rs.getString("LINE");
+
+ // if properties are provided in the
+ // SQL they can be used here (for example, to route
+ // events to a unique tab in
+ // Chainsaw if the machinename and/or appname
+ // property
+ // are set)
+ String propertiesString = rs.getString("PROPERTIES");
+ properties = new Hashtable();
+
+ if (propertiesString != null) {
+ // support properties being wrapped in {{name,
+ // value}} or just name, value
+ if ((propertiesString.indexOf("{{") > -1)
+ && (propertiesString.indexOf("}}") > -1)) {
+ propertiesString = propertiesString.substring(
+ propertiesString.indexOf("{{") + 2,
+ propertiesString.indexOf("}}"));
+ }
+
+ StringTokenizer tok2 = new StringTokenizer(
+ propertiesString, ",");
+ while (tok2.countTokens() > 1) {
+ String tokenName = tok2.nextToken();
+ String value = tok2.nextToken();
+ if (tokenName.equals(LOG4J_ID_KEY)) {
+ try {
+ int thisInt = Integer.parseInt(value);
+ value = String.valueOf(thisInt);
+ if (thisInt > lastID) {
+ lastID = thisInt;
+ }
+ } catch (Exception e) {
+ }
+ }
+ properties.put(tokenName, value);
+ }
+ }
+
+ Level levelImpl = Level.toLevel(level);
+
+
+ LocationInfo locationInfo = new LocationInfo(fileName,
+ className, methodName, lineNumber);
+
+ ThrowableInformation throwableInfo = new ThrowableInformation(
+ throwable);
+
+ properties.putAll(mdc);
+
+ LoggingEvent event = new LoggingEvent(eventLogger.getName(),
+ eventLogger, timeStamp, levelImpl, message,
+ threadName,
+ throwableInfo,
+ ndc,
+ locationInfo,
+ properties);
+
+ doPost(event);
+ }
+ //log when rows are retrieved
+ if (lastID != oldLastID) {
+ getLogger().debug("lastID: " + lastID);
+ oldLastID = lastID;
+ }
+
+ statement.close();
+ statement = null;
+ } catch (SQLException sqle) {
+ getLogger()
+ .error("*************Problem receiving events", sqle);
+ } finally {
+ closeConnection();
+ }
+
+ // if paused, loop prior to executing sql query
+ synchronized (this) {
+ while (isPaused()) {
+ try {
+ wait(1000);
+ } catch (InterruptedException ie) {
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * @{inheritDoc}
+ */
+ public boolean parseUnrecognizedElement(Element element, Properties props) throws Exception {
+ if ("connectionSource".equals(element.getNodeName())) {
+ Object instance =
+ DOMConfigurator.parseElement(element, props, ConnectionSource.class);
+ if (instance instanceof ConnectionSource) {
+ ConnectionSource source = (ConnectionSource) instance;
+ source.activateOptions();
+ setConnectionSource(source);
+ }
+ return true;
+ }
+ return false;
+ }
+
+}
diff --git a/src/main/java/org/apache/log4j/receivers/db/DBAppender.java b/src/main/java/org/apache/log4j/receivers/db/DBAppender.java
new file mode 100644
index 0000000..402b4cc
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/db/DBAppender.java
@@ -0,0 +1,401 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.db;
+
+import org.apache.log4j.AppenderSkeleton;
+import org.apache.log4j.helpers.LogLog;
+import org.apache.log4j.receivers.db.ConnectionSource;
+import org.apache.log4j.receivers.db.DBHelper;
+import org.apache.log4j.receivers.db.dialect.SQLDialect;
+import org.apache.log4j.receivers.db.dialect.Util;
+import org.apache.log4j.spi.LocationInfo;
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.xml.DOMConfigurator;
+import org.apache.log4j.xml.UnrecognizedElementHandler;
+import org.w3c.dom.Element;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.sql.*;
+import java.util.Iterator;
+import java.util.Properties;
+import java.util.Set;
+
+
+/**
+ * The DBAppender inserts loggin events into three database tables in a format
+ * independent of the Java programming language. The three tables that
+ * DBAppender inserts to must exists before DBAppender can be used. These tables
+ * may be created with the help of SQL scripts found in the
+ * <em>src/java/org/apache/log4j/db/dialect</em> directory. There is a
+ * specific script for each of the most popular database systems. If the script
+ * for your particular type of database system is missing, it should be quite
+ * easy to write one, taking example on the already existing scripts. If you
+ * send them to us, we will gladly include missing scripts in future releases.
+ *
+ * <p>
+ * If the JDBC driver you are using supports the
+ * {@link java.sql.Statement#getGeneratedKeys}method introduced in JDBC 3.0
+ * specification, then you are all set. Otherwise, there must be an
+ * {@link SQLDialect}appropriate for your database system. Currently, we have
+ * dialects for PostgreSQL, MySQL, Oracle and MsSQL. As mentioed previously, an
+ * SQLDialect is required only if the JDBC driver for your database system does
+ * not support the {@link java.sql.Statement#getGeneratedKeys getGeneratedKeys}
+ * method.
+ * </p>
+ *
+ * <table border="1" cellpadding="4">
+ * <tr>
+ * <th>RDBMS</th>
+ * <th>supports <br/><code>getGeneratedKeys()</code> method</th>
+ * <th>specific <br/>SQLDialect support</th>
+ * <tr>
+ * <tr>
+ * <td>PostgreSQL</td>
+ * <td align="center">NO</td>
+ * <td>present and used</td>
+ * <tr>
+ * <tr>
+ * <td>MySQL</td>
+ * <td align="center">YES</td>
+ * <td>present, but not actually needed or used</td>
+ * <tr>
+ * <tr>
+ * <td>Oracle</td>
+ * <td align="center">YES</td>
+ * <td>present, but not actually needed or used</td>
+ * <tr>
+ * <tr>
+ * <td>DB2</td>
+ * <td align="center">YES</td>
+ * <td>not present, and not needed or used</td>
+ * <tr>
+ * <tr>
+ * <td>MsSQL</td>
+ * <td align="center">YES</td>
+ * <td>not present, and not needed or used</td>
+ * <tr>
+ * <tr>
+ * <td>HSQL</td>
+ * <td align="center">NO</td>
+ * <td>present and used</td>
+ * <tr>
+ *
+ * </table>
+ * <p>
+ * <b>Performance: </b> Experiments show that writing a single event into the
+ * database takes approximately 50 milliseconds, on a "standard" PC. If pooled
+ * connections are used, this figure drops to under 10 milliseconds. Note that
+ * most JDBC drivers already ship with connection pooling support.
+ * </p>
+ *
+ *
+ *
+ * <p>
+ * <b>Configuration </b> DBAppender can be configured programmatically, or using
+ * {@link org.apache.log4j.xml.DOMConfigurator JoranConfigurator}. Example
+ * scripts can be found in the <em>tests/input/db</em> directory.
+ *
+ * @author Ceki Gülcü
+ * @author Ray DeCampo
+ */
+public class DBAppender extends AppenderSkeleton implements UnrecognizedElementHandler {
+ static final String insertPropertiesSQL =
+ "INSERT INTO logging_event_property (event_id, mapped_key, mapped_value) VALUES (?, ?, ?)";
+ static final String insertExceptionSQL =
+ "INSERT INTO logging_event_exception (event_id, i, trace_line) VALUES (?, ?, ?)";
+ static final String insertSQL;
+ private static final Method GET_GENERATED_KEYS_METHOD;
+
+
+ static {
+ StringBuffer sql = new StringBuffer();
+ sql.append("INSERT INTO logging_event (");
+ sql.append("sequence_number, ");
+ sql.append("timestamp, ");
+ sql.append("rendered_message, ");
+ sql.append("logger_name, ");
+ sql.append("level_string, ");
+ sql.append("ndc, ");
+ sql.append("thread_name, ");
+ sql.append("reference_flag, ");
+ sql.append("caller_filename, ");
+ sql.append("caller_class, ");
+ sql.append("caller_method, ");
+ sql.append("caller_line) ");
+ sql.append(" VALUES (?, ?, ? ,?, ?, ?, ?, ?, ?, ?, ?, ?)");
+ insertSQL = sql.toString();
+ //
+ // PreparedStatement.getGeneratedKeys added in JDK 1.4
+ //
+ Method getGeneratedKeysMethod;
+ try {
+ getGeneratedKeysMethod = PreparedStatement.class.getMethod("getGeneratedKeys", null);
+ } catch(Exception ex) {
+ getGeneratedKeysMethod = null;
+ }
+ GET_GENERATED_KEYS_METHOD = getGeneratedKeysMethod;
+ }
+
+ ConnectionSource connectionSource;
+ boolean cnxSupportsGetGeneratedKeys = false;
+ boolean cnxSupportsBatchUpdates = false;
+ SQLDialect sqlDialect;
+ boolean locationInfo = false;
+
+
+ public DBAppender() {
+ super(false);
+ }
+
+ public void activateOptions() {
+ LogLog.debug("DBAppender.activateOptions called");
+
+ if (connectionSource == null) {
+ throw new IllegalStateException(
+ "DBAppender cannot function without a connection source");
+ }
+
+ sqlDialect = Util.getDialectFromCode(connectionSource.getSQLDialectCode());
+ if (GET_GENERATED_KEYS_METHOD != null) {
+ cnxSupportsGetGeneratedKeys = connectionSource.supportsGetGeneratedKeys();
+ } else {
+ cnxSupportsGetGeneratedKeys = false;
+ }
+ cnxSupportsBatchUpdates = connectionSource.supportsBatchUpdates();
+ if (!cnxSupportsGetGeneratedKeys && (sqlDialect == null)) {
+ throw new IllegalStateException(
+ "DBAppender cannot function if the JDBC driver does not support getGeneratedKeys method *and* without a specific SQL dialect");
+ }
+
+ // all nice and dandy on the eastern front
+ super.activateOptions();
+ }
+
+ /**
+ * @return Returns the connectionSource.
+ */
+ public ConnectionSource getConnectionSource() {
+ return connectionSource;
+ }
+
+ /**
+ * @param connectionSource
+ * The connectionSource to set.
+ */
+ public void setConnectionSource(ConnectionSource connectionSource) {
+ LogLog.debug("setConnectionSource called for DBAppender");
+ this.connectionSource = connectionSource;
+ }
+
+ protected void append(LoggingEvent event) {
+ Connection connection = null;
+ try {
+ connection = connectionSource.getConnection();
+ connection.setAutoCommit(false);
+
+ PreparedStatement insertStatement;
+ if (cnxSupportsGetGeneratedKeys) {
+ insertStatement = connection.prepareStatement(insertSQL, Statement.RETURN_GENERATED_KEYS);
+ } else {
+ insertStatement = connection.prepareStatement(insertSQL);
+ }
+
+/* insertStatement.setLong(1, event.getSequenceNumber());*/
+ insertStatement.setLong(1, 0);
+
+ insertStatement.setLong(2, event.getTimeStamp());
+ insertStatement.setString(3, event.getRenderedMessage());
+ insertStatement.setString(4, event.getLoggerName());
+ insertStatement.setString(5, event.getLevel().toString());
+ insertStatement.setString(6, event.getNDC());
+ insertStatement.setString(7, event.getThreadName());
+ insertStatement.setShort(8, DBHelper.computeReferenceMask(event));
+
+ LocationInfo li;
+
+ if (event.locationInformationExists() || locationInfo) {
+ li = event.getLocationInformation();
+ } else {
+ li = LocationInfo.NA_LOCATION_INFO;
+ }
+
+ insertStatement.setString(9, li.getFileName());
+ insertStatement.setString(10, li.getClassName());
+ insertStatement.setString(11, li.getMethodName());
+ insertStatement.setString(12, li.getLineNumber());
+
+ int updateCount = insertStatement.executeUpdate();
+ if (updateCount != 1) {
+ LogLog.warn("Failed to insert loggingEvent");
+ }
+
+ ResultSet rs = null;
+ Statement idStatement = null;
+ boolean gotGeneratedKeys = false;
+ if (cnxSupportsGetGeneratedKeys) {
+ try {
+ rs = (ResultSet) GET_GENERATED_KEYS_METHOD.invoke(insertStatement, null);
+ gotGeneratedKeys = true;
+ } catch(InvocationTargetException ex) {
+ Throwable target = ex.getTargetException();
+ if (target instanceof SQLException) {
+ throw (SQLException) target;
+ }
+ throw ex;
+ } catch(IllegalAccessException ex) {
+ LogLog.warn("IllegalAccessException invoking PreparedStatement.getGeneratedKeys", ex);
+ }
+ }
+
+ if (!gotGeneratedKeys) {
+ insertStatement.close();
+ insertStatement = null;
+
+ idStatement = connection.createStatement();
+ idStatement.setMaxRows(1);
+ rs = idStatement.executeQuery(sqlDialect.getSelectInsertId());
+ }
+
+ // A ResultSet cursor is initially positioned before the first row; the
+ // first call to the method next makes the first row the current row
+ rs.next();
+ int eventId = rs.getInt(1);
+
+ rs.close();
+
+ // we no longer need the insertStatement
+ if(insertStatement != null) {
+ insertStatement.close();
+ insertStatement = null;
+ }
+
+ if(idStatement != null) {
+ idStatement.close();
+ idStatement = null;
+ }
+
+ Set propertiesKeys = event.getPropertyKeySet();
+
+ if (propertiesKeys.size() > 0) {
+ PreparedStatement insertPropertiesStatement =
+ connection.prepareStatement(insertPropertiesSQL);
+
+ for (Iterator i = propertiesKeys.iterator(); i.hasNext();) {
+ String key = (String) i.next();
+ String value = (String) event.getProperty(key);
+
+ //LogLog.info("id " + eventId + ", key " + key + ", value " + value);
+ insertPropertiesStatement.setInt(1, eventId);
+ insertPropertiesStatement.setString(2, key);
+ insertPropertiesStatement.setString(3, value);
+
+ if (cnxSupportsBatchUpdates) {
+ insertPropertiesStatement.addBatch();
+ } else {
+ insertPropertiesStatement.execute();
+ }
+ }
+
+ if (cnxSupportsBatchUpdates) {
+ insertPropertiesStatement.executeBatch();
+ }
+
+ insertPropertiesStatement.close();
+ insertPropertiesStatement = null;
+ }
+
+ String[] strRep = event.getThrowableStrRep();
+
+ if (strRep != null) {
+ LogLog.debug("Logging an exception");
+
+ PreparedStatement insertExceptionStatement =
+ connection.prepareStatement(insertExceptionSQL);
+
+ for (short i = 0; i < strRep.length; i++) {
+ insertExceptionStatement.setInt(1, eventId);
+ insertExceptionStatement.setShort(2, i);
+ insertExceptionStatement.setString(3, strRep[i]);
+ if (cnxSupportsBatchUpdates) {
+ insertExceptionStatement.addBatch();
+ } else {
+ insertExceptionStatement.execute();
+ }
+ }
+ if (cnxSupportsBatchUpdates) {
+ insertExceptionStatement.executeBatch();
+ }
+ insertExceptionStatement.close();
+ insertExceptionStatement = null;
+ }
+
+ connection.commit();
+ } catch (Throwable sqle) {
+ LogLog.error("problem appending event", sqle);
+ } finally {
+ DBHelper.closeConnection(connection);
+ }
+ }
+
+ public void close() {
+ closed = true;
+ }
+
+ /**
+ * Returns value of the <b>LocationInfo </b> property which determines whether
+ * caller's location info is written to the database.
+ */
+ public boolean getLocationInfo() {
+ return locationInfo;
+ }
+
+ /**
+ * If true, the information written to the database will include caller's
+ * location information. Due to performance concerns, by default no location
+ * information is written to the database.
+ */
+ public void setLocationInfo(boolean locationInfo) {
+ this.locationInfo = locationInfo;
+ }
+
+ /**
+ * Gets whether appender requires a layout.
+ * @return false
+ */
+ public boolean requiresLayout() {
+ return false;
+ }
+
+ /**
+ * @{inheritDoc}
+ */
+ public boolean parseUnrecognizedElement(Element element, Properties props) throws Exception {
+ if ("connectionSource".equals(element.getNodeName())) {
+ Object instance =
+ DOMConfigurator.parseElement(element, props, ConnectionSource.class);
+ if (instance instanceof ConnectionSource) {
+ ConnectionSource source = (ConnectionSource) instance;
+ source.activateOptions();
+ setConnectionSource(source);
+ }
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/org/apache/log4j/receivers/db/DBHelper.java b/src/main/java/org/apache/log4j/receivers/db/DBHelper.java
new file mode 100644
index 0000000..9754c5e
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/db/DBHelper.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.receivers.db;
+
+import org.apache.log4j.spi.LoggingEvent;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Set;
+
+/**
+ * @author Ceki Gülcü
+ *
+ */
+public class DBHelper {
+
+ public final static short PROPERTIES_EXIST = 0x01;
+ public final static short EXCEPTION_EXISTS = 0x02;
+
+ public static short computeReferenceMask(LoggingEvent event) {
+ short mask = 0;
+ Set propertiesKeys = event.getPropertyKeySet();
+ if(propertiesKeys.size() > 0) {
+ mask = PROPERTIES_EXIST;
+ }
+ String[] strRep = event.getThrowableStrRep();
+ if(strRep != null) {
+ mask |= EXCEPTION_EXISTS;
+ }
+ return mask;
+ }
+
+ static public void closeConnection(Connection connection) {
+ if(connection != null) {
+ try {
+ connection.close();
+ } catch(SQLException sqle) {
+ // static utility classes should not log without an explicit repository
+ // reference
+ }
+ }
+ }
+
+ public static void closeStatement(Statement statement) {
+ if(statement != null) {
+ try {
+ statement.close();
+ } catch(SQLException sqle) {
+ }
+ }
+ }
+}
diff --git a/src/main/java/org/apache/log4j/receivers/db/DBReceiver.java b/src/main/java/org/apache/log4j/receivers/db/DBReceiver.java
new file mode 100644
index 0000000..6877a97
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/db/DBReceiver.java
@@ -0,0 +1,140 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.receivers.db;
+
+import org.apache.log4j.component.plugins.Pauseable;
+import org.apache.log4j.component.plugins.Receiver;
+import org.apache.log4j.component.scheduler.Scheduler;
+import org.apache.log4j.component.spi.LoggerRepositoryEx;
+import org.apache.log4j.xml.DOMConfigurator;
+import org.apache.log4j.xml.UnrecognizedElementHandler;
+import org.w3c.dom.Element;
+
+import java.util.Properties;
+
+/**
+ *
+ * @author Scott Deboy <sdeboy@apache.org>
+ * @author Ceki Gülcü
+ *
+ */
+public class DBReceiver extends Receiver implements Pauseable, UnrecognizedElementHandler {
+ /**
+ * By default we refresh data every 1000 milliseconds.
+ * @see #setRefreshMillis
+ */
+ static int DEFAULT_REFRESH_MILLIS = 1000;
+ ConnectionSource connectionSource;
+ int refreshMillis = DEFAULT_REFRESH_MILLIS;
+ DBReceiverJob receiverJob;
+ boolean paused = false;
+
+ public void activateOptions() {
+
+ if(connectionSource == null) {
+ throw new IllegalStateException(
+ "DBAppender cannot function without a connection source");
+ }
+
+ receiverJob = new DBReceiverJob(this);
+ receiverJob.setLoggerRepository(repository);
+
+ if(this.repository == null) {
+ throw new IllegalStateException(
+ "DBAppender cannot function without a reference to its owning repository");
+ }
+
+ if (repository instanceof LoggerRepositoryEx) {
+ Scheduler scheduler = ((LoggerRepositoryEx) repository).getScheduler();
+
+ scheduler.schedule(
+ receiverJob, System.currentTimeMillis() + 500, refreshMillis);
+ }
+
+ }
+
+ public void setRefreshMillis(int refreshMillis) {
+ this.refreshMillis = refreshMillis;
+ }
+
+ public int getRefreshMillis() {
+ return refreshMillis;
+ }
+
+
+ /**
+ * @return Returns the connectionSource.
+ */
+ public ConnectionSource getConnectionSource() {
+ return connectionSource;
+ }
+
+
+ /**
+ * @param connectionSource The connectionSource to set.
+ */
+ public void setConnectionSource(ConnectionSource connectionSource) {
+ this.connectionSource = connectionSource;
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.apache.log4j.plugins.Plugin#shutdown()
+ */
+ public void shutdown() {
+ getLogger().info("removing receiverJob from the Scheduler.");
+
+ if(this.repository instanceof LoggerRepositoryEx) {
+ Scheduler scheduler = ((LoggerRepositoryEx) repository).getScheduler();
+ scheduler.delete(receiverJob);
+ }
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.apache.log4j.plugins.Pauseable#setPaused(boolean)
+ */
+ public void setPaused(boolean paused) {
+ this.paused = paused;
+ }
+
+ /* (non-Javadoc)
+ * @see org.apache.log4j.plugins.Pauseable#isPaused()
+ */
+ public boolean isPaused() {
+ return paused;
+ }
+
+ /**
+ * @{inheritDoc}
+ */
+ public boolean parseUnrecognizedElement(Element element, Properties props) throws Exception {
+ if ("connectionSource".equals(element.getNodeName())) {
+ Object instance =
+ DOMConfigurator.parseElement(element, props, ConnectionSource.class);
+ if (instance instanceof ConnectionSource) {
+ ConnectionSource source = (ConnectionSource) instance;
+ source.activateOptions();
+ setConnectionSource(source);
+ }
+ return true;
+ }
+ return false;
+ }
+
+}
diff --git a/src/main/java/org/apache/log4j/receivers/db/DBReceiverJob.java b/src/main/java/org/apache/log4j/receivers/db/DBReceiverJob.java
new file mode 100644
index 0000000..cc03769
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/db/DBReceiverJob.java
@@ -0,0 +1,230 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.receivers.db;
+
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.apache.log4j.component.scheduler.Job;
+import org.apache.log4j.component.spi.ComponentBase;
+import org.apache.log4j.spi.LocationInfo;
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.spi.ThrowableInformation;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Hashtable;
+import java.util.Vector;
+
+/**
+ * Actual retrieval of data is made by the instance of DBReceiverJob associated
+ * with DBReceiver.
+ *
+ * @author Ceki Gülcü
+ */
+class DBReceiverJob extends ComponentBase implements Job {
+
+ String sqlException = "SELECT trace_line FROM logging_event_exception where event_id=? ORDER by i ASC";
+ String sqlProperties = "SELECT mapped_key, mapped_value FROM logging_event_property WHERE event_id=?";
+ String sqlSelect =
+ "SELECT " +
+ "sequence_number, timestamp, rendered_message, logger_name, " +
+ "level_string, ndc, thread_name, reference_flag, " +
+ "caller_filename, caller_class, caller_method, caller_line, " +
+ "event_id " +
+ "FROM logging_event " +
+ "WHERE event_id > ? ORDER BY event_id ASC";
+
+
+ long lastId = Short.MIN_VALUE;
+
+ DBReceiver parentDBReceiver;
+
+ DBReceiverJob(DBReceiver parent) {
+ parentDBReceiver = parent;
+ }
+
+ public void execute() {
+ getLogger().debug("DBReceiverJob.execute() called");
+
+ Connection connection = null;
+
+ try {
+ connection = parentDBReceiver.connectionSource.getConnection();
+ PreparedStatement statement = connection.prepareStatement(sqlSelect);
+ statement.setLong(1, lastId);
+ ResultSet rs = statement.executeQuery();
+ //rs.beforeFirst();
+
+ while (rs.next()) {
+ Logger logger = null;
+ long timeStamp = 0L;
+ String level = null;
+ String threadName = null;
+ Object message = null;
+ String ndc = null;
+ String className = null;
+ String methodName = null;
+ String fileName = null;
+ String lineNumber = null;
+ Hashtable properties = new Hashtable();
+
+
+ //event.setSequenceNumber(rs.getLong(1));
+ timeStamp = rs.getLong(2);
+ message = rs.getString(3);
+ logger = Logger.getLogger(rs.getString(4));
+ level = rs.getString(5);
+ Level levelImpl = Level.toLevel(level.trim());
+
+ ndc = rs.getString(6);
+ threadName = rs.getString(7);
+
+ short mask = rs.getShort(8);
+
+ fileName = rs.getString(9);
+ className = rs.getString(10);
+ methodName = rs.getString(11);
+ lineNumber = rs.getString(12).trim();
+
+ LocationInfo locationInfo = null;
+ if (fileName.equals(LocationInfo.NA)) {
+ locationInfo = LocationInfo.NA_LOCATION_INFO;
+ } else {
+ locationInfo = new LocationInfo(fileName, className,
+ methodName, lineNumber);
+ }
+
+ long id = rs.getLong(13);
+ //LogLog.info("Received event with id=" + id);
+ lastId = id;
+
+ ThrowableInformation throwableInfo = null;
+ if ((mask & DBHelper.EXCEPTION_EXISTS) != 0) {
+ throwableInfo = getException(connection, id);
+ }
+
+ LoggingEvent event = new LoggingEvent(logger.getName(),
+ logger, timeStamp, levelImpl, message,
+ threadName,
+ throwableInfo,
+ ndc,
+ locationInfo,
+ properties);
+
+
+ // Scott asked for this info to be
+ event.setProperty("log4jid", Long.toString(id));
+
+ if ((mask & DBHelper.PROPERTIES_EXIST) != 0) {
+ getProperties(connection, id, event);
+ }
+
+
+
+
+ if (!parentDBReceiver.isPaused()) {
+ parentDBReceiver.doPost(event);
+ }
+ } // while
+ statement.close();
+ statement = null;
+ } catch (SQLException sqle) {
+ getLogger().error("Problem receiving events", sqle);
+ } finally {
+ closeConnection(connection);
+ }
+ }
+
+ void closeConnection(Connection connection) {
+ if (connection != null) {
+ try {
+ //LogLog.warn("closing the connection. ", new Exception("x"));
+ connection.close();
+ } catch (SQLException sqle) {
+ // nothing we can do here
+ }
+ }
+ }
+
+ /**
+ * Retrieve the event properties from the logging_event_property table.
+ *
+ * @param connection
+ * @param id
+ * @param event
+ * @throws SQLException
+ */
+ void getProperties(Connection connection, long id, LoggingEvent event)
+ throws SQLException {
+
+ PreparedStatement statement = connection.prepareStatement(sqlProperties);
+ try {
+ statement.setLong(1, id);
+ ResultSet rs = statement.executeQuery();
+
+ while (rs.next()) {
+ String key = rs.getString(1);
+ String value = rs.getString(2);
+ event.setProperty(key, value);
+ }
+ } finally {
+ statement.close();
+ }
+ }
+
+ /**
+ * Retrieve the exception string representation from the
+ * logging_event_exception table.
+ *
+ * @param connection
+ * @param id
+ * @throws SQLException
+ */
+ ThrowableInformation getException(Connection connection, long id)
+ throws SQLException {
+
+ PreparedStatement statement = null;
+
+ try {
+ statement = connection.prepareStatement(sqlException);
+ statement.setLong(1, id);
+ ResultSet rs = statement.executeQuery();
+
+ Vector v = new Vector();
+
+ while (rs.next()) {
+ //int i = rs.getShort(1);
+ v.add(rs.getString(1));
+ }
+
+ int len = v.size();
+ String[] strRep = new String[len];
+ for (int i = 0; i < len; i++) {
+ strRep[i] = (String) v.get(i);
+ }
+ // we've filled strRep, we now attach it to the event
+ return new ThrowableInformation(strRep);
+ } finally {
+ if (statement != null) {
+ statement.close();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/log4j/receivers/db/DataSourceConnectionSource.java b/src/main/java/org/apache/log4j/receivers/db/DataSourceConnectionSource.java
new file mode 100644
index 0000000..6590b42
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/db/DataSourceConnectionSource.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.receivers.db;
+
+
+import org.apache.log4j.xml.DOMConfigurator;
+import org.apache.log4j.xml.UnrecognizedElementHandler;
+import org.w3c.dom.Element;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.Properties;
+
+
+/**
+ * The DataSourceConnectionSource is an implementation of {@link ConnectionSource}
+ * that obtains the Connection in the recommended JDBC manner based on
+ * a {@link javax.sql.DataSource DataSource}.
+ * <p>
+ *
+ * @author Ray DeCampo
+ * @author Ceki Gülcü
+ */
+public class DataSourceConnectionSource extends ConnectionSourceSkeleton
+ implements UnrecognizedElementHandler {
+
+ private DataSource dataSource;
+
+
+ public void activateOptions() {
+ //LogLog.debug("**********DataSourceConnectionSource.activateOptions called");
+ if (dataSource == null) {
+ getLogger().warn("WARNING: No data source specified");
+ } else {
+ Connection connection = null;
+ try {
+ connection = getConnection();
+ } catch(SQLException se) {
+ getLogger().warn("Could not get a connection to discover the dialect to use.", se);
+ }
+ if(connection != null) {
+ discoverConnnectionProperties();
+ }
+ if(!supportsGetGeneratedKeys() && getSQLDialectCode() == ConnectionSource.UNKNOWN_DIALECT) {
+ getLogger().warn("Connection does not support GetGeneratedKey method and could not discover the dialect.");
+ }
+ }
+ }
+
+ /**
+ * @see org.apache.log4j.db.ConnectionSource#getConnection()
+ */
+ public Connection getConnection() throws SQLException {
+ if (dataSource == null) {
+ getLogger().error("WARNING: No data source specified");
+ return null;
+ }
+
+ if (getUser() == null) {
+ return dataSource.getConnection();
+ } else {
+ return dataSource.getConnection(getUser(), getPassword());
+ }
+ }
+
+ public DataSource getDataSource() {
+ return dataSource;
+ }
+
+ public void setDataSource(DataSource dataSource) {
+ this.dataSource = dataSource;
+ }
+
+ /**
+ * @{inheritDoc}
+ */
+ public boolean parseUnrecognizedElement(Element element, Properties props) throws Exception {
+ if ("dataSource".equals(element.getNodeName())) {
+ Object instance =
+ DOMConfigurator.parseElement(element, props, DataSource.class);
+ if (instance instanceof DataSource) {
+ setDataSource((DataSource) instance);
+ }
+ return true;
+ }
+ return false;
+ }
+
+}
diff --git a/src/main/java/org/apache/log4j/receivers/db/DriverManagerConnectionSource.java b/src/main/java/org/apache/log4j/receivers/db/DriverManagerConnectionSource.java
new file mode 100644
index 0000000..a1e777a
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/db/DriverManagerConnectionSource.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.receivers.db;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+
+
+/**
+ * The DriverManagerConnectionSource is an implementation of {@link ConnectionSource}
+ * that obtains the Connection in the traditional JDBC manner based on the
+ * connection URL.
+ * <p>
+ * Note that this class will establish a new Connection for each call to
+ * {@link #getConnection()}. It is recommended that you either use a JDBC
+ * driver that natively supported Connection pooling or that you create
+ * your own implementation of {@link ConnectionSource} that taps into whatever
+ * pooling mechanism you are already using. (If you have access to a JNDI
+ * implementation that supports {@link javax.sql.DataSource}s, e.g. within
+ * a J2EE application server, see {@link JNDIConnectionSource}). See
+ * <a href="#dbcp">below</a> for a configuration example that uses the
+ * <a href="http://jakarta.apache.org/commons/dbcp/index.html">commons-dbcp</a>
+ * package from Apache.
+ * <p>
+ * Sample configuration:<br>
+ * <pre>
+ * <connectionSource class="org.apache.log4j.jdbc.DriverManagerConnectionSource">
+ * <param name="driver" value="com.mysql.jdbc.Driver" />
+ * <param name="url" value="jdbc:mysql://localhost:3306/mydb" />
+ * <param name="username" value="myUser" />
+ * <param name="password" value="myPassword" />
+ * </connectionSource>
+ * </pre>
+ * <p>
+ * <a name="dbcp">If</a> you do not have another connection pooling mechanism
+ * built into your application, you can use the
+ * <a href="http://jakarta.apache.org/commons/dbcp/index.html">commons-dbcp</a>
+ * package from Apache:<br>
+ * <pre>
+ * <connectionSource class="org.apache.log4j.jdbc.DriverManagerConnectionSource">
+ * <param name="driver" value="org.apache.commons.dbcp.PoolingDriver" />
+ * <param name="url" value="jdbc:apache:commons:dbcp:/myPoolingDriver" />
+ * </connectionSource>
+ * </pre>
+ * Then the configuration information for the commons-dbcp package goes into
+ * the file myPoolingDriver.jocl and is placed in the classpath. See the
+ * <a href="http://jakarta.apache.org/commons/dbcp/index.html">commons-dbcp</a>
+ * documentation for details.
+ *
+ * @author <a href="mailto:rdecampo@twcny.rr.com">Ray DeCampo</a>
+ */
+public class DriverManagerConnectionSource extends ConnectionSourceSkeleton {
+ private String driverClass = null;
+ private String url = null;
+
+ public void activateOptions() {
+ try {
+ if (driverClass != null) {
+ Class.forName(driverClass);
+ discoverConnnectionProperties();
+ } else {
+ getLogger().error(
+ "WARNING: No JDBC driver specified for log4j DriverManagerConnectionSource.");
+ }
+ } catch (final ClassNotFoundException cnfe) {
+ getLogger().error("Could not load JDBC driver class: " + driverClass, cnfe);
+ }
+ }
+
+
+ /**
+ * @see org.apache.log4j.db.ConnectionSource#getConnection()
+ */
+ public Connection getConnection() throws SQLException {
+ if (getUser() == null) {
+ return DriverManager.getConnection(url);
+ } else {
+ return DriverManager.getConnection(url, getUser(), getPassword());
+ }
+ }
+
+
+ /**
+ * Returns the url.
+ * @return String
+ */
+ public String getUrl() {
+ return url;
+ }
+
+
+ /**
+ * Sets the url.
+ * @param url The url to set
+ */
+ public void setUrl(String url) {
+ this.url = url;
+ }
+
+
+ /**
+ * Returns the name of the driver class.
+ * @return String
+ */
+ public String getDriverClass() {
+ return driverClass;
+ }
+
+
+ /**
+ * Sets the driver class.
+ * @param driverClass The driver class to set
+ */
+ public void setDriverClass(String driverClass) {
+ this.driverClass = driverClass;
+ }
+}
diff --git a/src/main/java/org/apache/log4j/receivers/db/JNDIConnectionSource.java b/src/main/java/org/apache/log4j/receivers/db/JNDIConnectionSource.java
new file mode 100644
index 0000000..fe95c42
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/db/JNDIConnectionSource.java
@@ -0,0 +1,142 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.log4j.receivers.db;
+
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.SQLException;
+
+// PortableRemoteObject was introduced in JDK 1.3. We won't use it.
+// import javax.rmi.PortableRemoteObject;
+
+
+/**
+ * The <id>JNDIConnectionSource</id> is an implementation of
+ * {@link ConnectionSource} that obtains a {@link javax.sql.DataSource} from a
+ * JNDI provider and uses it to obtain a {@link java.sql.Connection}. It is
+ * primarily designed to be used inside of J2EE application servers or
+ * application server clients, assuming the application server supports remote
+ * access of {@link javax.sql.DataSource}s. In this way one can take
+ * advantage of connection pooling and whatever other goodies the application
+ * server provides.
+ * <p>
+ * Sample configuration:<br>
+ * <pre>
+ * <connectionSource class="org.apache.log4j.jdbc.JNDIConnectionSource">
+ * <param name="jndiLocation" value="jdbc/MySQLDS" />
+ * </connectionSource>
+ * </pre>
+ * <p>
+ * Sample configuration (with username and password):<br>
+ * <pre>
+ * <connectionSource class="org.apache.log4j.jdbc.JNDIConnectionSource">
+ * <param name="jndiLocation" value="jdbc/MySQLDS" />
+ * <param name="username" value="myUser" />
+ * <param name="password" value="myPassword" />
+ * </connectionSource>
+ * </pre>
+ * <p>
+ * Note that this class will obtain an {@link javax.naming.InitialContext}
+ * using the no-argument constructor. This will usually work when executing
+ * within a J2EE environment. When outside the J2EE environment, make sure
+ * that you provide a jndi.properties file as described by your JNDI
+ * provider's documentation.
+ *
+ * @author <a href="mailto:rdecampo@twcny.rr.com">Ray DeCampo</a>
+ */
+public class JNDIConnectionSource
+ extends ConnectionSourceSkeleton {
+ private String jndiLocation = null;
+ private DataSource dataSource = null;
+
+ /**
+ * @see org.apache.log4j.spi.OptionHandler#activateOptions()
+ */
+ public void activateOptions() {
+ if (jndiLocation == null) {
+ getLogger().error("No JNDI location specified for JNDIConnectionSource.");
+ }
+
+ discoverConnnectionProperties();
+
+ }
+
+ /**
+ * @see org.apache.log4j.db.ConnectionSource#getConnection()
+ */
+ public Connection getConnection()
+ throws SQLException {
+ Connection conn = null;
+ try {
+
+ if(dataSource == null) {
+ dataSource = lookupDataSource();
+ }
+ if (getUser() == null) {
+ conn = dataSource.getConnection();
+ } else {
+ conn = dataSource.getConnection(getUser(), getPassword());
+ }
+ } catch (final NamingException ne) {
+ getLogger().error("Error while getting data source", ne);
+ throw new SQLException("NamingException while looking up DataSource: " + ne.getMessage());
+ } catch (final ClassCastException cce) {
+ getLogger().error("ClassCastException while looking up DataSource.", cce);
+ throw new SQLException("ClassCastException while looking up DataSource: " + cce.getMessage());
+ }
+
+ return conn;
+ }
+
+ /**
+ * Returns the jndiLocation.
+ * @return String
+ */
+ public String getJndiLocation() {
+ return jndiLocation;
+ }
+
+
+ /**
+ * Sets the jndiLocation.
+ * @param jndiLocation The jndiLocation to set
+ */
+ public void setJndiLocation(String jndiLocation) {
+ this.jndiLocation = jndiLocation;
+ }
+
+
+ private DataSource lookupDataSource()
+ throws NamingException, SQLException {
+ DataSource ds;
+ Context ctx = new InitialContext();
+ Object obj = ctx.lookup(jndiLocation);
+
+ // PortableRemoteObject was introduced in JDK 1.3. We won't use it.
+ //ds = (DataSource)PortableRemoteObject.narrow(obj, DataSource.class);
+ ds = (DataSource) obj;
+
+ if (ds == null) {
+ throw new SQLException("Failed to obtain data source from JNDI location " + jndiLocation);
+ } else {
+ return ds;
+ }
+ }
+}
diff --git a/src/main/java/org/apache/log4j/receivers/db/dialect/HSQLDBDialect.java b/src/main/java/org/apache/log4j/receivers/db/dialect/HSQLDBDialect.java
new file mode 100644
index 0000000..c8a8c45
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/db/dialect/HSQLDBDialect.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.receivers.db.dialect;
+
+/**
+ * The HSQLDB dialect.
+ *
+ * @author <a href="http://www.qos.ch/log4j/">Ceki Gülcü</a>
+*/
+public class HSQLDBDialect implements SQLDialect {
+ public static final String SELECT_CURRVAL = "CALL IDENTITY()";
+
+ public String getSelectInsertId() {
+ return SELECT_CURRVAL;
+ }
+}
diff --git a/src/main/java/org/apache/log4j/receivers/db/dialect/MsSQLDialect.java b/src/main/java/org/apache/log4j/receivers/db/dialect/MsSQLDialect.java
new file mode 100644
index 0000000..d4cf4d6
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/db/dialect/MsSQLDialect.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.receivers.db.dialect;
+
+/**
+* The MS SQL Server dialect is untested.
+*
+* Note that the dialect is not needed if your JDBC driver supports
+* the getGeneratedKeys method introduced in JDBC 3.0 specification.
+*
+* @author James Stauffer
+*/
+public class MsSQLDialect implements SQLDialect {
+ public static final String SELECT_CURRVAL = "SELECT @@identity id";
+
+ public String getSelectInsertId() {
+ return SELECT_CURRVAL;
+ }
+}
diff --git a/src/main/java/org/apache/log4j/receivers/db/dialect/MySQLDialect.java b/src/main/java/org/apache/log4j/receivers/db/dialect/MySQLDialect.java
new file mode 100644
index 0000000..4bebd71
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/db/dialect/MySQLDialect.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.receivers.db.dialect;
+
+/**
+ *
+ *
+ * @author Ceki
+ *
+ */
+public class MySQLDialect implements SQLDialect {
+ public static final String SELECT_LAST_INSERT_ID = "SELECT LAST_INSERT_ID()";
+
+ public String getSelectInsertId() {
+ return SELECT_LAST_INSERT_ID;
+ }
+}
diff --git a/src/main/java/org/apache/log4j/receivers/db/dialect/OracleDialect.java b/src/main/java/org/apache/log4j/receivers/db/dialect/OracleDialect.java
new file mode 100644
index 0000000..9f652e7
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/db/dialect/OracleDialect.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.receivers.db.dialect;
+
+/**
+ * The Oracle dialect. Tested successfully on Oracle9i Release 9.2.0.3.0 by
+ * James Stauffer.
+ *
+ * @author Ceki Gülcü
+ */
+public class OracleDialect implements SQLDialect {
+ public static final String SELECT_CURRVAL = "SELECT logging_event_id_seq.currval from dual";
+
+ public String getSelectInsertId() {
+ return SELECT_CURRVAL;
+ }
+
+}
diff --git a/src/main/java/org/apache/log4j/receivers/db/dialect/PostgreSQLDialect.java b/src/main/java/org/apache/log4j/receivers/db/dialect/PostgreSQLDialect.java
new file mode 100644
index 0000000..cd17fa1
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/db/dialect/PostgreSQLDialect.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.receivers.db.dialect;
+
+
+/**
+ *
+ * @author ceki
+ *
+ * To change the template for this generated type comment go to
+ * Window>Preferences>Java>Code Generation>Code and Comments
+ */
+public class PostgreSQLDialect
+ implements SQLDialect {
+ public static final String SELECT_CURRVAL = "SELECT currval('logging_event_id_seq')";
+
+ public String getSelectInsertId() {
+ return SELECT_CURRVAL;
+ }
+}
diff --git a/src/main/java/org/apache/log4j/receivers/db/dialect/SQLDialect.java b/src/main/java/org/apache/log4j/receivers/db/dialect/SQLDialect.java
new file mode 100644
index 0000000..109554f
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/db/dialect/SQLDialect.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.log4j.receivers.db.dialect;
+
+/**
+ * @author ceki
+ *
+ */
+public interface SQLDialect {
+
+ public String getSelectInsertId();
+
+}
diff --git a/src/main/java/org/apache/log4j/receivers/db/dialect/SybaseDialect.java b/src/main/java/org/apache/log4j/receivers/db/dialect/SybaseDialect.java
new file mode 100644
index 0000000..cc463db
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/db/dialect/SybaseDialect.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.log4j.receivers.db.dialect;
+
+/**
+ * The Sybase dialect.
+ *
+*/
+public class SybaseDialect implements SQLDialect {
+ public static final String SELECT_CURRVAL = "select @@identity";
+
+ public String getSelectInsertId() {
+ return SELECT_CURRVAL;
+ }
+}
diff --git a/src/main/java/org/apache/log4j/receivers/db/dialect/Util.java b/src/main/java/org/apache/log4j/receivers/db/dialect/Util.java
new file mode 100644
index 0000000..7d0ba4c
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/db/dialect/Util.java
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.receivers.db.dialect;
+
+import org.apache.log4j.component.spi.ComponentBase;
+import org.apache.log4j.receivers.db.ConnectionSource;
+
+import java.sql.DatabaseMetaData;
+import java.sql.SQLException;
+
+
+/**
+ *
+ * @author Ceki Gulcu
+ *
+ */
+public class Util extends ComponentBase {
+ private static final String POSTGRES_PART = "postgresql";
+ private static final String MYSQL_PART = "mysql";
+ private static final String ORACLE_PART = "oracle";
+ //private static final String MSSQL_PART = "mssqlserver4";
+ private static final String MSSQL_PART = "microsoft";
+ private static final String HSQL_PART = "hsql";
+
+ public static int discoverSQLDialect(DatabaseMetaData meta) {
+ int dialectCode = 0;
+
+ try {
+
+ String dbName = meta.getDatabaseProductName().toLowerCase();
+
+ if (dbName.indexOf(POSTGRES_PART) != -1) {
+ return ConnectionSource.POSTGRES_DIALECT;
+ } else if (dbName.indexOf(MYSQL_PART) != -1) {
+ return ConnectionSource.MYSQL_DIALECT;
+ } else if (dbName.indexOf(ORACLE_PART) != -1) {
+ return ConnectionSource.ORACLE_DIALECT;
+ } else if (dbName.indexOf(MSSQL_PART) != -1) {
+ return ConnectionSource.MSSQL_DIALECT;
+ } else if (dbName.indexOf(HSQL_PART) != -1) {
+ return ConnectionSource.HSQL_DIALECT;
+ } else {
+ return ConnectionSource.UNKNOWN_DIALECT;
+ }
+ } catch (SQLException sqle) {
+ // we can't do much here
+ }
+
+ return dialectCode;
+ }
+
+ public static SQLDialect getDialectFromCode(int dialectCode) {
+ SQLDialect sqlDialect = null;
+
+ switch (dialectCode) {
+ case ConnectionSource.POSTGRES_DIALECT:
+ sqlDialect = new PostgreSQLDialect();
+
+ break;
+ case ConnectionSource.MYSQL_DIALECT:
+ sqlDialect = new MySQLDialect();
+
+ break;
+ case ConnectionSource.ORACLE_DIALECT:
+ sqlDialect = new OracleDialect();
+
+ break;
+ case ConnectionSource.MSSQL_DIALECT:
+ sqlDialect = new MsSQLDialect();
+
+ break;
+ case ConnectionSource.HSQL_DIALECT:
+ sqlDialect = new HSQLDBDialect();
+
+ break;
+ }
+ return sqlDialect;
+ }
+
+ /**
+ * This method handles cases where the
+ * {@link DatabaseMetaData#supportsGetGeneratedKeys} method is missing in the
+ * JDBC driver implementation.
+ */
+ public boolean supportsGetGeneratedKeys(DatabaseMetaData meta) {
+ try {
+ //
+ // invoking JDK 1.4 method by reflection
+ //
+ return ((Boolean) DatabaseMetaData.class.getMethod("supportsGetGeneratedKeys", null).invoke(meta, null)).booleanValue();
+ } catch(Throwable e) {
+ getLogger().info("Could not call supportsGetGeneratedKeys method. This may be recoverable");
+ return false;
+ }
+ }
+
+/**
+ * This method handles cases where the
+ * {@link DatabaseMetaData#supportsBatchUpdates} method is missing in the
+ * JDBC driver implementation.
+ */
+ public boolean supportsBatchUpdates(DatabaseMetaData meta) {
+ try {
+ return meta.supportsBatchUpdates();
+ } catch(Throwable e) {
+ getLogger().info("Missing DatabaseMetaData.supportsBatchUpdates method.");
+ return false;
+ }
+ }
+}
diff --git a/src/main/java/org/apache/log4j/receivers/db/dialect/db2.sql b/src/main/java/org/apache/log4j/receivers/db/dialect/db2.sql
new file mode 100644
index 0000000..47d2164
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/db/dialect/db2.sql
@@ -0,0 +1,64 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# This SQL script creates the required tables by org.apache.log4j.db.DBAppender and
+# org.apache.log4j.db.DBReceiver.
+#
+# It is intended for IBM DB2 databases.
+#
+# WARNING WARNING WARNING WARNING
+# =================================
+# This SQL script has not been tested on an actual DB2
+# instance. It may contain errors or even invalid SQL
+# statements.
+
+DROP TABLE logging_event_property;
+DROP TABLE logging_event_exception;
+DROP TABLE logging_event;
+
+CREATE TABLE logging_event
+ (
+ sequence_number BIGINT NOT NULL,
+ timestamp BIGINT NOT NULL,
+ rendered_message VARCHAR(4000) NOT NULL,
+ logger_name VARCHAR(254) NOT NULL,
+ level_string VARCHAR(254) NOT NULL,
+ ndc VARCHAR(4000),
+ thread_name VARCHAR(254),
+ reference_flag SMALLINT,
+ caller_filename VARCHAR(254) NOT NULL,
+ caller_class VARCHAR(254) NOT NULL,
+ caller_method VARCHAR(254) NOT NULL,
+ caller_line CHAR(4) NOT NULL,
+ event_id INTEGER GENERATED ALWAYS AS IDENTITY (START WITH 1)
+ );
+
+CREATE TABLE logging_event_property
+ (
+ event_id INTEGER NOT NULL,
+ mapped_key VARCHAR(254) NOT NULL,
+ mapped_value VARCHAR(1024),
+ PRIMARY KEY(event_id, mapped_key),
+ FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
+ );
+
+CREATE TABLE logging_event_exception
+ (
+ event_id INTEGER NOT NULL,
+ i SMALLINT NOT NULL,
+ trace_line VARCHAR(254) NOT NULL,
+ PRIMARY KEY(event_id, i),
+ FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
+ );
diff --git a/src/main/java/org/apache/log4j/receivers/db/dialect/db2l.sql b/src/main/java/org/apache/log4j/receivers/db/dialect/db2l.sql
new file mode 100644
index 0000000..0f91315
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/db/dialect/db2l.sql
@@ -0,0 +1,61 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# This SQL script creates the required tables by org.apache.log4j.db.DBAppender and
+# org.apache.log4j.db.DBReceiver.
+#
+# It is intended for PostgreSQL databases.
+
+DROP TABLE logging_event_property;
+DROP TABLE logging_event_exception;
+DROP TABLE logging_event;
+
+
+CREATE SEQUENCE logging_event_id_seq MINVALUE 1 START 1;
+
+
+CREATE TABLE logging_event
+ (
+ sequence_number BIGINT NOT NULL,
+ timestamp BIGINT NOT NULL,
+ rendered_message TEXT NOT NULL,
+ logger_name VARCHAR(254) NOT NULL,
+ level_string VARCHAR(254) NOT NULL,
+ ndc TEXT,
+ thread_name VARCHAR(254),
+ reference_flag SMALLINT,
+ caller_filename VARCHAR(254) NOT NULL,
+ caller_class VARCHAR(254) NOT NULL,
+ caller_method VARCHAR(254) NOT NULL,
+ caller_line CHAR(4) NOT NULL,
+ event_id INT IDENTITY GENERATED ALWAYS PRIMARY KEY
+ );
+
+CREATE TABLE logging_event_property
+ (
+ event_id INT NOT NULL,
+ mapped_key VARCHAR(254) NOT NULL,
+ mapped_value VARCHAR(1024),
+ PRIMARY KEY(event_id, mapped_key),
+ FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
+ );
+
+CREATE TABLE logging_event_exception
+ (
+ event_id INT NOT NULL,
+ i SMALLINT NOT NULL,
+ trace_line VARCHAR(254) NOT NULL,
+ PRIMARY KEY(event_id, i),
+ FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
+ );
diff --git a/src/main/java/org/apache/log4j/receivers/db/dialect/hsqldb.sql b/src/main/java/org/apache/log4j/receivers/db/dialect/hsqldb.sql
new file mode 100644
index 0000000..50f8f78
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/db/dialect/hsqldb.sql
@@ -0,0 +1,60 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// This SQL script creates the required tables by
+// org.apache.log4j.db.DBAppender and org.apache.log4j.db.DBReceiver.
+//
+// It is intended for HSQLDB.
+//
+
+DROP TABLE logging_event_exception IF EXISTS;
+DROP TABLE logging_event_property IF EXISTS;
+DROP TABLE logging_event IF EXISTS;
+
+
+CREATE TABLE logging_event
+ (
+ sequence_number BIGINT NOT NULL,
+ timestamp BIGINT NOT NULL,
+ rendered_message LONGVARCHAR NOT NULL,
+ logger_name VARCHAR NOT NULL,
+ level_string VARCHAR NOT NULL,
+ ndc LONGVARCHAR,
+ thread_name VARCHAR,
+ reference_flag SMALLINT,
+ caller_filename VARCHAR,
+ caller_class VARCHAR,
+ caller_method VARCHAR,
+ caller_line CHAR(4),
+ event_id INT NOT NULL IDENTITY
+ );
+
+
+CREATE TABLE logging_event_property
+ (
+ event_id INT NOT NULL,
+ mapped_key VARCHAR(254) NOT NULL,
+ mapped_value LONGVARCHAR,
+ PRIMARY KEY(event_id, mapped_key),
+ FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
+ );
+
+CREATE TABLE logging_event_exception
+ (
+ event_id INT NOT NULL,
+ i SMALLINT NOT NULL,
+ trace_line VARCHAR NOT NULL,
+ PRIMARY KEY(event_id, i),
+ FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
+ );
diff --git a/src/main/java/org/apache/log4j/receivers/db/dialect/mssql.sql b/src/main/java/org/apache/log4j/receivers/db/dialect/mssql.sql
new file mode 100644
index 0000000..d87e0a0
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/db/dialect/mssql.sql
@@ -0,0 +1,61 @@
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements. See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"); you may not use this file except in compliance with
+-- the License. You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+--
+-- This SQL script creates the required tables by org.apache.log4j.db.DBAppender and
+-- org.apache.log4j.db.DBReceiver.
+--
+-- It is intended for MS SQL Server databases. This has been tested with version 7.0.
+
+DROP TABLE logging_event_property
+DROP TABLE logging_event_exception
+DROP TABLE logging_event
+
+CREATE TABLE logging_event
+ (
+ sequence_number DECIMAL(20) NOT NULL,
+ timestamp DECIMAL(20) NOT NULL,
+ rendered_message VARCHAR(4000) NOT NULL,
+ logger_name VARCHAR(254) NOT NULL,
+ level_string VARCHAR(254) NOT NULL,
+ ndc VARCHAR(4000),
+ thread_name VARCHAR(254),
+ reference_flag SMALLINT,
+ caller_filename VARCHAR(254) NOT NULL,
+ caller_class VARCHAR(254) NOT NULL,
+ caller_method VARCHAR(254) NOT NULL,
+ caller_line CHAR(4) NOT NULL,
+ event_id INT NOT NULL identity,
+ PRIMARY KEY(event_id)
+ )
+
+CREATE TABLE logging_event_property
+ (
+ event_id INT NOT NULL,
+ mapped_key VARCHAR(254) NOT NULL,
+ mapped_value VARCHAR(1024),
+ PRIMARY KEY(event_id, mapped_key),
+ FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
+ )
+
+CREATE TABLE logging_event_exception
+ (
+ event_id INT NOT NULL,
+ i SMALLINT NOT NULL,
+ trace_line VARCHAR(254) NOT NULL,
+ PRIMARY KEY(event_id, i),
+ FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
+ )
+
diff --git a/src/main/java/org/apache/log4j/receivers/db/dialect/mysql.sql b/src/main/java/org/apache/log4j/receivers/db/dialect/mysql.sql
new file mode 100644
index 0000000..e3a2be1
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/db/dialect/mysql.sql
@@ -0,0 +1,71 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+#
+#
+# This SQL script creates the required tables by org.apache.log4j.db.DBAppender and
+# org.apache.log4j.db.DBReceiver.
+#
+# It is intended for MySQL databases. It has been tested on MySQL 4.1.1 with
+# INNODB tables.
+
+
+BEGIN;
+DROP TABLE IF EXISTS logging_event_property;
+DROP TABLE IF EXISTS logging_event_exception;
+DROP TABLE IF EXISTS logging_event;
+COMMIT;
+
+
+BEGIN;
+CREATE TABLE logging_event
+ (
+ sequence_number BIGINT NOT NULL,
+ timestamp BIGINT NOT NULL,
+ rendered_message TEXT NOT NULL,
+ logger_name VARCHAR(254) NOT NULL,
+ level_string VARCHAR(254) NOT NULL,
+ ndc TEXT,
+ thread_name VARCHAR(254),
+ reference_flag SMALLINT,
+ caller_filename VARCHAR(254) NOT NULL,
+ caller_class VARCHAR(254) NOT NULL,
+ caller_method VARCHAR(254) NOT NULL,
+ caller_line CHAR(4) NOT NULL,
+ event_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
+ );
+COMMIT;
+
+BEGIN;
+CREATE TABLE logging_event_property
+ (
+ event_id INT NOT NULL,
+ mapped_key VARCHAR(254) NOT NULL,
+ mapped_value TEXT,
+ PRIMARY KEY(event_id, mapped_key),
+ FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
+ );
+COMMIT;
+
+BEGIN;
+CREATE TABLE logging_event_exception
+ (
+ event_id INT NOT NULL,
+ i SMALLINT NOT NULL,
+ trace_line VARCHAR(254) NOT NULL,
+ PRIMARY KEY(event_id, i),
+ FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
+ );
+COMMIT;
diff --git a/src/main/java/org/apache/log4j/receivers/db/dialect/oracle.sql b/src/main/java/org/apache/log4j/receivers/db/dialect/oracle.sql
new file mode 100644
index 0000000..84bf9e5
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/db/dialect/oracle.sql
@@ -0,0 +1,77 @@
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements. See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"); you may not use this file except in compliance with
+-- the License. You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+--
+-- This SQL script creates the required tables by org.apache.log4j.db.DBAppender and
+-- org.apache.log4j.db.DBReceiver.
+--
+-- It is intended for Oracle databases.
+
+-- Tested successfully on Oracle9i Release 9.2.0.3.0 by James Stauffer
+-- Tested successfully on Oracle9i Release by Elias Ross
+
+-- The following lines are useful in cleaning any previous tables
+
+--drop TRIGGER logging_event_id_seq_trig;
+--drop SEQUENCE logging_event_id_seq;
+--drop table logging_event_property;
+--drop table logging_event_exception;
+--drop table logging_event;
+
+CREATE SEQUENCE logging_event_id_seq MINVALUE 1 START WITH 1;
+
+CREATE TABLE logging_event
+ (
+ sequence_number NUMBER(20) NOT NULL,
+ timestamp NUMBER(20) NOT NULL,
+ rendered_message VARCHAR2(4000) NOT NULL,
+ logger_name VARCHAR2(254) NOT NULL,
+ level_string VARCHAR2(254) NOT NULL,
+ ndc VARCHAR2(4000),
+ thread_name VARCHAR2(254),
+ reference_flag NUMBER(5),
+ caller_filename VARCHAR2(254) NOT NULL,
+ caller_class VARCHAR2(254) NOT NULL,
+ caller_method VARCHAR2(254) NOT NULL,
+ caller_line CHAR(4) NOT NULL,
+ event_id NUMBER(10) PRIMARY KEY
+ );
+
+CREATE OR REPLACE TRIGGER logging_event_id_seq_trig
+BEFORE INSERT ON logging_event
+FOR EACH ROW
+BEGIN
+ SELECT logging_event_id_seq.nextval
+ INTO :new.sequence_number FROM dual;
+END;
+
+CREATE TABLE logging_event_property
+ (
+ event_id NUMBER(10) NOT NULL,
+ mapped_key VARCHAR2(254) NOT NULL,
+ mapped_value VARCHAR2(1024),
+ PRIMARY KEY(event_id, mapped_key),
+ FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
+ );
+
+CREATE TABLE logging_event_exception
+ (
+ event_id NUMBER(10) NOT NULL,
+ i NUMBER(5) NOT NULL,
+ trace_line VARCHAR2(254) NOT NULL,
+ PRIMARY KEY(event_id, i),
+ FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
+ );
+
diff --git a/src/main/java/org/apache/log4j/receivers/db/dialect/postgresql.sql b/src/main/java/org/apache/log4j/receivers/db/dialect/postgresql.sql
new file mode 100644
index 0000000..c38757b
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/db/dialect/postgresql.sql
@@ -0,0 +1,63 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+## This SQL script creates the required tables by org.apache.log4j.db.DBAppender and
+# org.apache.log4j.db.DBReceiver.
+#
+# It is intended for PostgreSQL databases.
+
+DROP TABLE logging_event_property;
+DROP TABLE logging_event_exception;
+DROP SEQUENCE logging_event_id_seq;
+DROP TABLE logging_event;
+
+
+CREATE SEQUENCE logging_event_id_seq MINVALUE 1 START 1;
+
+
+CREATE TABLE logging_event
+ (
+ sequence_number BIGINT NOT NULL,
+ timestamp BIGINT NOT NULL,
+ rendered_message TEXT NOT NULL,
+ logger_name VARCHAR(254) NOT NULL,
+ level_string VARCHAR(254) NOT NULL,
+ ndc TEXT,
+ thread_name VARCHAR(254),
+ reference_flag SMALLINT,
+ caller_filename VARCHAR(254) NOT NULL,
+ caller_class VARCHAR(254) NOT NULL,
+ caller_method VARCHAR(254) NOT NULL,
+ caller_line CHAR(4) NOT NULL,
+ event_id INT DEFAULT nextval('logging_event_id_seq') PRIMARY KEY
+ );
+
+CREATE TABLE logging_event_property
+ (
+ event_id INT NOT NULL,
+ mapped_key VARCHAR(254) NOT NULL,
+ mapped_value VARCHAR(1024),
+ PRIMARY KEY(event_id, mapped_key),
+ FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
+ );
+
+CREATE TABLE logging_event_exception
+ (
+ event_id INT NOT NULL,
+ i SMALLINT NOT NULL,
+ trace_line VARCHAR(254) NOT NULL,
+ PRIMARY KEY(event_id, i),
+ FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
+ );
diff --git a/src/main/java/org/apache/log4j/receivers/db/package.html b/src/main/java/org/apache/log4j/receivers/db/package.html
new file mode 100644
index 0000000..55652fb
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/db/package.html
@@ -0,0 +1,36 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<html>
+<body>
+
+<p>The org.apache.log4j.db package provides means to append logging events
+into various databases. The persisted data can be later read back using
+{@link org.apache.log4j.db.DBReceiver}.
+</p>
+
+<p>Most popular database systems, such as PostgreSQL, MySQL, Oracle, DB2 and MsSQL
+are supported.
+</p>
+
+<p>Just as importantly, the way for obtaining JDBC connections is pluggable. Connections can
+be obtained through the tradinal way of DriverManager, or alternatively as a DataSource.
+A DataSource can be instantiated directly or it can obtained through JNDI.
+</p>
+
+</body>
+</html>
diff --git a/src/main/java/org/apache/log4j/receivers/helpers/UtilLoggingLevel.java b/src/main/java/org/apache/log4j/receivers/helpers/UtilLoggingLevel.java
new file mode 100644
index 0000000..0dc8f38
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/helpers/UtilLoggingLevel.java
@@ -0,0 +1,238 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.receivers.helpers;
+
+import org.apache.log4j.Level;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * An extension of the Level class that provides support for java.util.logging
+ * Levels.
+ *
+ *
+ * @author Scott Deboy (sdeboy@apache.org)
+ */
+
+public class UtilLoggingLevel extends Level {
+
+ /**
+ * Serialization version id.
+ */
+ private static final long serialVersionUID = 909301162611820211L;
+
+ /**
+ * Numerical value for SEVERE.
+ */
+ public static final int SEVERE_INT = 22000;
+ /**
+ * Numerical value for WARNING.
+ */
+ public static final int WARNING_INT = 21000;
+
+ //INFO level defined in parent as 20000..no need to redefine here
+
+ /**
+ * Numerical value for CONFIG.
+ */
+ public static final int CONFIG_INT = 14000;
+ /**
+ * Numerical value for FINE.
+ */
+ public static final int FINE_INT = 13000;
+ /**
+ * Numerical value for FINER.
+ */
+ public static final int FINER_INT = 12000;
+ /**
+ * Numerical value for FINEST.
+ */
+ public static final int FINEST_INT = 11000;
+ /**
+ * Numerical value for UNKNOWN.
+ */
+ public static final int UNKNOWN_INT = 10000;
+
+ /**
+ * SEVERE.
+ */
+ public static final UtilLoggingLevel SEVERE =
+ new UtilLoggingLevel(SEVERE_INT, "SEVERE", 0);
+ /**
+ * WARNING.
+ */
+ public static final UtilLoggingLevel WARNING =
+ new UtilLoggingLevel(WARNING_INT, "WARNING", 4);
+ /**
+ * INFO.
+ */
+ //note: we've aligned the int values of the java.util.logging INFO level with log4j's level
+ public static final UtilLoggingLevel INFO =
+ new UtilLoggingLevel(INFO_INT, "INFO", 5);
+ /**
+ * CONFIG.
+ */
+ public static final UtilLoggingLevel CONFIG =
+ new UtilLoggingLevel(CONFIG_INT, "CONFIG", 6);
+ /**
+ * FINE.
+ */
+ public static final UtilLoggingLevel FINE =
+ new UtilLoggingLevel(FINE_INT, "FINE", 7);
+ /**
+ * FINER.
+ */
+ public static final UtilLoggingLevel FINER =
+ new UtilLoggingLevel(FINER_INT, "FINER", 8);
+ /**
+ * FINEST.
+ */
+ public static final UtilLoggingLevel FINEST =
+ new UtilLoggingLevel(FINEST_INT, "FINEST", 9);
+
+ /**
+ * Create new instance.
+ * @param level numeric value for level.
+ * @param levelStr symbolic name for level.
+ * @param syslogEquivalent Equivalent syslog severity.
+ */
+ protected UtilLoggingLevel(final int level,
+ final String levelStr,
+ final int syslogEquivalent) {
+ super(level, levelStr, syslogEquivalent);
+ }
+
+ /**
+ * Convert an integer passed as argument to a level. If the
+ * conversion fails, then this method returns the specified default.
+ * @param val numeric value.
+ * @param defaultLevel level to be returned if no level matches
+ * numeric value.
+ * @return matching level or default level.
+ */
+ public static UtilLoggingLevel toLevel(final int val,
+ final UtilLoggingLevel defaultLevel) {
+ switch (val) {
+ case SEVERE_INT:
+ return SEVERE;
+
+ case WARNING_INT:
+ return WARNING;
+
+ case INFO_INT:
+ return INFO;
+
+ case CONFIG_INT:
+ return CONFIG;
+
+ case FINE_INT:
+ return FINE;
+
+ case FINER_INT:
+ return FINER;
+
+ case FINEST_INT:
+ return FINEST;
+
+ default:
+ return defaultLevel;
+ }
+ }
+
+ /**
+ * Gets level matching numeric value.
+ * @param val numeric value.
+ * @return matching level or UtilLoggerLevel.FINEST if no match.
+ */
+ public static Level toLevel(final int val) {
+ return toLevel(val, FINEST);
+ }
+
+ /**
+ * Gets list of supported levels.
+ * @return list of supported levels.
+ */
+ public static List getAllPossibleLevels() {
+ ArrayList list = new ArrayList();
+ list.add(FINE);
+ list.add(FINER);
+ list.add(FINEST);
+ list.add(INFO);
+ list.add(CONFIG);
+ list.add(WARNING);
+ list.add(SEVERE);
+ return list;
+ }
+
+ /**
+ * Get level with specified symbolic name.
+ * @param s symbolic name.
+ * @return matching level or Level.DEBUG if no match.
+ */
+ public static Level toLevel(final String s) {
+ return toLevel(s, Level.DEBUG);
+ }
+
+
+ /**
+ * Get level with specified symbolic name.
+ * @param sArg symbolic name.
+ * @param defaultLevel level to return if no match.
+ * @return matching level or defaultLevel if no match.
+ */
+ public static Level toLevel(final String sArg,
+ final Level defaultLevel) {
+ if (sArg == null) {
+ return defaultLevel;
+ }
+
+ String s = sArg.toUpperCase();
+
+ if (s.equals("SEVERE")) {
+ return SEVERE;
+ }
+
+ //if(s.equals("FINE")) return Level.FINE;
+ if (s.equals("WARNING")) {
+ return WARNING;
+ }
+
+ if (s.equals("INFO")) {
+ return INFO;
+ }
+
+ if (s.equals("CONFI")) {
+ return CONFIG;
+ }
+
+ if (s.equals("FINE")) {
+ return FINE;
+ }
+
+ if (s.equals("FINER")) {
+ return FINER;
+ }
+
+ if (s.equals("FINEST")) {
+ return FINEST;
+ }
+ return defaultLevel;
+ }
+
+}
diff --git a/src/main/java/org/apache/log4j/receivers/net/AddressBased.java b/src/main/java/org/apache/log4j/receivers/net/AddressBased.java
new file mode 100644
index 0000000..b1d39d8
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/net/AddressBased.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.receivers.net;
+
+
+/**
+ * Net based entities that 'work with' an Address
+ * should consider implementing this
+ * interface so that they can be treated generically.
+ *
+ * @author Paul Smith (psmith@apache.org)
+ *
+ */
+public interface AddressBased extends NetworkBased {
+ /**
+ * Returns a String representation of the Address this instance
+ * encompasses.
+ * @return String representation of the Address
+ */
+ String getAddress();
+}
diff --git a/src/main/java/org/apache/log4j/receivers/net/JMSReceiver.java b/src/main/java/org/apache/log4j/receivers/net/JMSReceiver.java
new file mode 100644
index 0000000..fada8e7
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/net/JMSReceiver.java
@@ -0,0 +1,291 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.receivers.net;
+
+import org.apache.log4j.component.plugins.Plugin;
+import org.apache.log4j.component.plugins.Receiver;
+import org.apache.log4j.spi.LoggingEvent;
+
+import javax.jms.*;
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.NameNotFoundException;
+import javax.naming.NamingException;
+import java.io.FileInputStream;
+import java.util.Properties;
+
+/**
+ JMSReceiver receives a remote logging event on a configured
+ JSM topic and "posts" it to a LoggerRepository as if the event was
+ generated locally. This class is designed to receive events from
+ the JMSAppender class (or classes that send compatible events).
+
+ <p>Once the event has been "posted", it will be handled by the
+ appenders currently configured in the LoggerRespository.
+
+ <p>This implementation borrows heavily from the JMSSink
+ implementation.
+
+ @author Mark Womack
+ @author Paul Smith
+ @author Stephen Pain
+*/
+public class JMSReceiver extends Receiver implements MessageListener {
+
+ private boolean active = false;
+
+ protected String topicFactoryName;
+ protected String topicName;
+ protected String userId;
+ protected String password;
+ protected TopicConnection topicConnection;
+ protected String jndiPath;
+
+ private String remoteInfo;
+ private String providerUrl;
+
+ public JMSReceiver() { }
+
+ public JMSReceiver(String _topicFactoryName, String _topicName,
+ String _userId, String _password, String _jndiPath) {
+ topicFactoryName = _topicFactoryName;
+ topicName = _topicName;
+ userId = _userId;
+ password = _password;
+ jndiPath = _jndiPath;
+ }
+
+ /**
+ * Sets the path to a properties file containing
+ * the initial context and jndi provider url
+ */
+ public void setJndiPath(String _jndiPath) {
+ jndiPath = _jndiPath;
+ }
+
+ /**
+ * Gets the path to a properties file containing
+ * the initial context and jndi provider url
+ */
+ public String getJndiPath() {
+ return jndiPath;
+ }
+
+ /**
+ Sets the JMS topic factory name to use when creating the
+ JMS connection. */
+ public void setTopicFactoryName(String _topicFactoryName) {
+ topicFactoryName = _topicFactoryName;
+ }
+
+ /**
+ Gets the curernt JMS topic factory name property. */
+ public String getTopicFactoryName() {
+ return topicFactoryName;
+ }
+
+ /**
+ * Sets the JMS topic name to use when creating the
+ * JMS connection.
+ */
+ public void setTopicName(String _topicName) {
+ topicName = _topicName;
+ }
+
+ /**
+ * Gets the curernt JMS topic name property.
+ */
+ public String getTopicName() {
+ return topicName;
+ }
+
+ /**
+ Sets the user id to use when creating the
+ JMS connection. */
+ public void setUserId(String _userId) {
+ userId = _userId;
+ }
+
+ /**
+ * Gets the current user id property.
+ */
+ public String getUserId() {
+ return userId;
+ }
+
+ /**
+ * Sets the password to use when creating the
+ * JMS connection.
+ */
+ public void setPassword(String _password) {
+ password = _password;
+ }
+
+ /**
+ * Gets the curernt password property.
+ */
+ public String getPassword() {
+ return password;
+ }
+
+ /**
+ * Returns true if the receiver is the same class and they are
+ * configured for the same properties, and super class also considers
+ * them to be equivalent. This is used by PluginRegistry when determining
+ * if the a similarly configured receiver is being started.
+ *
+ * @param testPlugin The plugin to test equivalency against.
+ * @return boolean True if the testPlugin is equivalent to this plugin.
+ */
+ public boolean isEquivalent(Plugin testPlugin) {
+ // only do full check if an instance of this class
+ if (testPlugin instanceof JMSReceiver) {
+
+ JMSReceiver receiver = (JMSReceiver)testPlugin;
+
+ // check for same topic name and super class equivalency
+ return (
+ topicFactoryName.equals(receiver.getTopicFactoryName()) &&
+ (jndiPath == null || jndiPath.equals(receiver.getJndiPath())) &&
+ super.isEquivalent(testPlugin)
+ );
+ }
+
+ return false;
+ }
+
+ /**
+ Returns true if this receiver is active. */
+ public synchronized boolean isActive() {
+ return active;
+ }
+
+ /**
+ Sets the flag to indicate if receiver is active or not. */
+ protected synchronized void setActive(boolean _active) {
+ active = _active;
+ }
+
+ /**
+ Starts the JMSReceiver with the current options. */
+ public void activateOptions() {
+ if (!isActive()) {
+ try {
+ remoteInfo = topicFactoryName + ":" + topicName;
+
+ Context ctx = null;
+ if (jndiPath == null || jndiPath.equals("")) {
+ ctx = new InitialContext();
+ } else {
+ FileInputStream is = new FileInputStream(jndiPath);
+ Properties p = new Properties();
+ p.load(is);
+ is.close();
+ ctx = new InitialContext(p);
+ }
+
+ // give some more flexibility about the choice of a tab name
+ providerUrl = (String)ctx.getEnvironment().get(Context.PROVIDER_URL);
+ TopicConnectionFactory topicConnectionFactory;
+ topicConnectionFactory =
+ (TopicConnectionFactory) lookup(ctx, topicFactoryName);
+
+ if (userId != null && password != null) {
+ topicConnection =
+ topicConnectionFactory.createTopicConnection(userId, password);
+ } else {
+ topicConnection =
+ topicConnectionFactory.createTopicConnection();
+ }
+
+ TopicSession topicSession =
+ topicConnection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ Topic topic = (Topic)ctx.lookup(topicName);
+
+ TopicSubscriber topicSubscriber = topicSession.createSubscriber(topic);
+
+ topicSubscriber.setMessageListener(this);
+
+ topicConnection.start();
+
+ setActive(true);
+ } catch(Exception e) {
+ setActive(false);
+ if (topicConnection != null) {
+ try {
+ topicConnection.close();
+ } catch (Exception e2) {
+ // do nothing
+ }
+ topicConnection = null;
+ }
+ getLogger().error("Could not start JMSReceiver.", e);
+ }
+ }
+ }
+
+ /**
+ Called when the receiver should be stopped. */
+ public synchronized void shutdown() {
+ if (isActive()) {
+ // mark this as no longer running
+ setActive(false);
+
+ if (topicConnection != null) {
+ try {
+ topicConnection.close();
+ } catch (Exception e) {
+ // do nothing
+ }
+ topicConnection = null;
+ }
+ }
+ }
+
+ public void onMessage(Message message) {
+ try {
+ if(message instanceof ObjectMessage) {
+ // get the logging event and post it to the repository
+ ObjectMessage objectMessage = (ObjectMessage) message;
+ LoggingEvent event = (LoggingEvent) objectMessage.getObject();
+
+ // store the known remote info in an event property
+ event.setProperty("log4j.remoteSourceInfo", remoteInfo);
+ event.setProperty("log4j.jmsProviderUrl", providerUrl);
+
+ doPost(event);
+ } else {
+ getLogger().warn("Received message is of type "+message.getJMSType()
+ +", was expecting ObjectMessage.");
+ }
+ } catch(Exception e) {
+ getLogger().error("Exception thrown while processing incoming message.", e);
+ }
+ }
+
+ protected Object lookup(Context ctx, String name) throws NamingException {
+ try {
+ return ctx.lookup(name);
+ } catch(NameNotFoundException e) {
+ getLogger().error("Could not find name ["+name+"].");
+ throw e;
+ }
+ }
+
+}
diff --git a/src/main/java/org/apache/log4j/receivers/net/JMSReceiverBeanInfo.java b/src/main/java/org/apache/log4j/receivers/net/JMSReceiverBeanInfo.java
new file mode 100644
index 0000000..13ccef0
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/net/JMSReceiverBeanInfo.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.log4j.receivers.net;
+
+import java.beans.PropertyDescriptor;
+import java.beans.SimpleBeanInfo;
+
+
+/**
+ * BeanInfo class for the JMSReceiver.
+ *
+ * @author Paul Smith <psmith@apache.org>
+ *
+ */
+public class JMSReceiverBeanInfo extends SimpleBeanInfo {
+
+ /* (non-Javadoc)
+ * @see java.beans.BeanInfo#getPropertyDescriptors()
+ */
+ public PropertyDescriptor[] getPropertyDescriptors() {
+
+ try {
+
+ return new PropertyDescriptor[] {
+ new PropertyDescriptor("name", JMSReceiver.class),
+ new PropertyDescriptor("topicFactoryName", JMSReceiver.class),
+ new PropertyDescriptor("topicName", JMSReceiver.class),
+ new PropertyDescriptor("threshold", JMSReceiver.class),
+ new PropertyDescriptor("jndiPath", JMSReceiver.class),
+ new PropertyDescriptor("userId",
+ JMSReceiver.class),
+ };
+ } catch (Exception e) {
+ }
+
+ return null;
+ }
+}
diff --git a/src/main/java/org/apache/log4j/receivers/net/MulticastAppender.java b/src/main/java/org/apache/log4j/receivers/net/MulticastAppender.java
new file mode 100644
index 0000000..a6f76b9
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/net/MulticastAppender.java
@@ -0,0 +1,346 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.receivers.net;
+
+import org.apache.log4j.AppenderSkeleton;
+
+import org.apache.log4j.component.helpers.Constants;
+import org.apache.log4j.helpers.LogLog;
+import org.apache.log4j.net.ZeroConfSupport;
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.xml.XMLLayout;
+
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.InetAddress;
+import java.net.MulticastSocket;
+import java.net.UnknownHostException;
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ * Multicast-based Appender. Works in conjunction with the MulticastReceiver, which expects
+ * a LoggingEvent encoded using XMLLayout.
+ *
+ * Sends log information as a multicast datagrams.
+ *
+ * <p>Messages are not sent as LoggingEvent objects but as text after
+ * applying XMLLayout.
+ *
+ * <p>The port and remoteHost properties can be set in configuration properties.
+ * By setting the remoteHost to a broadcast address any number of clients can
+ * listen for log messages.
+ *
+ * <p>This was inspired and really extended/copied from {@link SocketAppender}. Please
+ * see the docs for the proper credit to the authors of that class.
+ *
+ * @author <a href="mailto:kbrown@versatilesolutions.com">Kevin Brown</a>
+ * @author Scott Deboy <sdeboy@apache.org>
+ *
+ */
+public class MulticastAppender extends AppenderSkeleton implements PortBased {
+ /**
+ The default port number for the multicast packets. (9991).
+ */
+ static final int DEFAULT_PORT = 9991;
+
+ /**
+ * The MulticastDNS zone advertised by a MulticastAppender
+ * the MulticastAppender also adds a 'multicastAddress' property with the multicast address value as a string
+ */
+ public static final String ZONE = "_log4j_xml_mcast_appender.local.";
+
+ /**
+ We remember host name as String in addition to the resolved
+ InetAddress so that it can be returned via getOption().
+ */
+ String hostname;
+ String remoteHost;
+ String application;
+ int timeToLive;
+ InetAddress address;
+ int port = DEFAULT_PORT;
+ MulticastSocket outSocket;
+ private String encoding;
+
+ private boolean locationInfo = false;
+ private boolean advertiseViaMulticastDNS;
+ private ZeroConfSupport zeroConf;
+
+ public MulticastAppender() {
+ super(false);
+ }
+
+ /**
+ Open the multicast sender for the <b>RemoteHost</b> and <b>Port</b>.
+ */
+ public void activateOptions() {
+ try {
+ hostname = InetAddress.getLocalHost().getHostName();
+ } catch (UnknownHostException uhe) {
+ try {
+ hostname = InetAddress.getLocalHost().getHostAddress();
+ } catch (UnknownHostException uhe2) {
+ hostname = "unknown";
+ }
+ }
+
+ //allow system property of application to be primary
+ if (application == null) {
+ application = System.getProperty(Constants.APPLICATION_KEY);
+ } else {
+ if (System.getProperty(Constants.APPLICATION_KEY) != null) {
+ application = application + "-" + System.getProperty(Constants.APPLICATION_KEY);
+ }
+ }
+
+ if(remoteHost != null) {
+ address = getAddressByName(remoteHost);
+ } else {
+ String err = "The RemoteHost property is required for MulticastAppender named "+ name;
+ LogLog.error(err);
+ throw new IllegalStateException(err);
+ }
+
+ if (layout == null) {
+ layout = new XMLLayout();
+ }
+
+ if (advertiseViaMulticastDNS) {
+ Map properties = new HashMap();
+ properties.put("multicastAddress", remoteHost);
+ zeroConf = new ZeroConfSupport(ZONE, port, getName(), properties);
+ zeroConf.advertise();
+ }
+ connect();
+ super.activateOptions();
+ }
+
+ /**
+ Close this appender.
+ <p>This will mark the appender as closed and
+ call then {@link #cleanUp} method.
+ */
+ public synchronized void close() {
+ if (closed) {
+ return;
+ }
+
+ this.closed = true;
+ if (advertiseViaMulticastDNS) {
+ zeroConf.unadvertise();
+ }
+ cleanUp();
+ }
+
+ /**
+ Close the Socket and release the underlying
+ connector thread if it has been created
+ */
+ public void cleanUp() {
+ if (outSocket != null) {
+ try {
+ outSocket.close();
+ } catch (Exception e) {
+ LogLog.error("Could not close outSocket.", e);
+ }
+
+ outSocket = null;
+ }
+ }
+
+ void connect() {
+ if (this.address == null) {
+ return;
+ }
+
+ try {
+ // First, close the previous connection if any.
+ cleanUp();
+ outSocket = new MulticastSocket();
+ outSocket.setTimeToLive(timeToLive);
+ } catch (IOException e) {
+ LogLog.error("Error in connect method of MulticastAppender named "+name, e);
+ }
+ }
+
+ public void append(LoggingEvent event) {
+ if (event == null) {
+ return;
+ }
+
+ if(locationInfo) {
+ event.getLocationInformation();
+ }
+
+ if (outSocket != null) {
+ event.setProperty(Constants.HOSTNAME_KEY, hostname);
+
+ if (application != null) {
+ event.setProperty(Constants.APPLICATION_KEY, application);
+ }
+
+ if(locationInfo) {
+ event.getLocationInformation();
+ }
+
+
+ try {
+ StringBuffer buf = new StringBuffer(layout.format(event));
+
+ byte[] payload;
+ if(encoding == null) {
+ payload = buf.toString().getBytes();
+ } else {
+ payload = buf.toString().getBytes(encoding);
+ }
+
+ DatagramPacket dp =
+ new DatagramPacket(payload, payload.length, address, port);
+ outSocket.send(dp);
+ } catch (IOException e) {
+ outSocket = null;
+ LogLog.warn("Detected problem with Multicast connection: " + e);
+ }
+ }
+ }
+
+ InetAddress getAddressByName(String host) {
+ try {
+ return InetAddress.getByName(host);
+ } catch (Exception e) {
+ LogLog.error("Could not find address of [" + host + "].", e);
+ return null;
+ }
+ }
+
+ /**
+ The <b>RemoteHost</b> option takes a string value which should be
+ the host name or ipaddress to send the multicast packets.
+ */
+ public void setRemoteHost(String host) {
+ remoteHost = host;
+ }
+
+ /**
+ Returns value of the <b>RemoteHost</b> option.
+ */
+ public String getRemoteHost() {
+ return remoteHost;
+ }
+
+ /**
+ The <b>LocationInfo</b> option takes a boolean value. If true,
+ the information sent to the remote host will include location
+ information. By default no location information is sent to the server.
+ */
+ public void setLocationInfo(boolean locationInfo) {
+ this.locationInfo = locationInfo;
+ }
+
+ /**
+ * Returns value of the <b>LocationInfo</b> option.
+ */
+ public boolean getLocationInfo() {
+ return locationInfo;
+ }
+
+ /**
+ The <b>Encoding</b> option specifies how the bytes are encoded. If this option is not specified,
+ the System encoding is used.
+ */
+ public void setEncoding(String encoding) {
+ this.encoding = encoding;
+ }
+
+ /**
+ Returns value of the <b>Encoding</b> option.
+ */
+ public String getEncoding() {
+ return encoding;
+ }
+ /**
+ The <b>App</b> option takes a string value which should be the name of the application getting logged.
+ If property was already set (via system property), don't set here.
+ */
+ public void setApplication(String app) {
+ this.application = app;
+ }
+
+ /**
+ Returns value of the <b>App</b> option.
+ */
+ public String getApplication() {
+ return application;
+ }
+
+ /**
+ The <b>Time to live</b> option takes a positive integer representing
+ the time to live value.
+ */
+ public void setTimeToLive(int timeToLive) {
+ this.timeToLive = timeToLive;
+ }
+
+ /**
+ Returns value of the <b>Time to Live</b> option.
+ */
+ public int getTimeToLive() {
+ return timeToLive;
+ }
+
+ /**
+ The <b>Port</b> option takes a positive integer representing
+ the port where multicast packets will be sent.
+ */
+ public void setPort(int port) {
+ this.port = port;
+ }
+
+ /**
+ Returns value of the <b>Port</b> option.
+ */
+ public int getPort() {
+ return port;
+ }
+
+ /* (non-Javadoc)
+ * @see org.apache.log4j.net.NetworkBased#isActive()
+ */
+ public boolean isActive() {
+ // TODO handle active/inactive
+ return true;
+ }
+
+ /**
+ * Gets whether appender requires a layout.
+ * @return false
+ */
+ public boolean requiresLayout() {
+ return true;
+ }
+
+ public boolean isAdvertiseViaMulticastDNS() {
+ return advertiseViaMulticastDNS;
+ }
+
+ public void setAdvertiseViaMulticastDNS(boolean advertiseViaMulticastDNS) {
+ this.advertiseViaMulticastDNS = advertiseViaMulticastDNS;
+ }
+}
diff --git a/src/main/java/org/apache/log4j/receivers/net/MulticastReceiver.java b/src/main/java/org/apache/log4j/receivers/net/MulticastReceiver.java
new file mode 100644
index 0000000..d6b01d7
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/net/MulticastReceiver.java
@@ -0,0 +1,274 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.receivers.net;
+
+
+import org.apache.log4j.component.plugins.Pauseable;
+import org.apache.log4j.component.plugins.Receiver;
+import org.apache.log4j.net.ZeroConfSupport;
+import org.apache.log4j.receivers.spi.Decoder;
+import org.apache.log4j.spi.LoggingEvent;
+
+import java.io.IOException;
+import java.net.*;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+
+/**
+ * Multicast-based receiver. Accepts LoggingEvents encoded using
+ * MulticastAppender and XMLLayout. The the XML data is converted
+ * back to a LoggingEvent and is posted.
+ *
+ * @author Scott Deboy <sdeboy@apache.org>
+ *
+ */
+public class MulticastReceiver extends Receiver implements PortBased,
+ AddressBased, Pauseable {
+ private static final int PACKET_LENGTH = 16384;
+ private int port;
+ private String address;
+ private String encoding;
+ private MulticastSocket socket = null;
+
+ //default to log4j xml decoder
+ private String decoder = "org.apache.log4j.xml.XMLDecoder";
+ private Decoder decoderImpl;
+ private MulticastHandlerThread handlerThread;
+ private MulticastReceiverThread receiverThread;
+ private boolean paused;
+ private boolean advertiseViaMulticastDNS;
+ private ZeroConfSupport zeroConf;
+
+ /**
+ * The MulticastDNS zone advertised by a MulticastReceiver
+ */
+ public static final String ZONE = "_log4j_xml_mcast_receiver.local.";
+
+ public String getDecoder() {
+ return decoder;
+ }
+
+ public void setDecoder(String decoder) {
+ this.decoder = decoder;
+ }
+
+ public int getPort() {
+ return port;
+ }
+
+ public void setPort(int port) {
+ this.port = port;
+ }
+
+ public String getAddress() {
+ return address;
+ }
+
+ /**
+ The <b>Encoding</b> option specifies how the bytes are encoded. If this option is not specified,
+ the system encoding will be used.
+ */
+ public void setEncoding(String encoding) {
+ this.encoding = encoding;
+ }
+
+ /**
+ Returns value of the <b>Encoding</b> option.
+ */
+ public String getEncoding() {
+ return encoding;
+ }
+
+ public synchronized void shutdown() {
+ active = false;
+ if (advertiseViaMulticastDNS) {
+ zeroConf.unadvertise();
+ }
+ if (handlerThread != null) {
+ handlerThread.interrupt();
+ }
+ if (receiverThread != null) {
+ receiverThread.interrupt();
+ }
+ if (socket != null) {
+ socket.close();
+ }
+ }
+
+ public void setAddress(String address) {
+ this.address = address;
+ }
+
+ public boolean isPaused() {
+ return paused;
+ }
+
+ public void setPaused(boolean b) {
+ paused = b;
+ }
+
+ public void activateOptions() {
+ InetAddress addr = null;
+
+ try {
+ Class c = Class.forName(decoder);
+ Object o = c.newInstance();
+
+ if (o instanceof Decoder) {
+ this.decoderImpl = (Decoder) o;
+ }
+ } catch (ClassNotFoundException cnfe) {
+ getLogger().warn("Unable to find decoder", cnfe);
+ } catch (IllegalAccessException iae) {
+ getLogger().warn("Could not construct decoder", iae);
+ } catch (InstantiationException ie) {
+ getLogger().warn("Could not construct decoder", ie);
+ }
+
+ try {
+ addr = InetAddress.getByName(address);
+ } catch (UnknownHostException uhe) {
+ uhe.printStackTrace();
+ }
+
+ try {
+ active = true;
+ socket = new MulticastSocket(port);
+ socket.joinGroup(addr);
+ receiverThread = new MulticastReceiverThread();
+ receiverThread.start();
+ handlerThread = new MulticastHandlerThread();
+ handlerThread.start();
+ if (advertiseViaMulticastDNS) {
+ zeroConf = new ZeroConfSupport(ZONE, port, getName());
+ zeroConf.advertise();
+ }
+
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ }
+ }
+
+ public void setAdvertiseViaMulticastDNS(boolean advertiseViaMulticastDNS) {
+ this.advertiseViaMulticastDNS = advertiseViaMulticastDNS;
+ }
+
+ public boolean isAdvertiseViaMulticastDNS() {
+ return advertiseViaMulticastDNS;
+ }
+
+ class MulticastHandlerThread extends Thread {
+ private List list = new ArrayList();
+
+ public MulticastHandlerThread() {
+ setDaemon(true);
+ }
+
+ public void append(String data) {
+ synchronized (list) {
+ list.add(data);
+ list.notify();
+ }
+ }
+
+ public void run() {
+ ArrayList list2 = new ArrayList();
+
+ while (isAlive()) {
+ synchronized (list) {
+ try {
+ while (list.size() == 0) {
+ list.wait();
+ }
+
+ if (list.size() > 0) {
+ list2.addAll(list);
+ list.clear();
+ }
+ } catch (InterruptedException ie) {
+ }
+ }
+
+ if (list2.size() > 0) {
+ Iterator iter = list2.iterator();
+
+ while (iter.hasNext()) {
+ String data = (String) iter.next();
+ List v = decoderImpl.decodeEvents(data.trim());
+
+ if (v != null) {
+ Iterator eventIter = v.iterator();
+
+ while (eventIter.hasNext()) {
+ if (!isPaused()) {
+ doPost((LoggingEvent) eventIter.next());
+ }
+ }
+ }
+ }
+
+ list2.clear();
+ } else {
+ try {
+ synchronized (this) {
+ wait(1000);
+ }
+ } catch (InterruptedException ie) {
+ }
+ }
+ }
+ }
+ }
+
+ class MulticastReceiverThread extends Thread {
+ public MulticastReceiverThread() {
+ setDaemon(true);
+ }
+
+ public void run() {
+ active = true;
+
+ byte[] b = new byte[PACKET_LENGTH];
+ DatagramPacket p = new DatagramPacket(b, b.length);
+
+ while (active) {
+ try {
+ socket.receive(p);
+
+ //this string constructor which accepts a charset throws an exception if it is
+ //null
+ if (encoding == null) {
+ handlerThread.append(
+ new String(p.getData(), 0, p.getLength()));
+ } else {
+ handlerThread.append(
+ new String(p.getData(), 0, p.getLength(), encoding));
+ }
+ } catch (SocketException se) {
+ //disconnected
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ }
+ }
+
+ getLogger().debug("{}'s thread is ending.", MulticastReceiver.this.getName());
+ }
+ }
+}
diff --git a/src/main/java/org/apache/log4j/receivers/net/MulticastReceiverBeanInfo.java b/src/main/java/org/apache/log4j/receivers/net/MulticastReceiverBeanInfo.java
new file mode 100644
index 0000000..e0ea6a4
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/net/MulticastReceiverBeanInfo.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.log4j.receivers.net;
+
+import java.beans.PropertyDescriptor;
+import java.beans.SimpleBeanInfo;
+
+
+/**
+ * BeanInfo class for the meta-data of the MulticastReceiver.
+ *
+ * @author Paul Smith <psmith@apache.org>
+ *
+ */
+public class MulticastReceiverBeanInfo extends SimpleBeanInfo {
+
+ /* (non-Javadoc)
+ * @see java.beans.BeanInfo#getPropertyDescriptors()
+ */
+ public PropertyDescriptor[] getPropertyDescriptors() {
+
+ try {
+
+ return new PropertyDescriptor[] {
+ new PropertyDescriptor("name", MulticastReceiver.class),
+ new PropertyDescriptor("address", MulticastReceiver.class),
+ new PropertyDescriptor("port", MulticastReceiver.class),
+ new PropertyDescriptor("threshold", MulticastReceiver.class),
+ new PropertyDescriptor("decoder", MulticastReceiver.class),
+ new PropertyDescriptor("advertiseViaMulticastDNS", MulticastReceiver.class),
+ };
+ } catch (Exception e) {
+ }
+
+ return null;
+ }
+}
diff --git a/src/main/java/org/apache/log4j/receivers/net/NetworkBased.java b/src/main/java/org/apache/log4j/receivers/net/NetworkBased.java
new file mode 100644
index 0000000..abbde16
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/net/NetworkBased.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.receivers.net;
+
+/**
+ * The parent of all the Network based interfaces.
+ *
+ * @author Paul Smith (psmith@apache.org)
+ *
+ */
+public interface NetworkBased {
+
+ /**
+ * Get name.
+ * @return name.
+ */
+ String getName();
+
+ /**
+ * Get if item is active.
+ * @return if true, item is active.
+ */
+ boolean isActive();
+}
diff --git a/src/main/java/org/apache/log4j/receivers/net/PortBased.java b/src/main/java/org/apache/log4j/receivers/net/PortBased.java
new file mode 100644
index 0000000..8ef8e69
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/net/PortBased.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.receivers.net;
+
+
+/**
+ * Net based entities that 'work with' a Port should consider implementing this
+ * interface so that they can be treated generically.
+ *
+ * @author Paul Smith (psmith@apache.org)
+ *
+ */
+public interface PortBased extends NetworkBased {
+ /**
+ * Returns the Port # that this net based thing is using.
+ * @return int port number
+ */
+ int getPort();
+}
diff --git a/src/main/java/org/apache/log4j/receivers/net/SocketHubReceiver.java b/src/main/java/org/apache/log4j/receivers/net/SocketHubReceiver.java
new file mode 100644
index 0000000..3256fce
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/net/SocketHubReceiver.java
@@ -0,0 +1,411 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.receivers.net;
+
+
+import org.apache.log4j.component.plugins.Plugin;
+import org.apache.log4j.component.plugins.Receiver;
+import org.apache.log4j.net.ZeroConfSupport;
+import org.apache.log4j.spi.LoggerRepository;
+
+import java.io.IOException;
+import java.net.Socket;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ SocketHubReceiver receives a remote logging event on a configured
+ socket and "posts" it to a LoggerRepository as if the event was
+ generated locally. This class is designed to receive events from
+ the SocketHubAppender class (or classes that send compatible events).
+
+ <p>Once the event has been "posted", it will be handled by the
+ appenders currently configured in the LoggerRespository.
+
+ @author Mark Womack
+ @author Ceki Gülcü
+ @author Paul Smith (psmith@apache.org)
+*/
+public class SocketHubReceiver
+extends Receiver implements SocketNodeEventListener, PortBased {
+
+ /**
+ * Default reconnection delay.
+ */
+ static final int DEFAULT_RECONNECTION_DELAY = 30000;
+
+ /**
+ * Host.
+ */
+ protected String host;
+
+ /**
+ * Port.
+ */
+ protected int port;
+ /**
+ * Reconnection delay.
+ */
+ protected int reconnectionDelay = DEFAULT_RECONNECTION_DELAY;
+
+ /**
+ * The MulticastDNS zone advertised by a SocketHubReceiver
+ */
+ public static final String ZONE = "_log4j_obj_tcpconnect_receiver.local.";
+
+ /**
+ * Active.
+ */
+ protected boolean active = false;
+
+ /**
+ * Connector.
+ */
+ protected Connector connector;
+
+ /**
+ * Socket.
+ */
+ protected SocketNode13 socketNode;
+
+ /**
+ * Listener list.
+ */
+ private List listenerList = Collections.synchronizedList(new ArrayList());
+
+ private boolean advertiseViaMulticastDNS;
+ private ZeroConfSupport zeroConf;
+
+ /**
+ * Create new instance.
+ */
+ public SocketHubReceiver() {
+ super();
+ }
+
+ /**
+ * Create new instance.
+ * @param h host
+ * @param p port
+ */
+ public SocketHubReceiver(final String h,
+ final int p) {
+ super();
+ host = h;
+ port = p;
+ }
+
+ /**
+ * Create new instance.
+ * @param h host
+ * @param p port
+ * @param repo logger repository
+ */
+ public SocketHubReceiver(final String h,
+ final int p,
+ final LoggerRepository repo) {
+ super();
+ host = h;
+ port = p;
+ repository = repo;
+ }
+
+ /**
+ * Adds a SocketNodeEventListener to this receiver to be notified
+ * of SocketNode events.
+ * @param l listener
+ */
+ public void addSocketNodeEventListener(final SocketNodeEventListener l) {
+ listenerList.add(l);
+ }
+
+ /**
+ * Removes a specific SocketNodeEventListener from this instance
+ * so that it will no longer be notified of SocketNode events.
+ * @param l listener
+ */
+ public void removeSocketNodeEventListener(
+ final SocketNodeEventListener l) {
+ listenerList.remove(l);
+ }
+
+ /**
+ Get the remote host to connect to for logging events.
+ @return host
+ */
+ public String getHost() {
+ return host;
+ }
+
+ /**
+ * Configures the Host property, this will require activateOptions
+ * to be called for this to take effect.
+ * @param remoteHost address of remote host.
+ */
+ public void setHost(final String remoteHost) {
+ this.host = remoteHost;
+ }
+ /**
+ Set the remote host to connect to for logging events.
+ Equivalent to setHost.
+ @param remoteHost address of remote host.
+ */
+ public void setPort(final String remoteHost) {
+ host = remoteHost;
+ }
+
+ /**
+ Get the remote port to connect to for logging events.
+ @return port
+ */
+ public int getPort() {
+ return port;
+ }
+
+ /**
+ Set the remote port to connect to for logging events.
+ @param p port
+ */
+ public void setPort(final int p) {
+ this.port = p;
+ }
+
+ /**
+ The <b>ReconnectionDelay</b> option takes a positive integer
+ representing the number of milliseconds to wait between each
+ failed connection attempt to the server. The default value of
+ this option is 30000 which corresponds to 30 seconds.
+
+ <p>Setting this option to zero turns off reconnection
+ capability.
+ @param delay milliseconds to wait or zero to not reconnect.
+ */
+ public void setReconnectionDelay(final int delay) {
+ int oldValue = this.reconnectionDelay;
+ this.reconnectionDelay = delay;
+ firePropertyChange("reconnectionDelay", oldValue, this.reconnectionDelay);
+ }
+
+ /**
+ Returns value of the <b>ReconnectionDelay</b> option.
+ @return value of reconnection delay option.
+ */
+ public int getReconnectionDelay() {
+ return reconnectionDelay;
+ }
+
+ /**
+ * Returns true if the receiver is the same class and they are
+ * configured for the same properties, and super class also considers
+ * them to be equivalent. This is used by PluginRegistry when determining
+ * if the a similarly configured receiver is being started.
+ *
+ * @param testPlugin The plugin to test equivalency against.
+ * @return boolean True if the testPlugin is equivalent to this plugin.
+ */
+ public boolean isEquivalent(final Plugin testPlugin) {
+ if (testPlugin != null && testPlugin instanceof SocketHubReceiver) {
+ SocketHubReceiver sReceiver = (SocketHubReceiver) testPlugin;
+
+ return (port == sReceiver.getPort()
+ && host.equals(sReceiver.getHost())
+ && reconnectionDelay == sReceiver.getReconnectionDelay()
+ && super.isEquivalent(testPlugin));
+ }
+ return false;
+ }
+
+ /**
+ Sets the flag to indicate if receiver is active or not.
+ @param b new value
+ */
+ protected synchronized void setActive(final boolean b) {
+ active = b;
+ }
+
+ /**
+ Starts the SocketReceiver with the current options. */
+ public void activateOptions() {
+ if (!isActive()) {
+ setActive(true);
+ if (advertiseViaMulticastDNS) {
+ zeroConf = new ZeroConfSupport(ZONE, port, getName());
+ zeroConf.advertise();
+ }
+
+ fireConnector(false);
+ }
+ }
+
+ /**
+ Called when the receiver should be stopped. Closes the socket */
+ public synchronized void shutdown() {
+ // mark this as no longer running
+ active = false;
+
+ // close the socket
+ try {
+ if (socketNode != null) {
+ socketNode.close();
+ socketNode = null;
+ }
+ } catch (Exception e) {
+ getLogger().info("Excpetion closing socket", e);
+ // ignore for now
+ }
+
+ // stop the connector
+ if (connector != null) {
+ connector.interrupted = true;
+ connector = null; // allow gc
+ }
+ if (advertiseViaMulticastDNS) {
+ zeroConf.unadvertise();
+ }
+ }
+
+ /**
+ Listen for a socketClosedEvent from the SocketNode. Reopen the
+ socket if this receiver is still active.
+ @param e exception not used.
+ */
+ public void socketClosedEvent(final Exception e) {
+ // if it is a non-normal closed event
+ // we clear the connector object here
+ // so that it actually does reconnect if the
+ // remote socket dies.
+ if (e != null) {
+ connector = null;
+ fireConnector(true);
+ }
+ }
+
+ /**
+ * Fire connectors.
+ * @param isReconnect true if reconnect.
+ */
+ private synchronized void fireConnector(final boolean isReconnect) {
+ if (active && connector == null) {
+ getLogger().debug("Starting a new connector thread.");
+ connector = new Connector(isReconnect);
+ connector.setDaemon(true);
+ connector.setPriority(Thread.MIN_PRIORITY);
+ connector.start();
+ }
+ }
+
+ /**
+ * Set socket.
+ * @param newSocket new value for socket.
+ */
+ private synchronized void setSocket(final Socket newSocket) {
+ connector = null;
+ socketNode = new SocketNode13(newSocket, this);
+ socketNode.addSocketNodeEventListener(this);
+
+ synchronized (listenerList) {
+ for (Iterator iter = listenerList.iterator(); iter.hasNext();) {
+ SocketNodeEventListener listener =
+ (SocketNodeEventListener) iter.next();
+ socketNode.addSocketNodeEventListener(listener);
+ }
+ }
+ new Thread(socketNode).start();
+ }
+
+ public void setAdvertiseViaMulticastDNS(boolean advertiseViaMulticastDNS) {
+ this.advertiseViaMulticastDNS = advertiseViaMulticastDNS;
+ }
+
+ public boolean isAdvertiseViaMulticastDNS() {
+ return advertiseViaMulticastDNS;
+ }
+
+ /**
+ The Connector will reconnect when the server becomes available
+ again. It does this by attempting to open a new connection every
+ <code>reconnectionDelay</code> milliseconds.
+
+ <p>It stops trying whenever a connection is established. It will
+ restart to try reconnect to the server when previpously open
+ connection is droppped.
+
+ @author Ceki Gülcü
+ */
+ private final class Connector extends Thread {
+
+ /**
+ * Interruption status.
+ */
+ boolean interrupted = false;
+ /**
+ * If true, then delay on next iteration.
+ */
+ boolean doDelay;
+
+ /**
+ * Create new instance.
+ * @param isReconnect true if reconnecting.
+ */
+ public Connector(final boolean isReconnect) {
+ super();
+ doDelay = isReconnect;
+ }
+
+ /**
+ * Attempt to connect until interrupted.
+ */
+ public void run() {
+ while (!interrupted) {
+ try {
+ if (doDelay) {
+ getLogger().debug("waiting for " + reconnectionDelay
+ + " milliseconds before reconnecting.");
+ sleep(reconnectionDelay);
+ }
+ doDelay = true;
+ getLogger().debug("Attempting connection to " + host);
+ Socket s = new Socket(host, port);
+ setSocket(s);
+ getLogger().debug(
+ "Connection established. Exiting connector thread.");
+ break;
+ } catch (InterruptedException e) {
+ getLogger().debug("Connector interrupted. Leaving loop.");
+ return;
+ } catch (java.net.ConnectException e) {
+ getLogger().debug("Remote host {} refused connection.", host);
+ } catch (IOException e) {
+ getLogger().debug("Could not connect to {}. Exception is {}.",
+ host, e);
+ }
+ }
+ }
+ }
+
+ /**
+ * This method does nothing.
+ * @param remoteInfo remote info.
+ */
+ public void socketOpened(final String remoteInfo) {
+
+ // This method does nothing.
+ }
+}
diff --git a/src/main/java/org/apache/log4j/receivers/net/SocketNode13.java b/src/main/java/org/apache/log4j/receivers/net/SocketNode13.java
new file mode 100644
index 0000000..2b3e8cd
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/net/SocketNode13.java
@@ -0,0 +1,299 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.receivers.net;
+
+import org.apache.log4j.Logger;
+import org.apache.log4j.component.helpers.Constants;
+import org.apache.log4j.component.plugins.Pauseable;
+import org.apache.log4j.component.plugins.Receiver;
+import org.apache.log4j.component.spi.ComponentBase;
+import org.apache.log4j.spi.LoggerRepository;
+import org.apache.log4j.spi.LoggingEvent;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.net.Socket;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+
+// Contributors: Moses Hohman <mmhohman@rainbow.uchicago.edu>
+
+/**
+ Read {@link LoggingEvent} objects sent from a remote client using
+ Sockets (TCP). These logging events are logged according to local
+ policy, as if they were generated locally.
+
+ <p>For example, the socket node might decide to log events to a
+ local file and also resent them to a second socket node.
+
+ Implementation lifted from org.apache.log4j.net.SocketNode
+ in log4j 1.3 and renamed to prevent collision with
+ log4j 1.2 implementation.
+
+ @author Ceki Gülcü
+ @author Paul Smith (psmith@apache.org)
+
+
+*/
+public class SocketNode13 extends ComponentBase implements Runnable, Pauseable {
+
+ /**
+ * Paused state.
+ */
+ private boolean paused;
+ /**
+ * Closed state.
+ */
+ private boolean closed;
+ /**
+ * Socket.
+ */
+ private Socket socket;
+ /**
+ * Receiver.
+ */
+ private Receiver receiver;
+ /**
+ * List of listeners.
+ */
+ private List listenerList = Collections.synchronizedList(new ArrayList());
+
+
+
+ /**
+ Constructor for socket and logger repository.
+ @param s socket
+ @param hierarchy logger repository
+ */
+ public SocketNode13(final Socket s,
+ final LoggerRepository hierarchy) {
+ super();
+ this.socket = s;
+ this.repository = hierarchy;
+ }
+
+ /**
+ Constructor for socket and receiver.
+ @param s socket
+ @param r receiver
+ */
+ public SocketNode13(final Socket s, final Receiver r) {
+ super();
+ this.socket = s;
+ this.receiver = r;
+ }
+
+ /**
+ * Set the event listener on this node.
+ *
+ * @deprecated Now supports mutliple listeners, this method
+ * simply invokes the removeSocketNodeEventListener() to remove
+ * the listener, and then readds it.
+ * @param l listener
+ */
+ public void setListener(final SocketNodeEventListener l) {
+ removeSocketNodeEventListener(l);
+ addSocketNodeEventListener(l);
+ }
+
+ /**
+ * Adds the listener to the list of listeners to be notified of the
+ * respective event.
+ * @param listener the listener to add to the list
+ */
+ public void addSocketNodeEventListener(
+ final SocketNodeEventListener listener) {
+ listenerList.add(listener);
+ }
+
+ /**
+ * Removes the registered Listener from this instances list of
+ * listeners. If the listener has not been registered, then invoking
+ * this method has no effect.
+ *
+ * @param listener the SocketNodeEventListener to remove
+ */
+ public void removeSocketNodeEventListener(
+ final SocketNodeEventListener listener) {
+ listenerList.remove(listener);
+ }
+
+
+ /**
+ * Deserialize events from socket until interrupted.
+ */
+ public void run() {
+ LoggingEvent event;
+ Logger remoteLogger;
+ Exception listenerException = null;
+ ObjectInputStream ois = null;
+
+ try {
+ ois =
+ new ObjectInputStream(
+ new BufferedInputStream(socket.getInputStream()));
+ } catch (Exception e) {
+ ois = null;
+ listenerException = e;
+ getLogger().error("Exception opening ObjectInputStream to " + socket, e);
+ }
+
+ if (ois != null) {
+
+ String hostName = socket.getInetAddress().getHostName();
+ String remoteInfo = hostName + ":" + socket.getPort();
+
+ /**
+ * notify the listener that the socket has been
+ * opened and this SocketNode is ready and waiting
+ */
+ fireSocketOpened(remoteInfo);
+
+ try {
+ while (!isClosed()) {
+ // read an event from the wire
+ event = (LoggingEvent) ois.readObject();
+ event.setProperty(Constants.HOSTNAME_KEY, hostName);
+ // store the known remote info in an event property
+ event.setProperty("log4j.remoteSourceInfo", remoteInfo);
+
+ // if configured with a receiver, tell it to post the event
+ if (!isPaused() && !isClosed()) {
+ if ((receiver != null)) {
+ receiver.doPost(event);
+
+ // else post it via the hierarchy
+ } else {
+ // get a logger from the hierarchy. The name of the logger
+ // is taken to be the name contained in the event.
+ remoteLogger = repository.getLogger(event.getLoggerName());
+
+ //event.logger = remoteLogger;
+ // apply the logger-level filter
+ if (event
+ .getLevel()
+ .isGreaterOrEqual(remoteLogger.getEffectiveLevel())) {
+ // finally log the event as if was generated locally
+ remoteLogger.callAppenders(event);
+ }
+ }
+ } else {
+ //we simply discard this event.
+ }
+ }
+ } catch (java.io.EOFException e) {
+ getLogger().info("Caught java.io.EOFException closing connection.");
+ listenerException = e;
+ } catch (java.net.SocketException e) {
+ getLogger().info("Caught java.net.SocketException closing connection.");
+ listenerException = e;
+ } catch (IOException e) {
+ getLogger().info("Caught java.io.IOException: " + e);
+ getLogger().info("Closing connection.");
+ listenerException = e;
+ } catch (Exception e) {
+ getLogger().error("Unexpected exception. Closing connection.", e);
+ listenerException = e;
+ }
+ }
+
+ // close the socket
+ try {
+ if (ois != null) {
+ ois.close();
+ }
+ } catch (Exception e) {
+ //getLogger().info("Could not close connection.", e);
+ }
+
+ // send event to listener, if configured
+ if (listenerList.size() > 0 && !isClosed()) {
+ fireSocketClosedEvent(listenerException);
+ }
+ }
+
+ /**
+ * Notifies all registered listeners regarding the closing of the Socket.
+ * @param listenerException listener exception
+ */
+ private void fireSocketClosedEvent(final Exception listenerException) {
+ synchronized (listenerList) {
+ for (Iterator iter = listenerList.iterator(); iter.hasNext();) {
+ SocketNodeEventListener snel =
+ (SocketNodeEventListener) iter.next();
+ if (snel != null) {
+ snel.socketClosedEvent(listenerException);
+ }
+ }
+ }
+ }
+
+ /**
+ * Notifies all registered listeners regarding the opening of a Socket.
+ * @param remoteInfo remote info
+ */
+ private void fireSocketOpened(final String remoteInfo) {
+ synchronized (listenerList) {
+ for (Iterator iter = listenerList.iterator(); iter.hasNext();) {
+ SocketNodeEventListener snel =
+ (SocketNodeEventListener) iter.next();
+ if (snel != null) {
+ snel.socketOpened(remoteInfo);
+ }
+ }
+ }
+ }
+
+ /**
+ * Sets if node is paused.
+ * @param b new value
+ */
+ public void setPaused(final boolean b) {
+ this.paused = b;
+ }
+
+ /**
+ * Get if node is paused.
+ * @return true if pause.
+ */
+ public boolean isPaused() {
+ return this.paused;
+ }
+
+ /**
+ * Close the node and underlying socket
+ */
+ public void close() throws IOException {
+ getLogger().debug("closing socket");
+ this.closed = true;
+ socket.close();
+ fireSocketClosedEvent(null);
+ }
+
+ /**
+ * Get if node is closed.
+ * @return true if closed.
+ */
+ public boolean isClosed() {
+ return this.closed;
+ }
+}
diff --git a/src/main/java/org/apache/log4j/receivers/net/SocketNodeEventListener.java b/src/main/java/org/apache/log4j/receivers/net/SocketNodeEventListener.java
new file mode 100644
index 0000000..a8c56ac
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/net/SocketNodeEventListener.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.receivers.net;
+
+import java.util.EventListener;
+
+/**
+ Interface used to listen for {@link SocketNode} related
+ events. Clients register an instance of the interface and the
+ instance is called back when the various events occur.
+
+ @author Mark Womack
+ @author Paul Smith (psmith@apache.org)
+*/
+public interface SocketNodeEventListener extends EventListener {
+
+ /**
+ * Called when the SocketNode is created and begins awaiting data.
+ * @param remoteInfo remote info
+ */
+ void socketOpened(String remoteInfo);
+
+ /**
+ Called when the socket the node was given has been closed.
+ @param e exception
+ */
+ void socketClosedEvent(Exception e);
+}
diff --git a/src/main/java/org/apache/log4j/receivers/net/SocketReceiver.java b/src/main/java/org/apache/log4j/receivers/net/SocketReceiver.java
new file mode 100644
index 0000000..71d757c
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/net/SocketReceiver.java
@@ -0,0 +1,474 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.receivers.net;
+
+
+import org.apache.log4j.component.plugins.Pauseable;
+import org.apache.log4j.component.plugins.Plugin;
+import org.apache.log4j.component.plugins.Receiver;
+import org.apache.log4j.net.ZeroConfSupport;
+import org.apache.log4j.spi.LoggerRepository;
+import org.apache.log4j.spi.LoggingEvent;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.*;
+
+
+/**
+ SocketReceiver receives a remote logging event on a configured
+ socket and "posts" it to a LoggerRepository as if the event was
+ generated locally. This class is designed to receive events from
+ the SocketAppender class (or classes that send compatible events).
+
+ <p>Once the event has been "posted", it will be handled by the
+ appenders currently configured in the LoggerRespository.
+
+ @author Mark Womack
+ @author Scott Deboy (sdeboy@apache.org)
+ @author Paul Smith (psmith@apache.org)
+*/
+public class SocketReceiver extends Receiver implements Runnable, PortBased,
+ Pauseable {
+ /**
+ * socket map.
+ */
+ private Map socketMap = new HashMap();
+ /**
+ * Paused.
+ */
+ private boolean paused;
+ /**
+ * Thread.
+ */
+ private Thread rThread;
+ /**
+ * Port.
+ */
+ protected int port;
+ /**
+ * Server socket.
+ */
+ private ServerSocket serverSocket;
+ /**
+ * Socket list.
+ */
+ private Vector socketList = new Vector();
+
+ /**
+ * The MulticastDNS zone advertised by a SocketReceiver
+ */
+ public static final String ZONE = "_log4j_obj_tcpaccept_receiver.local.";
+
+ /**
+ * Listener.
+ */
+ private SocketNodeEventListener listener = null;
+ /**
+ * Listeners.
+ */
+ private List listenerList = Collections.synchronizedList(new ArrayList());
+ private boolean advertiseViaMulticastDNS;
+ private ZeroConfSupport zeroConf;
+
+ /**
+ * Create new instance.
+ */
+ public SocketReceiver() {
+ super();
+ }
+
+ /**
+ * Create new instance.
+ * @param p port
+ */
+ public SocketReceiver(final int p) {
+ super();
+ port = p;
+ }
+
+ /**
+ * Create new instance.
+ * @param p port
+ * @param repo logger repository
+ */
+ public SocketReceiver(final int p, final LoggerRepository repo) {
+ super();
+ this.port = p;
+ repository = repo;
+ }
+
+ /** {@inheritDoc} */
+ public int getPort() {
+ return port;
+ }
+
+ /** {@inheritDoc} */
+ public void setPort(final int p) {
+ port = p;
+ }
+
+ /**
+ * Returns true if the receiver is the same class and they are
+ * configured for the same properties, and super class also considers
+ * them to be equivalent. This is used by PluginRegistry when determining
+ * if the a similarly configured receiver is being started.
+ *
+ * @param testPlugin The plugin to test equivalency against.
+ * @return boolean True if the testPlugin is equivalent to this plugin.
+ */
+ public boolean isEquivalent(final Plugin testPlugin) {
+ if ((testPlugin != null) && testPlugin instanceof SocketReceiver) {
+ SocketReceiver sReceiver = (SocketReceiver) testPlugin;
+
+ return (port == sReceiver.getPort() && super.isEquivalent(testPlugin));
+ }
+
+ return false;
+ }
+
+ /**
+ Starts the SocketReceiver with the current options. */
+ public void activateOptions() {
+ if (!isActive()) {
+ // shutdown();
+ rThread = new Thread(this);
+ rThread.setDaemon(true);
+ rThread.start();
+ if (advertiseViaMulticastDNS) {
+ zeroConf = new ZeroConfSupport(ZONE, port, getName());
+ zeroConf.advertise();
+ }
+
+ active = true;
+ }
+ }
+
+ /**
+ * Called when the receiver should be stopped. Closes the
+ * server socket and all of the open sockets.
+ */
+ public synchronized void shutdown() {
+ getLogger().debug(getName() + " received shutdown request");
+
+ // mark this as no longer running
+ active = false;
+
+ if (rThread != null) {
+ rThread.interrupt();
+ rThread = null;
+ }
+ if (advertiseViaMulticastDNS) {
+ zeroConf.unadvertise();
+ }
+
+ doShutdown();
+ }
+
+ /**
+ * Does the actual shutting down by closing the server socket
+ * and any connected sockets that have been created.
+ */
+ private synchronized void doShutdown() {
+ active = false;
+
+ getLogger().debug(getName() + " doShutdown called");
+
+ // close the server socket
+ closeServerSocket();
+
+ // close all of the accepted sockets
+ closeAllAcceptedSockets();
+ }
+
+ /**
+ * Closes the server socket, if created.
+ */
+ private void closeServerSocket() {
+ getLogger().debug("{} closing server socket", getName());
+
+ try {
+ if (serverSocket != null) {
+ serverSocket.close();
+ }
+ } catch (Exception e) {
+ // ignore for now
+ }
+
+ serverSocket = null;
+ }
+
+ /**
+ * Closes all the connected sockets in the List.
+ */
+ private synchronized void closeAllAcceptedSockets() {
+ for (int x = 0; x < socketList.size(); x++) {
+ try {
+ ((Socket) socketList.get(x)).close();
+ } catch (Exception e) {
+ // ignore for now
+ }
+ }
+
+ // clear member variables
+ socketMap.clear();
+ socketList.clear();
+ }
+
+ /**
+ Sets the flag to indicate if receiver is active or not.
+ @param b new value
+ */
+ protected synchronized void setActive(final boolean b) {
+ active = b;
+ }
+
+ public void setAdvertiseViaMulticastDNS(boolean advertiseViaMulticastDNS) {
+ this.advertiseViaMulticastDNS = advertiseViaMulticastDNS;
+ }
+
+ public boolean isAdvertiseViaMulticastDNS() {
+ return advertiseViaMulticastDNS;
+ }
+
+ /**
+ Loop, accepting new socket connections. */
+ public void run() {
+ /**
+ * Ensure we start fresh.
+ */
+ closeServerSocket();
+ closeAllAcceptedSockets();
+
+ // start the server socket
+ try {
+ serverSocket = new ServerSocket(port);
+ } catch (Exception e) {
+ getLogger().error(
+ "error starting SocketReceiver (" + this.getName()
+ + "), receiver did not start", e);
+ active = false;
+
+ return;
+ }
+
+ Socket socket = null;
+
+ try {
+ getLogger().debug("in run-about to enter while not interrupted loop");
+
+ active = true;
+
+ while (!rThread.isInterrupted()) {
+ // if we have a socket, start watching it
+ if (socket != null) {
+ getLogger().debug(
+ "socket not null - creating and starting socketnode");
+ socketList.add(socket);
+
+ SocketNode13 node = new SocketNode13(socket, this);
+ synchronized (listenerList) {
+ for (Iterator iter = listenerList.iterator();
+ iter.hasNext();) {
+ SocketNodeEventListener l =
+ (SocketNodeEventListener) iter.next();
+ node.addSocketNodeEventListener(l);
+ }
+ }
+ socketMap.put(socket, node);
+ new Thread(node).start();
+ socket = null;
+ }
+
+ getLogger().debug("waiting to accept socket");
+
+ // wait for a socket to open, then loop to start it
+ socket = serverSocket.accept();
+ getLogger().debug("accepted socket");
+ }
+ } catch (Exception e) {
+ getLogger().warn(
+ "exception while watching socket server in SocketReceiver ("
+ + this.getName() + "), stopping");
+ }
+
+ getLogger().debug("{} has exited the not interrupted loop", getName());
+
+ // socket not watched because we a no longer running
+ // so close it now.
+ if (socket != null) {
+ try {
+ socket.close();
+ } catch (IOException e1) {
+ getLogger().warn("socket exception caught - socket closed");
+ }
+ }
+
+ getLogger().debug("{} is exiting main run loop", getName());
+ }
+
+ /**
+ * Returns a Vector of SocketDetail representing the IP/Domain name
+ * of the currently connected sockets that this receiver has
+ * been responsible for creating.
+ * @return Vector of SocketDetails
+ */
+ public Vector getConnectedSocketDetails() {
+ Vector details = new Vector(socketList.size());
+
+ for (Enumeration enumeration = socketList.elements();
+ enumeration.hasMoreElements();
+ ) {
+ Socket socket = (Socket) enumeration.nextElement();
+ details.add(
+ new SocketDetail(socket, (SocketNode13) socketMap.get(socket)));
+ }
+
+ return details;
+ }
+
+ /**
+ * Returns the currently configured SocketNodeEventListener that
+ * will be automatically set for each SocketNode created.
+ * @return SocketNodeEventListener currently configured
+ *
+ * @deprecated This receiver now supports multiple listeners
+ */
+ public SocketNodeEventListener getListener() {
+ return listener;
+ }
+
+ /**
+ * Adds the listener to the list of listeners to be notified of the
+ * respective event.
+ * @param l the listener to add to the list
+ */
+ public void addSocketNodeEventListener(
+ final SocketNodeEventListener l) {
+ listenerList.add(l);
+ }
+
+ /**
+ * Removes the registered Listener from this instances list of
+ * listeners. If the listener has not been registered, then invoking
+ * this method has no effect.
+ *
+ * @param l the SocketNodeEventListener to remove
+ */
+ public void removeSocketNodeEventListener(
+ final SocketNodeEventListener l) {
+ listenerList.remove(l);
+ }
+
+ /**
+ * Sets the SocketNodeEventListener that will be used for each
+ * created SocketNode.
+ * @param l the listener to set on each creation of a SocketNode
+ * @deprecated This receiver now supports multiple listeners and
+ * so this method simply removes the listener (if there already)
+ * and readds it to the list.
+ *
+ * The passed listener will also be returned via the getListener()
+ * method still, but this is also deprecated
+ */
+ public void setListener(final SocketNodeEventListener l) {
+ removeSocketNodeEventListener(l);
+ addSocketNodeEventListener(l);
+ this.listener = l;
+ }
+
+ /** {@inheritDoc} */
+ public boolean isPaused() {
+ return paused;
+ }
+
+ /** {@inheritDoc} */
+ public void setPaused(final boolean b) {
+ paused = b;
+ }
+
+ /**
+ * Socket detail.
+ */
+ private static final class SocketDetail implements AddressBased, PortBased,
+ Pauseable {
+ /**
+ * Address.
+ */
+ private String address;
+ /**
+ * Port.
+ */
+ private int port;
+ /**
+ * Socket node.
+ */
+ private SocketNode13 socketNode;
+
+ /**
+ * Create new instance.
+ * @param socket socket
+ * @param node socket node
+ */
+ private SocketDetail(final Socket socket,
+ final SocketNode13 node) {
+ super();
+ this.address = socket.getInetAddress().getHostName();
+ this.port = socket.getPort();
+ this.socketNode = node;
+ }
+
+ /** {@inheritDoc} */
+ public String getAddress() {
+ return address;
+ }
+
+ /** {@inheritDoc} */
+ public int getPort() {
+ return port;
+ }
+
+ /** {@inheritDoc} */
+ public String getName() {
+ return "Socket";
+ }
+
+ /** {@inheritDoc} */
+ public boolean isActive() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ public boolean isPaused() {
+ return socketNode.isPaused();
+ }
+
+ /** {@inheritDoc} */
+ public void setPaused(final boolean b) {
+ socketNode.setPaused(b);
+ }
+ }
+ /** {@inheritDoc} */
+ public void doPost(final LoggingEvent event) {
+ if (!isPaused()) {
+ super.doPost(event);
+ }
+ }
+
+}
diff --git a/src/main/java/org/apache/log4j/receivers/net/UDPAppender.java b/src/main/java/org/apache/log4j/receivers/net/UDPAppender.java
new file mode 100644
index 0000000..a5ae94b
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/net/UDPAppender.java
@@ -0,0 +1,332 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.receivers.net;
+
+import org.apache.log4j.AppenderSkeleton;
+import org.apache.log4j.component.helpers.Constants;
+import org.apache.log4j.helpers.LogLog;
+
+import org.apache.log4j.net.ZeroConfSupport;
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.xml.XMLLayout;
+
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+
+
+/**
+ * Sends log information as a UDP datagrams.
+ *
+ * <p>The UDPAppender is meant to be used as a diagnostic logging tool
+ * so that logging can be monitored by a simple UDP client.
+ *
+ * <p>Messages are not sent as LoggingEvent objects but as text after
+ * applying the designated Layout.
+ *
+ * <p>The port and remoteHost properties can be set in configuration properties.
+ * By setting the remoteHost to a broadcast address any number of clients can
+ * listen for log messages.
+ *
+ * <p>This was inspired and really extended/copied from {@link SocketAppender}.
+ * Please see the docs for the proper credit to the authors of that class.
+ *
+ * @author <a href="mailto:kbrown@versatilesolutions.com">Kevin Brown</a>
+ * @author Scott Deboy <sdeboy@apache.org>
+ */
+public class UDPAppender extends AppenderSkeleton implements PortBased{
+ /**
+ * The default port number for the UDP packets, 9991.
+ */
+ public static final int DEFAULT_PORT = 9991;
+
+ /**
+ We remember host name as String in addition to the resolved
+ InetAddress so that it can be returned via getOption().
+ */
+ String hostname;
+ String remoteHost;
+ String application;
+ String encoding;
+ InetAddress address;
+ int port = DEFAULT_PORT;
+ DatagramSocket outSocket;
+
+ /**
+ * The MulticastDNS zone advertised by a UDPAppender
+ */
+ public static final String ZONE = "_log4j_xml_udp_appender.local.";
+
+ // if there is something irrecoverably wrong with the settings, there is no
+ // point in sending out packeets.
+ boolean inError = false;
+ private boolean advertiseViaMulticastDNS;
+ private ZeroConfSupport zeroConf;
+
+ public UDPAppender() {
+ super(false);
+ }
+
+ /**
+ Sends UDP packets to the <code>address</code> and <code>port</code>.
+ */
+ public UDPAppender(final InetAddress address, final int port) {
+ super(false);
+ this.address = address;
+ this.remoteHost = address.getHostName();
+ this.port = port;
+ activateOptions();
+ }
+
+ /**
+ Sends UDP packets to the <code>address</code> and <code>port</code>.
+ */
+ public UDPAppender(final String host, final int port) {
+ super(false);
+ this.port = port;
+ this.address = getAddressByName(host);
+ this.remoteHost = host;
+ activateOptions();
+ }
+
+ /**
+ Open the UDP sender for the <b>RemoteHost</b> and <b>Port</b>.
+ */
+ public void activateOptions() {
+ try {
+ hostname = InetAddress.getLocalHost().getHostName();
+ } catch (UnknownHostException uhe) {
+ try {
+ hostname = InetAddress.getLocalHost().getHostAddress();
+ } catch (UnknownHostException uhe2) {
+ hostname = "unknown";
+ }
+ }
+
+ //allow system property of application to be primary
+ if (application == null) {
+ application = System.getProperty(Constants.APPLICATION_KEY);
+ } else {
+ if (System.getProperty(Constants.APPLICATION_KEY) != null) {
+ application = application + "-" + System.getProperty(Constants.APPLICATION_KEY);
+ }
+ }
+
+ if(remoteHost != null) {
+ address = getAddressByName(remoteHost);
+ connect(address, port);
+ } else {
+ String err = "The RemoteHost property is required for SocketAppender named "+ name;
+ LogLog.error(err);
+ throw new IllegalStateException(err);
+ }
+
+ if (layout == null) {
+ layout = new XMLLayout();
+ }
+
+ if (advertiseViaMulticastDNS) {
+ zeroConf = new ZeroConfSupport(ZONE, port, getName());
+ zeroConf.advertise();
+ }
+
+ super.activateOptions();
+ }
+
+ /**
+ Close this appender.
+ <p>This will mark the appender as closed and
+ call then {@link #cleanUp} method.
+ */
+ public synchronized void close() {
+ if (closed) {
+ return;
+ }
+
+ if (advertiseViaMulticastDNS) {
+ zeroConf.unadvertise();
+ }
+
+ this.closed = true;
+ cleanUp();
+ }
+
+ /**
+ Close the UDP Socket and release the underlying
+ connector thread if it has been created
+ */
+ public void cleanUp() {
+ if (outSocket != null) {
+ try {
+ outSocket.close();
+ } catch (Exception e) {
+ LogLog.error("Could not close outSocket.", e);
+ }
+
+ outSocket = null;
+ }
+ }
+
+ void connect(InetAddress address, int port) {
+ if (this.address == null) {
+ return;
+ }
+
+ try {
+ // First, close the previous connection if any.
+ cleanUp();
+ outSocket = new DatagramSocket();
+ outSocket.connect(address, port);
+ } catch (IOException e) {
+ LogLog.error(
+ "Could not open UDP Socket for sending.", e);
+ inError = true;
+ }
+ }
+
+ public void append(LoggingEvent event) {
+ if(inError) {
+ return;
+ }
+
+ if (event == null) {
+ return;
+ }
+
+ if (address == null) {
+ return;
+ }
+
+ if (outSocket != null) {
+ event.setProperty(Constants.HOSTNAME_KEY, hostname);
+ if (application != null) {
+ event.setProperty(Constants.APPLICATION_KEY, application);
+ }
+
+ try {
+ StringBuffer buf = new StringBuffer(layout.format(event));
+
+ byte[] payload;
+ if(encoding == null) {
+ payload = buf.toString().getBytes();
+ } else {
+ payload = buf.toString().getBytes(encoding);
+ }
+
+ DatagramPacket dp =
+ new DatagramPacket(payload, payload.length, address, port);
+ outSocket.send(dp);
+ } catch (IOException e) {
+ outSocket = null;
+ LogLog.warn("Detected problem with UDP connection: " + e);
+ }
+ }
+ }
+
+ public boolean isActive() {
+ return !inError;
+ }
+
+ InetAddress getAddressByName(String host) {
+ try {
+ return InetAddress.getByName(host);
+ } catch (Exception e) {
+ LogLog.error("Could not find address of [" + host + "].", e);
+ return null;
+ }
+ }
+
+ /**
+ The UDPAppender uses layouts. Hence, this method returns
+ <code>true</code>.
+ */
+ public boolean requiresLayout() {
+ return true;
+ }
+
+ /**
+ The <b>RemoteHost</b> option takes a string value which should be
+ the host name or ipaddress to send the UDP packets.
+ */
+ public void setRemoteHost(String host) {
+ remoteHost = host;
+ }
+
+ /**
+ Returns value of the <b>RemoteHost</b> option.
+ */
+ public String getRemoteHost() {
+ return remoteHost;
+ }
+
+ /**
+ The <b>App</b> option takes a string value which should be the name of the application getting logged.
+ If property was already set (via system property), don't set here.
+ */
+ public void setApplication(String app) {
+ this.application = app;
+ }
+
+ /**
+ Returns value of the <b>App</b> option.
+ */
+ public String getApplication() {
+ return application;
+ }
+
+ /**
+ The <b>Encoding</b> option specifies how the bytes are encoded. If this option is not specified,
+ the System encoding is used.
+ */
+ public void setEncoding(String encoding) {
+ this.encoding = encoding;
+ }
+
+ /**
+ Returns value of the <b>Encoding</b> option.
+ */
+ public String getEncoding() {
+ return encoding;
+ }
+
+ /**
+ The <b>Port</b> option takes a positive integer representing
+ the port where UDP packets will be sent.
+ */
+ public void setPort(int port) {
+ this.port = port;
+ }
+
+ /**
+ Returns value of the <b>Port</b> option.
+ */
+ public int getPort() {
+ return port;
+ }
+
+ public void setAdvertiseViaMulticastDNS(boolean advertiseViaMulticastDNS) {
+ this.advertiseViaMulticastDNS = advertiseViaMulticastDNS;
+ }
+
+ public boolean isAdvertiseViaMulticastDNS() {
+ return advertiseViaMulticastDNS;
+ }
+}
diff --git a/src/main/java/org/apache/log4j/receivers/net/UDPReceiver.java b/src/main/java/org/apache/log4j/receivers/net/UDPReceiver.java
new file mode 100644
index 0000000..c762efc
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/net/UDPReceiver.java
@@ -0,0 +1,282 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.receivers.net;
+
+import org.apache.log4j.component.plugins.Pauseable;
+import org.apache.log4j.component.plugins.Receiver;
+import org.apache.log4j.net.ZeroConfSupport;
+import org.apache.log4j.receivers.spi.Decoder;
+import org.apache.log4j.spi.LoggingEvent;
+
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.SocketException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+
+/**
+ * Receive LoggingEvents encoded with an XMLLayout, convert the XML data to a
+ * LoggingEvent and post the LoggingEvent.
+ *
+ * @author Scott Deboy <sdeboy@apache.org>
+ *
+ */
+public class UDPReceiver extends Receiver implements PortBased, Pauseable {
+ private static final int PACKET_LENGTH = 16384;
+ private UDPReceiverThread receiverThread;
+ private String encoding;
+
+ //default to log4j xml decoder
+ private String decoder = "org.apache.log4j.xml.XMLDecoder";
+ private Decoder decoderImpl;
+ protected boolean paused;
+ private transient boolean closed = false;
+ private int port;
+ private DatagramSocket socket;
+ UDPHandlerThread handlerThread;
+ private boolean advertiseViaMulticastDNS;
+ private ZeroConfSupport zeroConf;
+
+ /**
+ * The MulticastDNS zone advertised by a UDPReceiver
+ */
+ public static final String ZONE = "_log4j_xml_udp_receiver.local.";
+
+
+ public int getPort() {
+ return port;
+ }
+
+ public void setPort(int port) {
+ this.port = port;
+ }
+
+ /**
+ * The <b>Encoding</b> option specifies how the bytes are encoded. If this
+ * option is not specified, the system encoding will be used.
+ * */
+ public void setEncoding(String encoding) {
+ this.encoding = encoding;
+ }
+
+ /**
+ * Returns value of the <b>Encoding</b> option.
+ */
+ public String getEncoding() {
+ return encoding;
+ }
+
+ public String getDecoder() {
+ return decoder;
+ }
+
+ public void setDecoder(String decoder) {
+ this.decoder = decoder;
+ }
+
+ public boolean isPaused() {
+ return paused;
+ }
+
+ public void setPaused(boolean b) {
+ paused = b;
+ }
+
+ public void setAdvertiseViaMulticastDNS(boolean advertiseViaMulticastDNS) {
+ this.advertiseViaMulticastDNS = advertiseViaMulticastDNS;
+ }
+
+ public boolean isAdvertiseViaMulticastDNS() {
+ return advertiseViaMulticastDNS;
+ }
+
+ public synchronized void shutdown() {
+ if(closed == true) {
+ return;
+ }
+ closed = true;
+ active = false;
+ // Closing the datagram socket will unblock the UDPReceiverThread if it is
+ // was waiting to receive data from the socket.
+ if (socket != null) {
+ socket.close();
+ }
+
+ if (advertiseViaMulticastDNS) {
+ zeroConf.unadvertise();
+ }
+
+ try {
+ if(handlerThread != null) {
+ handlerThread.close();
+ handlerThread.join();
+ }
+ if(receiverThread != null) {
+ receiverThread.join();
+ }
+ } catch(InterruptedException ie) {
+ }
+ }
+
+ /**
+ Returns true if this receiver is active. */
+// public synchronized boolean isActive() {
+// return isActive;
+//}
+
+ public void activateOptions() {
+ try {
+ Class c = Class.forName(decoder);
+ Object o = c.newInstance();
+
+ if (o instanceof Decoder) {
+ this.decoderImpl = (Decoder) o;
+ }
+ } catch (ClassNotFoundException cnfe) {
+ getLogger().warn("Unable to find decoder", cnfe);
+ } catch (IllegalAccessException iae) {
+ getLogger().warn("Could not construct decoder", iae);
+ } catch (InstantiationException ie) {
+ getLogger().warn("Could not construct decoder", ie);
+ }
+
+ try {
+ socket = new DatagramSocket(port);
+ receiverThread = new UDPReceiverThread();
+ receiverThread.start();
+ handlerThread = new UDPHandlerThread();
+ handlerThread.start();
+ if (advertiseViaMulticastDNS) {
+ zeroConf = new ZeroConfSupport(ZONE, port, getName());
+ zeroConf.advertise();
+ }
+ active = true;
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ }
+ }
+
+ class UDPHandlerThread extends Thread {
+ private List list = new ArrayList();
+
+ public UDPHandlerThread() {
+ setDaemon(true);
+ }
+
+ public void append(String data) {
+ synchronized (list) {
+ list.add(data);
+ list.notify();
+ }
+ }
+
+ /**
+ * Allow the UDPHandlerThread to wakeup and exit gracefully.
+ */
+ void close() {
+ synchronized(list) {
+ list.notify();
+ }
+ }
+
+ public void run() {
+ ArrayList list2 = new ArrayList();
+
+ while (!UDPReceiver.this.closed) {
+ synchronized (list) {
+ try {
+ while (!UDPReceiver.this.closed && list.size() == 0) {
+ list.wait(300);
+ }
+
+ if (list.size() > 0) {
+ list2.addAll(list);
+ list.clear();
+ }
+ } catch (InterruptedException ie) {
+ }
+ }
+
+ if (list2.size() > 0) {
+ Iterator iter = list2.iterator();
+
+ while (iter.hasNext()) {
+ String data = (String) iter.next();
+ List v = decoderImpl.decodeEvents(data);
+
+ if (v != null) {
+ Iterator eventIter = v.iterator();
+
+ while (eventIter.hasNext()) {
+ if (!isPaused()) {
+ doPost((LoggingEvent) eventIter.next());
+ }
+ }
+ }
+ }
+
+ list2.clear();
+ } else {
+ try {
+ synchronized (this) {
+ wait(1000);
+ }
+ } catch (InterruptedException ie) {
+ }
+ }
+ } // while
+ getLogger().debug(UDPReceiver.this.getName()+ "'s handler thread is exiting");
+ } // run
+ } // UDPHandlerThread
+
+ class UDPReceiverThread extends Thread {
+ public UDPReceiverThread() {
+ setDaemon(true);
+ }
+
+ public void run() {
+ byte[] b = new byte[PACKET_LENGTH];
+ DatagramPacket p = new DatagramPacket(b, b.length);
+
+ while (!UDPReceiver.this.closed) {
+ try {
+ socket.receive(p);
+
+ //this string constructor which accepts a charset throws an exception if it is
+ //null
+ if (encoding == null) {
+ handlerThread.append(
+ new String(p.getData(), 0, p.getLength()));
+ } else {
+ handlerThread.append(
+ new String(p.getData(), 0, p.getLength(), encoding));
+ }
+ } catch (SocketException se) {
+ //disconnected
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ }
+ }
+
+ //LogLog.debug(UDPReceiver.this.getName() + "'s thread is ending.");
+ }
+ }
+}
diff --git a/src/main/java/org/apache/log4j/receivers/net/XMLSocketNode.java b/src/main/java/org/apache/log4j/receivers/net/XMLSocketNode.java
new file mode 100644
index 0000000..3ead0f6
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/net/XMLSocketNode.java
@@ -0,0 +1,208 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.receivers.net;
+
+import org.apache.log4j.Logger;
+
+
+import org.apache.log4j.component.helpers.Constants;
+import org.apache.log4j.component.plugins.Receiver;
+import org.apache.log4j.component.spi.ComponentBase;
+import org.apache.log4j.receivers.spi.Decoder;
+import org.apache.log4j.spi.LoggerRepository;
+import org.apache.log4j.spi.LoggingEvent;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.Socket;
+import java.util.Iterator;
+import java.util.List;
+
+
+/**
+ Read {@link LoggingEvent} objects sent from a remote client using XML over
+ Sockets (TCP). These logging events are logged according to local
+ policy, as if they were generated locally.
+
+ <p>For example, the socket node might decide to log events to a
+ local file and also resent them to a second socket node.
+
+ @author Scott Deboy <sdeboy@apache.org>;
+
+ @since 0.8.4
+*/
+public class XMLSocketNode extends ComponentBase implements Runnable {
+ Socket socket;
+ Receiver receiver;
+ Decoder decoder;
+ SocketNodeEventListener listener;
+
+ /**
+ Constructor for socket and logger repository. */
+ public XMLSocketNode(
+ String decoder, Socket socket, LoggerRepository hierarchy) {
+ this.repository = hierarchy;
+ try {
+ Class c = Class.forName(decoder);
+ Object o = c.newInstance();
+
+ if (o instanceof Decoder) {
+ this.decoder = (Decoder) o;
+ }
+ } catch (ClassNotFoundException cnfe) {
+ getLogger().warn("Unable to find decoder", cnfe);
+ } catch (IllegalAccessException iae) {
+ getLogger().warn("Unable to construct decoder", iae);
+ } catch (InstantiationException ie) {
+ getLogger().warn("Unable to construct decoder", ie);
+ }
+
+ this.socket = socket;
+ }
+
+ /**
+ Constructor for socket and reciever. */
+ public XMLSocketNode(String decoder, Socket socket, Receiver receiver) {
+ try {
+ Class c = Class.forName(decoder);
+ Object o = c.newInstance();
+
+ if (o instanceof Decoder) {
+ this.decoder = (Decoder) o;
+ }
+ } catch (ClassNotFoundException cnfe) {
+ getLogger().warn("Unable to find decoder", cnfe);
+ } catch (IllegalAccessException iae) {
+ getLogger().warn("Unable to construct decoder", iae);
+ } catch (InstantiationException ie) {
+ getLogger().warn("Unable to construct decoder", ie);
+ }
+
+ this.socket = socket;
+ this.receiver = receiver;
+ }
+
+ /**
+ Set the event listener on this node. */
+ public void setListener(SocketNodeEventListener _listener) {
+ listener = _listener;
+ }
+
+ public void run() {
+ Logger remoteLogger;
+ Exception listenerException = null;
+ InputStream is = null;
+
+ if ((this.receiver == null) || (this.decoder == null)) {
+ is = null;
+ listenerException =
+ new Exception(
+ "No receiver or decoder provided. Cannot process xml socket events");
+ getLogger().error(
+ "Exception constructing XML Socket Receiver", listenerException);
+ }
+
+ try {
+ is = socket.getInputStream();
+ } catch (Exception e) {
+ is = null;
+ listenerException = e;
+ getLogger().error("Exception opening ObjectInputStream to " + socket, e);
+ }
+
+ if (is != null) {
+ String hostName = socket.getInetAddress().getHostName();
+ String remoteInfo = hostName + ":" + socket.getPort();
+
+ try {
+ //read data from the socket
+ //it's up to the individual decoder to handle incomplete event data
+ while (true) {
+ byte[] b = new byte[1024];
+ int length = is.read(b);
+ if (length == -1) {
+ getLogger().info(
+ "no bytes read from stream - closing connection.");
+ break;
+ }
+ List v = decoder.decodeEvents(new String(b, 0, length));
+
+ if (v != null) {
+ Iterator iter = v.iterator();
+
+ while (iter.hasNext()) {
+ LoggingEvent e = (LoggingEvent) iter.next();
+ e.setProperty(Constants.HOSTNAME_KEY, hostName);
+
+ // store the known remote info in an event property
+ e.setProperty("log4j.remoteSourceInfo", remoteInfo);
+
+ // if configured with a receiver, tell it to post the event
+ if (receiver != null) {
+ receiver.doPost(e);
+
+ // else post it via the hierarchy
+ } else {
+ // get a logger from the hierarchy. The name of the logger
+ // is taken to be the name contained in the event.
+ remoteLogger = repository.getLogger(e.getLoggerName());
+
+ //event.logger = remoteLogger;
+ // apply the logger-level filter
+ if (
+ e.getLevel().isGreaterOrEqual(
+ remoteLogger.getEffectiveLevel())) {
+ // finally log the event as if was generated locally
+ remoteLogger.callAppenders(e);
+ }
+ }
+ }
+ }
+ }
+ } catch (java.io.EOFException e) {
+ getLogger().info("Caught java.io.EOFException closing connection.");
+ listenerException = e;
+ } catch (java.net.SocketException e) {
+ getLogger().info(
+ "Caught java.net.SocketException closing connection.");
+ listenerException = e;
+ } catch (IOException e) {
+ getLogger().info("Caught java.io.IOException: " + e);
+ getLogger().info("Closing connection.");
+ listenerException = e;
+ } catch (Exception e) {
+ getLogger().error("Unexpected exception. Closing connection.", e);
+ listenerException = e;
+ }
+ }
+
+ // close the socket
+ try {
+ if (is != null) {
+ is.close();
+ }
+ } catch (Exception e) {
+ //logger.info("Could not close connection.", e);
+ }
+
+ // send event to listener, if configured
+ if (listener != null) {
+ listener.socketClosedEvent(listenerException);
+ }
+ }
+}
diff --git a/src/main/java/org/apache/log4j/receivers/net/XMLSocketReceiver.java b/src/main/java/org/apache/log4j/receivers/net/XMLSocketReceiver.java
new file mode 100644
index 0000000..9501754
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/net/XMLSocketReceiver.java
@@ -0,0 +1,317 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.receivers.net;
+
+import org.apache.log4j.component.plugins.Pauseable;
+import org.apache.log4j.component.plugins.Plugin;
+import org.apache.log4j.component.plugins.Receiver;
+import org.apache.log4j.net.ZeroConfSupport;
+import org.apache.log4j.spi.LoggerRepository;
+import org.apache.log4j.spi.LoggingEvent;
+
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.List;
+import java.util.Vector;
+
+
+/**
+ XMLSocketReceiver receives a remote logging event via XML on a configured
+ socket and "posts" it to a LoggerRepository as if the event were
+ generated locally. This class is designed to receive events from
+ the XMLSocketAppender class (or classes that send compatible events).
+ <p>
+ This receiver supports log files created using log4j's XMLLayout, as well as java.util.logging
+ XMLFormatter (via the org.apache.log4j.spi.Decoder interface).
+ <p>
+ By default, log4j's XMLLayout is supported (no need to specify a decoder in that case).
+ <p>
+ To configure this receiver to support java.util.logging's XMLFormatter, specify a 'decoder' param
+ of org.apache.log4j.xml.UtilLoggingXMLDecoder.
+ <p>
+ Once the event has been "posted", it will be handled by the
+ appenders currently configured in the LoggerRespository.
+
+ @author Mark Womack
+ @author Scott Deboy <sdeboy@apache.org>
+
+*/
+public class XMLSocketReceiver extends Receiver implements Runnable, PortBased, Pauseable {
+ private boolean paused;
+ //default to log4j xml decoder
+ protected String decoder = "org.apache.log4j.xml.XMLDecoder";
+ private ServerSocket serverSocket;
+ private List socketList = new Vector();
+ private Thread rThread;
+ public static final int DEFAULT_PORT = 4448;
+ protected int port = DEFAULT_PORT;
+ private boolean advertiseViaMulticastDNS;
+ private ZeroConfSupport zeroConf;
+
+ /**
+ * The MulticastDNS zone advertised by an XMLSocketReceiver
+ */
+ public static final String ZONE = "_log4j_xml_tcpaccept_receiver.local.";
+
+ /*
+ * Log4j doesn't provide an XMLSocketAppender, but the MulticastDNS zone that should be advertised by one is:
+ * _log4j_xml_tcpconnect_appender.local.
+ */
+
+ public XMLSocketReceiver() {
+ }
+
+ public XMLSocketReceiver(int _port) {
+ port = _port;
+ }
+
+ public XMLSocketReceiver(int _port, LoggerRepository _repository) {
+ port = _port;
+ repository = _repository;
+ }
+
+ /**
+ Get the port to receive logging events on. */
+ public int getPort() {
+ return port;
+ }
+
+ /**
+ Set the port to receive logging events on. */
+ public void setPort(int _port) {
+ port = _port;
+ }
+
+ public String getDecoder() {
+ return decoder;
+ }
+
+ /**
+ *Specify the class name implementing org.apache.log4j.spi.Decoder that can process the file.
+ */
+ public void setDecoder(String _decoder) {
+ decoder = _decoder;
+ }
+
+ public boolean isPaused() {
+ return paused;
+ }
+
+ public void setPaused(boolean b) {
+ paused = b;
+ }
+
+ /**
+ * Returns true if the receiver is the same class and they are
+ * configured for the same properties, and super class also considers
+ * them to be equivalent. This is used by PluginRegistry when determining
+ * if the a similarly configured receiver is being started.
+ *
+ * @param testPlugin The plugin to test equivalency against.
+ * @return boolean True if the testPlugin is equivalent to this plugin.
+ */
+ public boolean isEquivalent(Plugin testPlugin) {
+ if ((testPlugin != null) && testPlugin instanceof XMLSocketReceiver) {
+ XMLSocketReceiver sReceiver = (XMLSocketReceiver) testPlugin;
+
+ return (port == sReceiver.getPort() && super.isEquivalent(testPlugin));
+ }
+
+ return false;
+ }
+
+ public int hashCode() {
+
+ int result = 37 * (repository != null? repository.hashCode():0);
+ result = result * 37 + port;
+ return (result * 37 + (getName() != null? getName().hashCode():0));
+ }
+
+ /**
+ Sets the flag to indicate if receiver is active or not.
+ @param b new value
+ */
+ protected synchronized void setActive(final boolean b) {
+ active = b;
+ }
+
+ /**
+ Starts the SocketReceiver with the current options. */
+ public void activateOptions() {
+ if (!isActive()) {
+ rThread = new Thread(this);
+ rThread.setDaemon(true);
+ rThread.start();
+
+ if (advertiseViaMulticastDNS) {
+ zeroConf = new ZeroConfSupport(ZONE, port, getName());
+ zeroConf.advertise();
+ }
+
+ active = true;
+ }
+ }
+
+ public void setAdvertiseViaMulticastDNS(boolean advertiseViaMulticastDNS) {
+ this.advertiseViaMulticastDNS = advertiseViaMulticastDNS;
+ }
+
+ public boolean isAdvertiseViaMulticastDNS() {
+ return advertiseViaMulticastDNS;
+ }
+
+ /**
+ Called when the receiver should be stopped. Closes the
+ server socket and all of the open sockets. */
+ public synchronized void shutdown() {
+ // mark this as no longer running
+ active = false;
+
+ if (rThread != null) {
+ rThread.interrupt();
+ rThread = null;
+ }
+ doShutdown();
+ }
+
+ /**
+ * Does the actual shutting down by closing the server socket
+ * and any connected sockets that have been created.
+ */
+ private synchronized void doShutdown() {
+ active = false;
+
+ getLogger().debug("{} doShutdown called", getName());
+
+ // close the server socket
+ closeServerSocket();
+
+ // close all of the accepted sockets
+ closeAllAcceptedSockets();
+
+ if (advertiseViaMulticastDNS) {
+ zeroConf.unadvertise();
+ }
+ }
+
+ /**
+ * Closes the server socket, if created.
+ */
+ private void closeServerSocket() {
+ getLogger().debug("{} closing server socket", getName());
+
+ try {
+ if (serverSocket != null) {
+ serverSocket.close();
+ }
+ } catch (Exception e) {
+ // ignore for now
+ }
+
+ serverSocket = null;
+ }
+
+ /**
+ * Closes all the connected sockets in the List.
+ */
+ private synchronized void closeAllAcceptedSockets() {
+ for (int x = 0; x < socketList.size(); x++) {
+ try {
+ ((Socket) socketList.get(x)).close();
+ } catch (Exception e) {
+ // ignore for now
+ }
+ }
+
+ // clear member variables
+ socketList.clear();
+ }
+
+ /**
+ Loop, accepting new socket connections. */
+ public void run() {
+ /**
+ * Ensure we start fresh.
+ */
+ getLogger().debug("performing socket cleanup prior to entering loop for {}", name);
+ closeServerSocket();
+ closeAllAcceptedSockets();
+ getLogger().debug("socket cleanup complete for {}", name);
+ active = true;
+
+ // start the server socket
+ try {
+ serverSocket = new ServerSocket(port);
+ } catch (Exception e) {
+ getLogger().error(
+ "error starting SocketReceiver (" + this.getName()
+ + "), receiver did not start", e);
+ active = false;
+ doShutdown();
+
+ return;
+ }
+
+ Socket socket = null;
+
+ try {
+ getLogger().debug("in run-about to enter while isactiveloop");
+
+ active = true;
+
+ while (!rThread.isInterrupted()) {
+ // if we have a socket, start watching it
+ if (socket != null) {
+ getLogger().debug("socket not null - creating and starting socketnode");
+ socketList.add(socket);
+
+ XMLSocketNode node = new XMLSocketNode(decoder, socket, this);
+ node.setLoggerRepository(this.repository);
+ new Thread(node).start();
+ socket = null;
+ }
+
+ getLogger().debug("waiting to accept socket");
+
+ // wait for a socket to open, then loop to start it
+ socket = serverSocket.accept();
+ getLogger().debug("accepted socket");
+ }
+
+ // socket not watched because we a no longer running
+ // so close it now.
+ if (socket != null) {
+ socket.close();
+ }
+ } catch (Exception e) {
+ getLogger().warn(
+ "socket server disconnected, stopping");
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.apache.log4j.plugins.Receiver#doPost(org.apache.log4j.spi.LoggingEvent)
+ */
+ public void doPost(LoggingEvent event) {
+ if(!isPaused()){
+ super.doPost(event);
+ }
+ }
+
+
+}
diff --git a/src/main/java/org/apache/log4j/receivers/rewrite/MapRewritePolicy.java b/src/main/java/org/apache/log4j/receivers/rewrite/MapRewritePolicy.java
new file mode 100644
index 0000000..0f29d73
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/rewrite/MapRewritePolicy.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.log4j.receivers.rewrite;
+
+import org.apache.log4j.Logger;
+import org.apache.log4j.spi.LoggingEvent;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * This policy rewrites events where the message of the
+ * original event implementes java.util.Map.
+ * All other events are passed through unmodified.
+ * If the map contains a "message" entry, the value will be
+ * used as the message for the rewritten event. The rewritten
+ * event will have a property set that is the combination of the
+ * original property set and the other members of the message map.
+ * If both the original property set and the message map
+ * contain the same entry, the value from the message map
+ * will overwrite the original property set.
+ *
+ * The combination of the RewriteAppender and this policy
+ * performs the same actions as the MapFilter from log4j 1.3.
+ */
+public class MapRewritePolicy implements RewritePolicy {
+ /**
+ * {@inheritDoc}
+ */
+ public LoggingEvent rewrite(final LoggingEvent source) {
+ Object msg = source.getMessage();
+ if (msg instanceof Map) {
+ Map props = new HashMap(source.getProperties());
+ Map eventProps = (Map) msg;
+ //
+ // if the map sent in the logging request
+ // has "message" entry, use that as the message body
+ // otherwise, use the entire map.
+ //
+ Object newMsg = eventProps.get("message");
+ if (newMsg == null) {
+ newMsg = msg;
+ }
+
+ for(Iterator iter = eventProps.entrySet().iterator();
+ iter.hasNext();
+ ) {
+ Map.Entry entry = (Map.Entry) iter.next();
+ if (!("message".equals(entry.getKey()))) {
+ props.put(entry.getKey(), entry.getValue());
+ }
+ }
+
+ return new LoggingEvent(
+ source.getFQNOfLoggerClass(),
+ source.getLogger() != null ? source.getLogger(): Logger.getLogger(source.getLoggerName()),
+ source.getTimeStamp(),
+ source.getLevel(),
+ newMsg,
+ source.getThreadName(),
+ source.getThrowableInformation(),
+ source.getNDC(),
+ source.getLocationInformation(),
+ props);
+ } else {
+ return source;
+ }
+
+ }
+}
diff --git a/src/main/java/org/apache/log4j/receivers/rewrite/PropertyRewritePolicy.java b/src/main/java/org/apache/log4j/receivers/rewrite/PropertyRewritePolicy.java
new file mode 100644
index 0000000..69e86a6
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/rewrite/PropertyRewritePolicy.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.log4j.receivers.rewrite;
+
+import org.apache.log4j.Logger;
+import org.apache.log4j.spi.LoggingEvent;
+
+import java.util.*;
+
+/**
+ * This policy rewrites events by adding
+ * a user-specified list of properties to the event.
+ * Existing properties are not modified.
+ *
+ * The combination of the RewriteAppender and this policy
+ * performs the same actions as the PropertyFilter from log4j 1.3.
+ */
+
+public class PropertyRewritePolicy implements RewritePolicy {
+ private Map properties = Collections.EMPTY_MAP;
+ public PropertyRewritePolicy() {
+ }
+
+ /**
+ * Set a string representing the property name/value pairs.
+ *
+ * Form: propname1=propvalue1,propname2=propvalue2
+ *
+ * @param props
+ */
+ public void setProperties(String props) {
+ Map hashTable = new HashMap();
+ StringTokenizer pairs = new StringTokenizer(props, ",");
+ while (pairs.hasMoreTokens()) {
+ StringTokenizer entry = new StringTokenizer(pairs.nextToken(), "=");
+ hashTable.put(entry.nextElement().toString().trim(), entry.nextElement().toString().trim());
+ }
+ synchronized(this) {
+ properties = hashTable;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public LoggingEvent rewrite(final LoggingEvent source) {
+ if (!properties.isEmpty()) {
+ Map rewriteProps = new HashMap(source.getProperties());
+ for(Iterator iter = properties.entrySet().iterator();
+ iter.hasNext();
+ ) {
+ Map.Entry entry = (Map.Entry) iter.next();
+ if (!rewriteProps.containsKey(entry.getKey())) {
+ rewriteProps.put(entry.getKey(), entry.getValue());
+ }
+ }
+
+ return new LoggingEvent(
+ source.getFQNOfLoggerClass(),
+ source.getLogger() != null ? source.getLogger(): Logger.getLogger(source.getLoggerName()),
+ source.getTimeStamp(),
+ source.getLevel(),
+ source.getMessage(),
+ source.getThreadName(),
+ source.getThrowableInformation(),
+ source.getNDC(),
+ source.getLocationInformation(),
+ rewriteProps);
+ }
+ return source;
+ }
+
+
+
+}
diff --git a/src/main/java/org/apache/log4j/receivers/rewrite/ReflectionRewritePolicy.java b/src/main/java/org/apache/log4j/receivers/rewrite/ReflectionRewritePolicy.java
new file mode 100644
index 0000000..96b3bfe
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/rewrite/ReflectionRewritePolicy.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.log4j.receivers.rewrite;
+
+import org.apache.log4j.Logger;
+import org.apache.log4j.helpers.LogLog;
+import org.apache.log4j.spi.LoggingEvent;
+
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This policy rewrites events by evaluating any
+ * JavaBean properties on the message object and adding them
+ * to the event properties. If the message object has a
+ * message property, the value of that property will be
+ * used as the message for the rewritten event and will
+ * not be added to the event properties. Values from the
+ * JavaBean properties will replace any existing property
+ * with the same name.
+ *
+ * The combination of the RewriteAppender and this policy
+ * performs the same actions as the ReflectionFilter from log4j 1.3.
+ */
+public class ReflectionRewritePolicy implements RewritePolicy {
+ /**
+ * {@inheritDoc}
+ */
+ public LoggingEvent rewrite(final LoggingEvent source) {
+ Object msg = source.getMessage();
+ if (!(msg instanceof String)) {
+ Object newMsg = msg;
+ Map rewriteProps = new HashMap(source.getProperties());
+
+ try {
+ PropertyDescriptor[] props = Introspector.getBeanInfo(
+ msg.getClass(), Object.class).getPropertyDescriptors();
+ if (props.length > 0) {
+ for (int i=0;i<props.length;i++) {
+ try {
+ Object propertyValue =
+ props[i].getReadMethod().invoke(msg,
+ (Object[]) null);
+ if ("message".equalsIgnoreCase(props[i].getName())) {
+ newMsg = propertyValue;
+ } else {
+ rewriteProps.put(props[i].getName(), propertyValue);
+ }
+ } catch (Exception e) {
+ LogLog.warn("Unable to evaluate property " +
+ props[i].getName(), e);
+ }
+ }
+ return new LoggingEvent(
+ source.getFQNOfLoggerClass(),
+ source.getLogger() != null ? source.getLogger(): Logger.getLogger(source.getLoggerName()),
+ source.getTimeStamp(),
+ source.getLevel(),
+ newMsg,
+ source.getThreadName(),
+ source.getThrowableInformation(),
+ source.getNDC(),
+ source.getLocationInformation(),
+ rewriteProps);
+ }
+ } catch (Exception e) {
+ LogLog.warn("Unable to get property descriptors", e);
+ }
+
+ }
+ return source;
+ }
+}
diff --git a/src/main/java/org/apache/log4j/receivers/rewrite/RewriteAppender.java b/src/main/java/org/apache/log4j/receivers/rewrite/RewriteAppender.java
new file mode 100644
index 0000000..c768c37
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/rewrite/RewriteAppender.java
@@ -0,0 +1,199 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.log4j.receivers.rewrite;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.AppenderSkeleton;
+import org.apache.log4j.helpers.AppenderAttachableImpl;
+import org.apache.log4j.spi.AppenderAttachable;
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.spi.OptionHandler;
+import org.apache.log4j.xml.UnrecognizedElementHandler;
+import org.w3c.dom.Element;
+
+import java.util.Enumeration;
+import java.util.Properties;
+
+/**
+ * This appender forwards a logging request to another
+ * appender after possibly rewriting the logging event.
+ *
+ * This appender (with the appropriate policy)
+ * replaces the MapFilter, PropertyFilter and ReflectionFilter
+ * from log4j 1.3.
+ */
+public class RewriteAppender extends AppenderSkeleton
+ implements AppenderAttachable, UnrecognizedElementHandler {
+ /**
+ * Rewrite policy.
+ */
+ private RewritePolicy policy;
+ /**
+ * Nested appenders.
+ */
+ private final AppenderAttachableImpl appenders;
+
+ public RewriteAppender() {
+ appenders = new AppenderAttachableImpl();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected void append(final LoggingEvent event) {
+ LoggingEvent rewritten = event;
+ if (policy != null) {
+ rewritten = policy.rewrite(event);
+ }
+ if (rewritten != null) {
+ synchronized (appenders) {
+ appenders.appendLoopOnAppenders(rewritten);
+ }
+ }
+ }
+
+ /**
+ * Add appender.
+ *
+ * @param newAppender appender to add, may not be null.
+ */
+ public void addAppender(final Appender newAppender) {
+ synchronized (appenders) {
+ appenders.addAppender(newAppender);
+ }
+ }
+
+ /**
+ * Get iterator over attached appenders.
+ * @return iterator or null if no attached appenders.
+ */
+ public Enumeration getAllAppenders() {
+ synchronized (appenders) {
+ return appenders.getAllAppenders();
+ }
+ }
+
+ /**
+ * Get appender by name.
+ *
+ * @param name name, may not be null.
+ * @return matching appender or null.
+ */
+ public Appender getAppender(final String name) {
+ synchronized (appenders) {
+ return appenders.getAppender(name);
+ }
+ }
+
+
+ /**
+ * Close this <code>AsyncAppender</code> by interrupting the dispatcher
+ * thread which will process all pending events before exiting.
+ */
+ public void close() {
+ closed = true;
+ //
+ // close all attached appenders.
+ //
+ synchronized (appenders) {
+ Enumeration iter = appenders.getAllAppenders();
+
+ if (iter != null) {
+ while (iter.hasMoreElements()) {
+ Object next = iter.nextElement();
+
+ if (next instanceof Appender) {
+ ((Appender) next).close();
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Determines if specified appender is attached.
+ * @param appender appender.
+ * @return true if attached.
+ */
+ public boolean isAttached(final Appender appender) {
+ synchronized (appenders) {
+ return appenders.isAttached(appender);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean requiresLayout() {
+ return false;
+ }
+
+ /**
+ * Removes and closes all attached appenders.
+ */
+ public void removeAllAppenders() {
+ synchronized (appenders) {
+ appenders.removeAllAppenders();
+ }
+ }
+
+ /**
+ * Removes an appender.
+ * @param appender appender to remove.
+ */
+ public void removeAppender(final Appender appender) {
+ synchronized (appenders) {
+ appenders.removeAppender(appender);
+ }
+ }
+
+ /**
+ * Remove appender by name.
+ * @param name name.
+ */
+ public void removeAppender(final String name) {
+ synchronized (appenders) {
+ appenders.removeAppender(name);
+ }
+ }
+
+
+ public void setRewritePolicy(final RewritePolicy rewritePolicy) {
+ policy = rewritePolicy;
+ }
+ /**
+ * {@inheritDoc}
+ */
+ public boolean parseUnrecognizedElement(final Element element,
+ final Properties props) throws Exception {
+ final String nodeName = element.getNodeName();
+ if ("rewritePolicy".equals(nodeName)) {
+ Object rewritePolicy =
+ org.apache.log4j.xml.DOMConfigurator.parseElement(
+ element, props, RewritePolicy.class);
+ if (rewritePolicy != null) {
+ if (rewritePolicy instanceof OptionHandler) {
+ ((OptionHandler) rewritePolicy).activateOptions();
+ }
+ this.setRewritePolicy((RewritePolicy) rewritePolicy);
+ }
+ return true;
+ }
+ return false;
+ }
+
+}
diff --git a/src/main/java/org/apache/log4j/receivers/rewrite/RewritePolicy.java b/src/main/java/org/apache/log4j/receivers/rewrite/RewritePolicy.java
new file mode 100644
index 0000000..a351ed6
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/rewrite/RewritePolicy.java
@@ -0,0 +1,37 @@
+package org.apache.log4j.receivers.rewrite;
+
+import org.apache.log4j.spi.LoggingEvent;
+
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements. See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+/**
+ * This interface is implemented to provide a rewrite
+ * strategy for RewriteAppender. RewriteAppender will
+ * call the rewrite method with a source logging event.
+ * The strategy may return that event, create a new event
+ * or return null to suppress the logging request.
+ */
+public interface RewritePolicy {
+ /**
+ * Rewrite a logging event.
+ * @param source a logging event that may be returned or
+ * used to create a new logging event.
+ * @return a logging event or null to suppress processing.
+ */
+ LoggingEvent rewrite(final LoggingEvent source);
+}
diff --git a/src/main/java/org/apache/log4j/receivers/spi/Decoder.java b/src/main/java/org/apache/log4j/receivers/spi/Decoder.java
new file mode 100644
index 0000000..7b9c603
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/spi/Decoder.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.receivers.spi;
+
+
+import org.apache.log4j.spi.LoggingEvent;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Map;
+import java.util.Vector;
+
+
+/**
+ * Allow LoggingEvents to be reconstructed from a different format
+ * (usually XML).
+ *
+ * @author Scott Deboy (sdeboy@apache.org)
+ */
+public interface Decoder {
+ /**
+ * Decode events from document.
+ * @param document document to decode.
+ * @return list of LoggingEvent instances.
+ */
+ Vector decodeEvents(String document);
+
+ /**
+ * Decode event from string.
+ * @param event string representation of event
+ * @return event
+ */
+ LoggingEvent decode(String event);
+
+ /**
+ * Decode event from document retreived from URL.
+ * @param url url of document
+ * @return list of LoggingEvent instances.
+ * @throws IOException if IO error resolving document.
+ */
+ Vector decode(URL url) throws IOException;
+
+ /**
+ * Sets additional properties.
+ * @param additionalProperties map of additional properties.
+ */
+ void setAdditionalProperties(Map additionalProperties);
+}
diff --git a/src/main/java/org/apache/log4j/receivers/varia/ListModelAppender.java b/src/main/java/org/apache/log4j/receivers/varia/ListModelAppender.java
new file mode 100644
index 0000000..702e028
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/varia/ListModelAppender.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.receivers.varia;
+
+import org.apache.log4j.AppenderSkeleton;
+import org.apache.log4j.spi.LoggingEvent;
+
+import javax.swing.*;
+
+
+/**
+ * A very basic appender that takes the events and stores them in to a
+ * ListModel for late retrieval.
+ *
+ *
+ * @author Paul Smith (psmith@apache.org)
+ *
+ */
+public final class ListModelAppender extends AppenderSkeleton {
+ /**
+ * Default list model.
+ */
+ private final DefaultListModel model = new DefaultListModel();
+
+ /**
+ * Constructs a ListModelAppender.
+ */
+ public ListModelAppender() {
+ super(true);
+ }
+ /**
+ * Returns a reference to the ListModel that contains all the LoggingEvents
+ * that have been appended to this class.
+ *
+ * @return the list model
+ */
+ public ListModel getModel() {
+ return model;
+ }
+
+ /** {@inheritDoc} */
+ protected void append(final LoggingEvent event) {
+ model.addElement(event);
+ }
+
+ /** {@inheritDoc} */
+ public void close() {
+ clearModel();
+ }
+
+ /**
+ * Removes all the Events from the model.
+ */
+ public void clearModel() {
+ model.clear();
+ }
+
+ /** {@inheritDoc} */
+ public boolean requiresLayout() {
+ return false;
+ }
+
+}
diff --git a/src/main/java/org/apache/log4j/receivers/varia/LogFilePatternReceiver.java b/src/main/java/org/apache/log4j/receivers/varia/LogFilePatternReceiver.java
new file mode 100644
index 0000000..c673c4a
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/varia/LogFilePatternReceiver.java
@@ -0,0 +1,1033 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.receivers.varia;
+
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.apache.log4j.component.helpers.Constants;
+import org.apache.log4j.component.plugins.Receiver;
+import org.apache.log4j.rule.ExpressionRule;
+import org.apache.log4j.rule.Rule;
+import org.apache.log4j.spi.LocationInfo;
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.spi.ThrowableInformation;
+
+import java.io.*;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.regex.MatchResult;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+/**
+ * LogFilePatternReceiver can parse and tail log files, converting entries into
+ * LoggingEvents. If the file doesn't exist when the receiver is initialized, the
+ * receiver will look for the file once every 10 seconds.
+ * <p>
+ * This receiver relies on java.util.regex features to perform the parsing of text in the
+ * log file, however the only regular expression field explicitly supported is
+ * a glob-style wildcard used to ignore fields in the log file if needed. All other
+ * fields are parsed by using the supplied keywords.
+ * <p>
+ * <b>Features:</b><br>
+ * - specify the URL of the log file to be processed<br>
+ * - specify the timestamp format in the file (if one exists, using patterns from {@link java.text.SimpleDateFormat})<br>
+ * - specify the pattern (logFormat) used in the log file using keywords, a wildcard character (*) and fixed text<br>
+ * - 'tail' the file (allows the contents of the file to be continually read and new events processed)<br>
+ * - supports the parsing of multi-line messages and exceptions
+ * - 'hostname' property set to URL host (or 'file' if not available)
+ * - 'application' property set to URL path (or value of fileURL if not available)
+ *<p>
+ * <b>Keywords:</b><br>
+ * TIMESTAMP<br>
+ * LOGGER<br>
+ * LEVEL<br>
+ * THREAD<br>
+ * CLASS<br>
+ * FILE<br>
+ * LINE<br>
+ * METHOD<br>
+ * RELATIVETIME<br>
+ * MESSAGE<br>
+ * NDC<br>
+ * PROP(key)<br>
+ * <p>
+ * Use a * to ignore portions of the log format that should be ignored
+ * <p>
+ * Example:<br>
+ * If your file's patternlayout is this:<br>
+ * <b>%d %-5p [%t] %C{2} (%F:%L) - %m%n</b>
+ *<p>
+ * specify this as the log format:<br>
+ * <b>TIMESTAMP LEVEL [THREAD] CLASS (FILE:LINE) - MESSAGE</b>
+ *<p>
+ * To define a PROPERTY field, use PROP(key)
+ * <p>
+ * Example:<br>
+ * If you used the RELATIVETIME pattern layout character in the file,
+ * you can use PROP(RELATIVETIME) in the logFormat definition to assign
+ * the RELATIVETIME field as a property on the event.
+ * <p>
+ * If your file's patternlayout is this:<br>
+ * <b>%r [%t] %-5p %c %x - %m%n</b>
+ *<p>
+ * specify this as the log format:<br>
+ * <b>PROP(RELATIVETIME) [THREAD] LEVEL LOGGER * - MESSAGE</b>
+ * <p>
+ * Note the * - it can be used to ignore a single word or sequence of words in the log file
+ * (in order for the wildcard to ignore a sequence of words, the text being ignored must be
+ * followed by some delimiter, like '-' or '[') - ndc is being ignored in the following example.
+ * <p>
+ * Assign a filterExpression in order to only process events which match a filter.
+ * If a filterExpression is not assigned, all events are processed.
+ *<p>
+ * <b>Limitations:</b><br>
+ * - no support for the single-line version of throwable supported by patternlayout<br>
+ * (this version of throwable will be included as the last line of the message)<br>
+ * - the relativetime patternLayout character must be set as a property: PROP(RELATIVETIME)<br>
+ * - messages should appear as the last field of the logFormat because the variability in message content<br>
+ * - exceptions are converted if the exception stack trace (other than the first line of the exception)<br>
+ * is stored in the log file with a tab followed by the word 'at' as the first characters in the line<br>
+ * - tailing may fail if the file rolls over.
+ *<p>
+ * <b>Example receiver configuration settings</b> (add these as params, specifying a LogFilePatternReceiver 'plugin'):<br>
+ * param: "timestampFormat" value="yyyy-MM-d HH:mm:ss,SSS"<br>
+ * param: "logFormat" value="PROP(RELATIVETIME) [THREAD] LEVEL LOGGER * - MESSAGE"<br>
+ * param: "fileURL" value="file:///c:/events.log"<br>
+ * param: "tailing" value="true"
+ *<p>
+ * This configuration will be able to process these sample events:<br>
+ * 710 [ Thread-0] DEBUG first.logger first - <test> <test2>something here</test2> <test3 blah=something/> <test4> <test5>something else</test5> </test4></test><br>
+ * 880 [ Thread-2] DEBUG first.logger third - <test> <test2>something here</test2> <test3 blah=something/> <test4> <test5>something else</test5> </test4></test><br>
+ * 880 [ Thread-0] INFO first.logger first - infomsg-0<br>
+ * java.lang.Exception: someexception-first<br>
+ * at Generator2.run(Generator2.java:102)<br>
+ *
+ *@author Scott Deboy
+ */
+public class LogFilePatternReceiver extends Receiver {
+ private final List keywords = new ArrayList();
+
+ private static final String PROP_START = "PROP(";
+ private static final String PROP_END = ")";
+
+ private static final String LOGGER = "LOGGER";
+ private static final String MESSAGE = "MESSAGE";
+ private static final String TIMESTAMP = "TIMESTAMP";
+ private static final String NDC = "NDC";
+ private static final String LEVEL = "LEVEL";
+ private static final String THREAD = "THREAD";
+ private static final String CLASS = "CLASS";
+ private static final String FILE = "FILE";
+ private static final String LINE = "LINE";
+ private static final String METHOD = "METHOD";
+
+ private static final String DEFAULT_HOST = "file";
+
+ //all lines other than first line of exception begin with tab followed by 'at' followed by text
+ private static final String EXCEPTION_PATTERN = "^\\s+at.*";
+ private static final String REGEXP_DEFAULT_WILDCARD = ".*?";
+ private static final String REGEXP_GREEDY_WILDCARD = ".*";
+ private static final String PATTERN_WILDCARD = "*";
+ private static final String NOSPACE_GROUP = "(\\S*\\s*?)";
+ private static final String DEFAULT_GROUP = "(" + REGEXP_DEFAULT_WILDCARD + ")";
+ private static final String GREEDY_GROUP = "(" + REGEXP_GREEDY_WILDCARD + ")";
+ private static final String MULTIPLE_SPACES_REGEXP = "[ ]+";
+ private final String newLine = System.getProperty("line.separator");
+
+ private final String[] emptyException = new String[] { "" };
+
+ private SimpleDateFormat dateFormat;
+ private String timestampFormat = "yyyy-MM-d HH:mm:ss,SSS";
+ private String logFormat;
+ private String customLevelDefinitions;
+ private String fileURL;
+ private String host;
+ private String path;
+ private boolean tailing;
+ private String filterExpression;
+ private long waitMillis = 2000; //default 2 seconds
+
+ private static final String VALID_DATEFORMAT_CHARS = "GyMwWDdFEaHkKhmsSzZ";
+ private static final String VALID_DATEFORMAT_CHAR_PATTERN = "[" + VALID_DATEFORMAT_CHARS + "]";
+
+ private Rule expressionRule;
+
+ private Map currentMap;
+ private List additionalLines;
+ private List matchingKeywords;
+
+ private String regexp;
+ private Reader reader;
+ private Pattern regexpPattern;
+ private Pattern exceptionPattern;
+ private String timestampPatternText;
+
+ private boolean useCurrentThread;
+ public static final int MISSING_FILE_RETRY_MILLIS = 10000;
+ private boolean appendNonMatches;
+ private final Map customLevelDefinitionMap = new HashMap();
+
+ public LogFilePatternReceiver() {
+ keywords.add(TIMESTAMP);
+ keywords.add(LOGGER);
+ keywords.add(LEVEL);
+ keywords.add(THREAD);
+ keywords.add(CLASS);
+ keywords.add(FILE);
+ keywords.add(LINE);
+ keywords.add(METHOD);
+ keywords.add(MESSAGE);
+ keywords.add(NDC);
+ try {
+ exceptionPattern = Pattern.compile(EXCEPTION_PATTERN);
+ } catch (PatternSyntaxException pse) {
+ //shouldn't happen
+ }
+ }
+
+ /**
+ * Accessor
+ *
+ * @return file URL
+ */
+ public String getFileURL() {
+ return fileURL;
+ }
+
+ /**
+ * Mutator
+ *
+ * @param fileURL
+ */
+ public void setFileURL(String fileURL) {
+ this.fileURL = fileURL;
+ }
+
+ /**
+ * If the log file contains non-log4j level strings, they can be mapped to log4j levels using the format (android example):
+ * V=TRACE,D=DEBUG,I=INFO,W=WARN,E=ERROR,F=FATAL,S=OFF
+ *
+ * @param customLevelDefinitions the level definition string
+ */
+ public void setCustomLevelDefinitions(String customLevelDefinitions) {
+ this.customLevelDefinitions = customLevelDefinitions;
+ }
+
+ public String getCustomLevelDefinitions() {
+ return customLevelDefinitions;
+ }
+
+ /**
+ * Accessor
+ * @return append non matches
+ */
+ public boolean isAppendNonMatches() {
+ return appendNonMatches;
+ }
+
+ /**
+ * Mutator
+ * @param appendNonMatches
+ */
+ public void setAppendNonMatches(boolean appendNonMatches) {
+ this.appendNonMatches = appendNonMatches;
+ }
+
+ /**
+ * Accessor
+ *
+ * @return filter expression
+ */
+ public String getFilterExpression() {
+ return filterExpression;
+ }
+
+ /**
+ * Mutator
+ *
+ * @param filterExpression
+ */
+ public void setFilterExpression(String filterExpression) {
+ this.filterExpression = filterExpression;
+ }
+
+ /**
+ * Accessor
+ *
+ * @return tailing
+ */
+ public boolean isTailing() {
+ return tailing;
+ }
+
+ /**
+ * Mutator
+ *
+ * @param tailing
+ */
+ public void setTailing(boolean tailing) {
+ this.tailing = tailing;
+ }
+
+ /**
+ * When true, this property uses the current Thread to perform the import,
+ * otherwise when false (the default), a new Thread is created and started to manage
+ * the import.
+ * @return
+ */
+ public final boolean isUseCurrentThread() {
+ return useCurrentThread;
+ }
+
+ /**
+ * Sets whether the current Thread or a new Thread is created to perform the import,
+ * the default being false (new Thread created).
+ *
+ * @param useCurrentThread
+ */
+ public final void setUseCurrentThread(boolean useCurrentThread) {
+ this.useCurrentThread = useCurrentThread;
+ }
+
+ /**
+ * Accessor
+ *
+ * @return log format
+ */
+ public String getLogFormat() {
+ return logFormat;
+ }
+
+ /**
+ * Mutator
+ *
+ * @param logFormat
+ * the format
+ */
+ public void setLogFormat(String logFormat) {
+ this.logFormat = logFormat;
+ }
+
+ /**
+ * Mutator. Specify a pattern from {@link java.text.SimpleDateFormat}
+ *
+ * @param timestampFormat
+ */
+ public void setTimestampFormat(String timestampFormat) {
+ this.timestampFormat = timestampFormat;
+ }
+
+ /**
+ * Accessor
+ *
+ * @return timestamp format
+ */
+ public String getTimestampFormat() {
+ return timestampFormat;
+ }
+
+ /**
+ * Accessor
+ * @return millis between retrieves of content
+ */
+ public long getWaitMillis() {
+ return waitMillis;
+ }
+
+ /**
+ * Mutator
+ * @param waitMillis
+ */
+ public void setWaitMillis(long waitMillis) {
+ this.waitMillis = waitMillis;
+ }
+
+ /**
+ * Walk the additionalLines list, looking for the EXCEPTION_PATTERN.
+ * <p>
+ * Return the index of the first matched line
+ * (the match may be the 1st line of an exception)
+ * <p>
+ * Assumptions: <br>
+ * - the additionalLines list may contain both message and exception lines<br>
+ * - message lines are added to the additionalLines list and then
+ * exception lines (all message lines occur in the list prior to all
+ * exception lines)
+ *
+ * @return -1 if no exception line exists, line number otherwise
+ */
+ private int getExceptionLine() {
+ for (int i = 0; i < additionalLines.size(); i++) {
+ Matcher exceptionMatcher = exceptionPattern.matcher((String)additionalLines.get(i));
+ if (exceptionMatcher.matches()) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Combine all message lines occuring in the additionalLines list, adding
+ * a newline character between each line
+ * <p>
+ * the event will already have a message - combine this message
+ * with the message lines in the additionalLines list
+ * (all entries prior to the exceptionLine index)
+ *
+ * @param firstMessageLine primary message line
+ * @param exceptionLine index of first exception line
+ * @return message
+ */
+ private String buildMessage(String firstMessageLine, int exceptionLine) {
+ if (additionalLines.size() == 0) {
+ return firstMessageLine;
+ }
+ StringBuffer message = new StringBuffer();
+ if (firstMessageLine != null) {
+ message.append(firstMessageLine);
+ }
+
+ int linesToProcess = (exceptionLine == -1?additionalLines.size(): exceptionLine);
+
+ for (int i = 0; i < linesToProcess; i++) {
+ message.append(newLine);
+ message.append(additionalLines.get(i));
+ }
+ return message.toString();
+ }
+
+ /**
+ * Combine all exception lines occuring in the additionalLines list into a
+ * String array
+ * <p>
+ * (all entries equal to or greater than the exceptionLine index)
+ *
+ * @param exceptionLine index of first exception line
+ * @return exception
+ */
+ private String[] buildException(int exceptionLine) {
+ if (exceptionLine == -1) {
+ return emptyException;
+ }
+ String[] exception = new String[additionalLines.size() - exceptionLine - 1];
+ for (int i = 0; i < exception.length; i++) {
+ exception[i] = (String) additionalLines.get(i + exceptionLine);
+ }
+ return exception;
+ }
+
+ /**
+ * Construct a logging event from currentMap and additionalLines
+ * (additionalLines contains multiple message lines and any exception lines)
+ * <p>
+ * CurrentMap and additionalLines are cleared in the process
+ *
+ * @return event
+ */
+ private LoggingEvent buildEvent() {
+ if (currentMap.size() == 0) {
+ if (additionalLines.size() > 0) {
+ for (Iterator iter = additionalLines.iterator();iter.hasNext();) {
+ getLogger().info("found non-matching line: " + iter.next());
+ }
+ }
+ additionalLines.clear();
+ return null;
+ }
+ //the current map contains fields - build an event
+ int exceptionLine = getExceptionLine();
+ String[] exception = buildException(exceptionLine);
+
+ //messages are listed before exceptions in additionallines
+ if (additionalLines.size() > 0 && exception.length > 0) {
+ currentMap.put(MESSAGE, buildMessage((String) currentMap.get(MESSAGE),
+ exceptionLine));
+ }
+ LoggingEvent event = convertToEvent(currentMap, exception);
+ currentMap.clear();
+ additionalLines.clear();
+ return event;
+ }
+
+ /**
+ * Read, parse and optionally tail the log file, converting entries into logging events.
+ *
+ * A runtimeException is thrown if the logFormat pattern is malformed.
+ *
+ * @param bufferedReader
+ * @throws IOException
+ */
+ protected void process(BufferedReader bufferedReader) throws IOException {
+ Matcher eventMatcher;
+ Matcher exceptionMatcher;
+ String line;
+ while ((line = bufferedReader.readLine()) != null) {
+ //skip empty line entries
+ eventMatcher = regexpPattern.matcher(line);
+ if (line.trim().equals("")) {continue;}
+ exceptionMatcher = exceptionPattern.matcher(line);
+ if (eventMatcher.matches()) {
+ //build an event from the previous match (held in current map)
+ LoggingEvent event = buildEvent();
+ if (event != null) {
+ if (passesExpression(event)) {
+ doPost(event);
+ }
+ }
+ currentMap.putAll(processEvent(eventMatcher.toMatchResult()));
+ } else if (exceptionMatcher.matches()) {
+ //an exception line
+ additionalLines.add(line);
+ } else {
+ //neither...either post an event with the line or append as additional lines
+ //if this was a logging event with multiple lines, each line will show up as its own event instead of being
+ //appended as multiple lines on the same event..
+ //choice is to have each non-matching line show up as its own line, or append them all to a previous event
+ if (appendNonMatches) {
+ //hold on to the previous time, so we can do our best to preserve time-based ordering if the event is a non-match
+ String lastTime = (String)currentMap.get(TIMESTAMP);
+ //build an event from the previous match (held in current map)
+ if (currentMap.size() > 0) {
+ LoggingEvent event = buildEvent();
+ if (event != null) {
+ if (passesExpression(event)) {
+ doPost(event);
+ }
+ }
+ }
+ if (lastTime != null) {
+ currentMap.put(TIMESTAMP, lastTime);
+ }
+ currentMap.put(MESSAGE, line);
+ } else {
+ additionalLines.add(line);
+ }
+ }
+ }
+
+ //process last event if one exists
+ LoggingEvent event = buildEvent();
+ if (event != null) {
+ if (passesExpression(event)) {
+ doPost(event);
+ }
+ }
+ }
+
+ protected void createPattern() {
+ regexpPattern = Pattern.compile(regexp);
+ }
+
+ /**
+ * Helper method that supports the evaluation of the expression
+ *
+ * @param event
+ * @return true if expression isn't set, or the result of the evaluation otherwise
+ */
+ private boolean passesExpression(LoggingEvent event) {
+ if (event != null) {
+ if (expressionRule != null) {
+ return (expressionRule.evaluate(event, null));
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Convert the match into a map.
+ * <p>
+ * Relies on the fact that the matchingKeywords list is in the same
+ * order as the groups in the regular expression
+ *
+ * @param result
+ * @return map
+ */
+ private Map processEvent(MatchResult result) {
+ Map map = new HashMap();
+ //group zero is the entire match - process all other groups
+ for (int i = 1; i < result.groupCount() + 1; i++) {
+ Object key = matchingKeywords.get(i - 1);
+ Object value = result.group(i);
+ map.put(key, value);
+
+ }
+ return map;
+ }
+
+ /**
+ * Helper method that will convert timestamp format to a pattern
+ *
+ *
+ * @return string
+ */
+ private String convertTimestamp() {
+ //some locales (for example, French) generate timestamp text with characters not included in \w -
+ // now using \S (all non-whitespace characters) instead of /w
+ String result = timestampFormat.replaceAll(VALID_DATEFORMAT_CHAR_PATTERN + "+", "\\\\S+");
+ //make sure dots in timestamp are escaped
+ result = result.replaceAll(Pattern.quote("."), "\\\\.");
+ return result;
+ }
+
+ protected void setHost(String host) {
+ this.host = host;
+ }
+
+ protected void setPath(String path) {
+ this.path = path;
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ /**
+ * Build the regular expression needed to parse log entries
+ *
+ */
+ protected void initialize() {
+ if (host == null && path == null) {
+ try {
+ URL url = new URL(fileURL);
+ host = url.getHost();
+ path = url.getPath();
+ } catch (MalformedURLException e1) {
+ // TODO Auto-generated catch block
+ e1.printStackTrace();
+ }
+ }
+ if (host == null || host.trim().equals("")) {
+ host = DEFAULT_HOST;
+ }
+ if (path == null || path.trim().equals("")) {
+ path = fileURL;
+ }
+
+ currentMap = new HashMap();
+ additionalLines = new ArrayList();
+ matchingKeywords = new ArrayList();
+
+ if (timestampFormat != null) {
+ dateFormat = new SimpleDateFormat(quoteTimeStampChars(timestampFormat));
+ timestampPatternText = convertTimestamp();
+ }
+ //if custom level definitions exist, parse them
+ updateCustomLevelDefinitionMap();
+ try {
+ if (filterExpression != null) {
+ expressionRule = ExpressionRule.getRule(filterExpression);
+ }
+ } catch (Exception e) {
+ getLogger().warn("Invalid filter expression: " + filterExpression, e);
+ }
+
+ List buildingKeywords = new ArrayList();
+
+ String newPattern = logFormat;
+
+ int index = 0;
+ String current = newPattern;
+ //build a list of property names and temporarily replace the property with an empty string,
+ //we'll rebuild the pattern later
+ List propertyNames = new ArrayList();
+ while (index > -1) {
+ if (current.indexOf(PROP_START) > -1 && current.indexOf(PROP_END) > -1) {
+ index = current.indexOf(PROP_START);
+ String longPropertyName = current.substring(current.indexOf(PROP_START), current.indexOf(PROP_END) + 1);
+ String shortProp = getShortPropertyName(longPropertyName);
+ buildingKeywords.add(shortProp);
+ propertyNames.add(longPropertyName);
+ current = current.substring(longPropertyName.length() + 1 + index);
+ newPattern = singleReplace(newPattern, longPropertyName, new Integer(buildingKeywords.size() -1).toString());
+ } else {
+ //no properties
+ index = -1;
+ }
+ }
+
+ /*
+ * we're using a treemap, so the index will be used as the key to ensure
+ * keywords are ordered correctly
+ *
+ * examine pattern, adding keywords to an index-based map patterns can
+ * contain only one of these per entry...properties are the only 'keyword'
+ * that can occur multiple times in an entry
+ */
+ Iterator iter = keywords.iterator();
+ while (iter.hasNext()) {
+ String keyword = (String) iter.next();
+ int index2 = newPattern.indexOf(keyword);
+ if (index2 > -1) {
+ buildingKeywords.add(keyword);
+ newPattern = singleReplace(newPattern, keyword, new Integer(buildingKeywords.size() -1).toString());
+ }
+ }
+
+ String buildingInt = "";
+
+ for (int i=0;i<newPattern.length();i++) {
+ String thisValue = String.valueOf(newPattern.substring(i, i+1));
+ if (isInteger(thisValue)) {
+ buildingInt = buildingInt + thisValue;
+ } else {
+ if (isInteger(buildingInt)) {
+ matchingKeywords.add(buildingKeywords.get(Integer.parseInt(buildingInt)));
+ }
+ //reset
+ buildingInt = "";
+ }
+ }
+
+ //if the very last value is an int, make sure to add it
+ if (isInteger(buildingInt)) {
+ matchingKeywords.add(buildingKeywords.get(Integer.parseInt(buildingInt)));
+ }
+
+ newPattern = replaceMetaChars(newPattern);
+
+ //compress one or more spaces in the pattern into the [ ]+ regexp
+ //(supports padding of level in log files)
+ newPattern = newPattern.replaceAll(MULTIPLE_SPACES_REGEXP, MULTIPLE_SPACES_REGEXP);
+ newPattern = newPattern.replaceAll(Pattern.quote(PATTERN_WILDCARD), REGEXP_DEFAULT_WILDCARD);
+ //use buildingKeywords here to ensure correct order
+ for (int i = 0;i<buildingKeywords.size();i++) {
+ String keyword = (String) buildingKeywords.get(i);
+ //make the final keyword greedy (we're assuming it's the message)
+ if (i == (buildingKeywords.size() - 1)) {
+ newPattern = singleReplace(newPattern, String.valueOf(i), GREEDY_GROUP);
+ } else if (TIMESTAMP.equals(keyword)) {
+ newPattern = singleReplace(newPattern, String.valueOf(i), "(" + timestampPatternText + ")");
+ } else if (LOGGER.equals(keyword) || LEVEL.equals(keyword)) {
+ newPattern = singleReplace(newPattern, String.valueOf(i), NOSPACE_GROUP);
+ } else {
+ newPattern = singleReplace(newPattern, String.valueOf(i), DEFAULT_GROUP);
+ }
+ }
+
+ regexp = newPattern;
+ getLogger().debug("regexp is " + regexp);
+ }
+
+ private void updateCustomLevelDefinitionMap() {
+ if (customLevelDefinitions != null) {
+ StringTokenizer entryTokenizer = new StringTokenizer(customLevelDefinitions, ",");
+
+ customLevelDefinitionMap.clear();
+ while (entryTokenizer.hasMoreTokens()) {
+ StringTokenizer innerTokenizer = new StringTokenizer(entryTokenizer.nextToken(), "=");
+ customLevelDefinitionMap.put(innerTokenizer.nextToken(), Level.toLevel(innerTokenizer.nextToken()));
+ }
+ }
+ }
+
+ private boolean isInteger(String value) {
+ try {
+ Integer.parseInt(value);
+ return true;
+ } catch (NumberFormatException nfe) {
+ return false;
+ }
+ }
+
+ private String quoteTimeStampChars(String input) {
+ //put single quotes around text that isn't a supported dateformat char
+ StringBuffer result = new StringBuffer();
+ //ok to default to false because we also check for index zero below
+ boolean lastCharIsDateFormat = false;
+ for (int i = 0;i<input.length();i++) {
+ String thisVal = input.substring(i, i + 1);
+ boolean thisCharIsDateFormat = VALID_DATEFORMAT_CHARS.contains(thisVal);
+ //we have encountered a non-dateformat char
+ if (!thisCharIsDateFormat && (i == 0 || lastCharIsDateFormat)) {
+ result.append("'");
+ }
+ //we have encountered a dateformat char after previously encountering a non-dateformat char
+ if (thisCharIsDateFormat && i > 0 && !lastCharIsDateFormat) {
+ result.append("'");
+ }
+ lastCharIsDateFormat = thisCharIsDateFormat;
+ result.append(thisVal);
+ }
+ //append an end single-quote if we ended with non-dateformat char
+ if (!lastCharIsDateFormat) {
+ result.append("'");
+ }
+ return result.toString();
+ }
+
+ private String singleReplace(String inputString, String oldString, String newString)
+ {
+ int propLength = oldString.length();
+ int startPos = inputString.indexOf(oldString);
+ if (startPos == -1)
+ {
+ getLogger().info("string: " + oldString + " not found in input: " + inputString + " - returning input");
+ return inputString;
+ }
+ if (startPos == 0)
+ {
+ inputString = inputString.substring(propLength);
+ inputString = newString + inputString;
+ } else {
+ inputString = inputString.substring(0, startPos) + newString + inputString.substring(startPos + propLength);
+ }
+ return inputString;
+ }
+
+ private String getShortPropertyName(String longPropertyName)
+ {
+ String currentProp = longPropertyName.substring(longPropertyName.indexOf(PROP_START));
+ String prop = currentProp.substring(0, currentProp.indexOf(PROP_END) + 1);
+ String shortProp = prop.substring(PROP_START.length(), prop.length() - 1);
+ return shortProp;
+ }
+
+ /**
+ * Some perl5 characters may occur in the log file format.
+ * Escape these characters to prevent parsing errors.
+ *
+ * @param input
+ * @return string
+ */
+ private String replaceMetaChars(String input) {
+ //escape backslash first since that character is used to escape the remaining meta chars
+ input = input.replaceAll("\\\\", "\\\\\\");
+
+ //don't escape star - it's used as the wildcard
+ input = input.replaceAll(Pattern.quote("]"), "\\\\]");
+ input = input.replaceAll(Pattern.quote("["), "\\\\[");
+ input = input.replaceAll(Pattern.quote("^"), "\\\\^");
+ input = input.replaceAll(Pattern.quote("$"), "\\\\$");
+ input = input.replaceAll(Pattern.quote("."), "\\\\.");
+ input = input.replaceAll(Pattern.quote("|"), "\\\\|");
+ input = input.replaceAll(Pattern.quote("?"), "\\\\?");
+ input = input.replaceAll(Pattern.quote("+"), "\\\\+");
+ input = input.replaceAll(Pattern.quote("("), "\\\\(");
+ input = input.replaceAll(Pattern.quote(")"), "\\\\)");
+ input = input.replaceAll(Pattern.quote("-"), "\\\\-");
+ input = input.replaceAll(Pattern.quote("{"), "\\\\{");
+ input = input.replaceAll(Pattern.quote("}"), "\\\\}");
+ input = input.replaceAll(Pattern.quote("#"), "\\\\#");
+ return input;
+ }
+
+ /**
+ * Convert a keyword-to-values map to a LoggingEvent
+ *
+ * @param fieldMap
+ * @param exception
+ *
+ * @return logging event
+ */
+ private LoggingEvent convertToEvent(Map fieldMap, String[] exception) {
+ if (fieldMap == null) {
+ return null;
+ }
+
+ //a logger must exist at a minimum for the event to be processed
+ if (!fieldMap.containsKey(LOGGER)) {
+ fieldMap.put(LOGGER, "Unknown");
+ }
+ if (exception == null) {
+ exception = emptyException;
+ }
+
+ Logger logger = null;
+ long timeStamp = 0L;
+ String level = null;
+ String threadName = null;
+ Object message = null;
+ String ndc = null;
+ String className = null;
+ String methodName = null;
+ String eventFileName = null;
+ String lineNumber = null;
+ Hashtable properties = new Hashtable();
+
+ logger = Logger.getLogger((String) fieldMap.remove(LOGGER));
+
+ if ((dateFormat != null) && fieldMap.containsKey(TIMESTAMP)) {
+ try {
+ timeStamp = dateFormat.parse((String) fieldMap.remove(TIMESTAMP))
+ .getTime();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ //use current time if timestamp not parseable
+ if (timeStamp == 0L) {
+ timeStamp = System.currentTimeMillis();
+ }
+
+ message = fieldMap.remove(MESSAGE);
+ if (message == null) {
+ message = "";
+ }
+
+ level = (String) fieldMap.remove(LEVEL);
+ Level levelImpl;
+ if (level == null) {
+ levelImpl = Level.DEBUG;
+ } else {
+ //first try to resolve against custom level definition map, then fall back to regular levels
+ levelImpl = (Level) customLevelDefinitionMap.get(level);
+ if (levelImpl == null) {
+ levelImpl = Level.toLevel(level.trim());
+ if (!level.equals(levelImpl.toString())) {
+ //check custom level map
+ if (levelImpl == null) {
+ levelImpl = Level.DEBUG;
+ getLogger().debug("found unexpected level: " + level + ", logger: " + logger.getName() + ", msg: " + message);
+ //make sure the text that couldn't match a level is added to the message
+ message = level + " " + message;
+ }
+ }
+ }
+ }
+
+ threadName = (String) fieldMap.remove(THREAD);
+
+ ndc = (String) fieldMap.remove(NDC);
+
+ className = (String) fieldMap.remove(CLASS);
+
+ methodName = (String) fieldMap.remove(METHOD);
+
+ eventFileName = (String) fieldMap.remove(FILE);
+
+ lineNumber = (String) fieldMap.remove(LINE);
+
+ properties.put(Constants.HOSTNAME_KEY, host);
+ properties.put(Constants.APPLICATION_KEY, path);
+ properties.put(Constants.RECEIVER_NAME_KEY, getName());
+
+ //all remaining entries in fieldmap are properties
+ properties.putAll(fieldMap);
+
+ LocationInfo info = null;
+
+ if ((eventFileName != null) || (className != null) || (methodName != null)
+ || (lineNumber != null)) {
+ info = new LocationInfo(eventFileName, className, methodName, lineNumber);
+ } else {
+ info = LocationInfo.NA_LOCATION_INFO;
+ }
+
+ LoggingEvent event = new LoggingEvent(null,
+ logger, timeStamp, levelImpl, message,
+ threadName,
+ new ThrowableInformation(exception),
+ ndc,
+ info,
+ properties);
+
+ return event;
+ }
+
+// public static void main(String[] args) {
+// org.apache.log4j.Logger rootLogger = org.apache.log4j.Logger.getRootLogger();
+// org.apache.log4j.ConsoleAppender appender = new org.apache.log4j.ConsoleAppender(new org.apache.log4j.SimpleLayout());
+// appender.setName("console");
+// rootLogger.addAppender(appender);
+// LogFilePatternReceiver test = new LogFilePatternReceiver();
+// org.apache.log4j.spi.LoggerRepository repo = new org.apache.log4j.LoggerRepositoryExImpl(org.apache.log4j.LogManager.getLoggerRepository());
+// test.setLoggerRepository(repo);
+// test.setLogFormat("PROP(RELATIVETIME) [THREAD] LEVEL LOGGER * - MESSAGE");
+// test.setTailing(false);
+// test.setAppendNonMatches(true);
+// test.setTimestampFormat("yyyy-MM-d HH:mm:ss,SSS");
+// test.setFileURL("file:///C:/log/test.log");
+// test.initialize();
+// test.activateOptions();
+// }
+
+ /**
+ * Close the reader.
+ */
+ public void shutdown() {
+ getLogger().info(getPath() + " shutdown");
+ active = false;
+ try {
+ if (reader != null) {
+ reader.close();
+ reader = null;
+ }
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ }
+ }
+
+ /**
+ * Read and process the log file.
+ */
+ public void activateOptions() {
+ getLogger().info("activateOptions");
+ active = true;
+ Runnable runnable = new Runnable() {
+ public void run() {
+ initialize();
+ while (reader == null) {
+ getLogger().info("attempting to load file: " + getFileURL());
+ try {
+ reader = new InputStreamReader(new URL(getFileURL()).openStream());
+ } catch (FileNotFoundException fnfe) {
+ getLogger().info("file not available - will try again");
+ synchronized (this) {
+ try {
+ wait(MISSING_FILE_RETRY_MILLIS);
+ } catch (InterruptedException ie) {}
+ }
+ } catch (IOException ioe) {
+ getLogger().warn("unable to load file", ioe);
+ return;
+ }
+ }
+ try {
+ BufferedReader bufferedReader = new BufferedReader(reader);
+ createPattern();
+ do {
+ process(bufferedReader);
+ try {
+ synchronized (this) {
+ wait(waitMillis);
+ }
+ } catch (InterruptedException ie) {}
+ if (tailing) {
+ getLogger().debug("tailing file");
+ }
+ } while (tailing);
+
+ } catch (IOException ioe) {
+ //io exception - probably shut down
+ getLogger().info("stream closed");
+ }
+ getLogger().debug("processing " + path + " complete");
+ shutdown();
+ }
+ };
+ if(useCurrentThread) {
+ runnable.run();
+ }else {
+ new Thread(runnable, "LogFilePatternReceiver-"+getName()).start();
+ }
+ }
+}
diff --git a/src/main/java/org/apache/log4j/receivers/varia/LogFilePatternReceiverBeanInfo.java b/src/main/java/org/apache/log4j/receivers/varia/LogFilePatternReceiverBeanInfo.java
new file mode 100644
index 0000000..21bbcbf
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/varia/LogFilePatternReceiverBeanInfo.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.receivers.varia;
+
+import java.beans.PropertyDescriptor;
+import java.beans.SimpleBeanInfo;
+
+
+/**
+ * BeanInfo class for the meta-data of the LogFilePatternReceiver.
+ *
+ */
+public class LogFilePatternReceiverBeanInfo extends SimpleBeanInfo {
+ /* (non-Javadoc)
+ * @see java.beans.BeanInfo#getPropertyDescriptors()
+ */
+ public PropertyDescriptor[] getPropertyDescriptors() {
+ try {
+ return new PropertyDescriptor[] {
+ new PropertyDescriptor("fileURL", LogFilePatternReceiver.class),
+ new PropertyDescriptor(
+ "timestampFormat", LogFilePatternReceiver.class),
+ new PropertyDescriptor("logFormat", LogFilePatternReceiver.class),
+ new PropertyDescriptor("name", LogFilePatternReceiver.class),
+ new PropertyDescriptor("tailing", LogFilePatternReceiver.class),
+ new PropertyDescriptor(
+ "filterExpression", LogFilePatternReceiver.class),
+ new PropertyDescriptor("waitMillis", LogFilePatternReceiver.class),
+ new PropertyDescriptor("appendNonMatches", LogFilePatternReceiver.class),
+ new PropertyDescriptor("customLevelDefinitions", LogFilePatternReceiver.class),
+ new PropertyDescriptor("useCurrentThread", LogFilePatternReceiver.class),
+ };
+ } catch (Exception e) {
+ }
+
+ return null;
+ }
+}
diff --git a/src/main/java/org/apache/log4j/receivers/xml/LogFileXMLReceiver.java b/src/main/java/org/apache/log4j/receivers/xml/LogFileXMLReceiver.java
new file mode 100644
index 0000000..495bf2c
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/xml/LogFileXMLReceiver.java
@@ -0,0 +1,306 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.receivers.xml;
+
+import org.apache.log4j.component.helpers.Constants;
+import org.apache.log4j.component.plugins.Receiver;
+import org.apache.log4j.rule.ExpressionRule;
+import org.apache.log4j.rule.Rule;
+import org.apache.log4j.receivers.spi.Decoder;
+import org.apache.log4j.spi.LoggingEvent;
+
+import java.io.*;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Collection;
+import java.util.Iterator;
+
+/**
+ * LogFileXMLReceiver will read an xml-formated log file and make the events in the log file
+ * available to the log4j framework.
+ * <p>
+ * This receiver supports log files created using log4j's XMLLayout, as well as java.util.logging
+ * XMLFormatter (via the org.apache.log4j.spi.Decoder interface).
+ * <p>
+ * By default, log4j's XMLLayout is supported (no need to specify a decoder in that case).
+ * <p>
+ * To configure this receiver to support java.util.logging's XMLFormatter, specify a 'decoder' param
+ * of org.apache.log4j.xml.UtilLoggingXMLDecoder.
+ * <p>
+ * Tailing -may- work, but not in all cases (try using a file:// URL). If a process has a log file
+ * open, the receiver may be able to read and tail the file. If the process closes the file and
+ * reopens the file, the receiver may not be able to continue tailing the file.
+ * <p>
+ * An expressionFilter may be specified. Only events passing the expression will be forwarded to the
+ * log4j framework.
+ * <p>
+ * Once the event has been "posted", it will be handled by the appenders currently configured in the
+ * LoggerRespository.
+ *
+ * @author Scott Deboy <sdeboy@apache.org>
+ * @since 1.3
+ */
+
+public class LogFileXMLReceiver extends Receiver {
+ private String fileURL;
+ private Rule expressionRule;
+ private String filterExpression;
+ private String decoder = "org.apache.log4j.xml.XMLDecoder";
+ private boolean tailing = false;
+
+ private Decoder decoderInstance;
+ private Reader reader;
+ private static final String FILE_KEY = "file";
+ private String host;
+ private String path;
+ private boolean useCurrentThread;
+
+ /**
+ * Accessor
+ *
+ * @return file URL
+ */
+ public String getFileURL() {
+ return fileURL;
+ }
+
+ /**
+ * Specify the URL of the XML-formatted file to process.
+ *
+ * @param fileURL
+ */
+ public void setFileURL(String fileURL) {
+ this.fileURL = fileURL;
+ }
+
+ /**
+ * Accessor
+ *
+ * @return
+ */
+ public String getDecoder() {
+ return decoder;
+ }
+
+ /**
+ * Specify the class name implementing org.apache.log4j.spi.Decoder that can process the file.
+ *
+ * @param _decoder
+ */
+ public void setDecoder(String _decoder) {
+ decoder = _decoder;
+ }
+
+ /**
+ * Accessor
+ *
+ * @return filter expression
+ */
+ public String getFilterExpression() {
+ return filterExpression;
+ }
+
+ /**
+ * Accessor
+ *
+ * @return tailing flag
+ */
+ public boolean isTailing() {
+ return tailing;
+ }
+
+ /**
+ * Set the 'tailing' flag - may only work on file:// URLs and may stop tailing if the writing
+ * process closes the file and reopens.
+ *
+ * @param tailing
+ */
+ public void setTailing(boolean tailing) {
+ this.tailing = tailing;
+ }
+
+ /**
+ * Set the filter expression that will cause only events which pass the filter to be forwarded
+ * to the log4j framework.
+ *
+ * @param filterExpression
+ */
+ public void setFilterExpression(String filterExpression) {
+ this.filterExpression = filterExpression;
+ }
+
+ private boolean passesExpression(LoggingEvent event) {
+ if (event != null) {
+ if (expressionRule != null) {
+ return (expressionRule.evaluate(event, null));
+ }
+ }
+ return true;
+ }
+
+ public static void main(String[] args) {
+ /*
+ * LogFileXMLReceiver test = new LogFileXMLReceiver();
+ * test.setFileURL("file:///c:/samplelog.xml"); test.setFilterExpression("level >= TRACE");
+ * test.activateOptions();
+ */
+ }
+
+ /**
+ * Close the receiver, release any resources that are accessing the file.
+ */
+ public void shutdown() {
+ try {
+ if (reader != null) {
+ reader.close();
+ reader = null;
+ }
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ }
+ }
+
+ /**
+ * Process the file
+ */
+ public void activateOptions() {
+ Runnable runnable = new Runnable() {
+ public void run() {
+ try {
+ URL url = new URL(fileURL);
+ host = url.getHost();
+ if (host != null && host.equals("")) {
+ host = FILE_KEY;
+ }
+ path = url.getPath();
+ } catch (MalformedURLException e1) {
+ // TODO Auto-generated catch block
+ e1.printStackTrace();
+ }
+
+ try {
+ if (filterExpression != null) {
+ expressionRule = ExpressionRule.getRule(filterExpression);
+ }
+ } catch (Exception e) {
+ getLogger().warn("Invalid filter expression: " + filterExpression, e);
+ }
+
+ Class c;
+ try {
+ c = Class.forName(decoder);
+ Object o = c.newInstance();
+ if (o instanceof Decoder) {
+ decoderInstance = (Decoder) o;
+ }
+ } catch (ClassNotFoundException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (InstantiationException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ try {
+ reader = new InputStreamReader(new URL(getFileURL()).openStream());
+ process(reader);
+ } catch (FileNotFoundException fnfe) {
+ getLogger().info("file not available");
+ } catch (IOException ioe) {
+ getLogger().warn("unable to load file", ioe);
+ return;
+ }
+ }
+ };
+ if (useCurrentThread) {
+ runnable.run();
+ } else {
+ Thread thread = new Thread(runnable, "LogFileXMLReceiver-" + getName());
+
+ thread.start();
+
+ }
+ }
+
+ private void process(Reader unbufferedReader) throws IOException {
+ BufferedReader bufferedReader = new BufferedReader(unbufferedReader);
+ char[] content = new char[10000];
+ getLogger().debug("processing starting: " + fileURL);
+ int length = 0;
+ do {
+ System.out.println("in do loop-about to process");
+ while ((length = bufferedReader.read(content)) > -1) {
+ processEvents(decoderInstance.decodeEvents(String.valueOf(content, 0, length)));
+ }
+ if (tailing) {
+ try {
+ Thread.sleep(5000);
+ } catch (InterruptedException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ } while (tailing);
+ getLogger().debug("processing complete: " + fileURL);
+
+ shutdown();
+ }
+
+ private void processEvents(Collection c) {
+ if (c == null) {
+ return;
+ }
+
+ for (Iterator iter = c.iterator(); iter.hasNext();) {
+ LoggingEvent evt = (LoggingEvent) iter.next();
+ if (passesExpression(evt)) {
+ if (evt.getProperty(Constants.HOSTNAME_KEY) != null) {
+ evt.setProperty(Constants.HOSTNAME_KEY, host);
+ }
+ if (evt.getProperty(Constants.APPLICATION_KEY) != null) {
+ evt.setProperty(Constants.APPLICATION_KEY, path);
+ }
+ doPost(evt);
+ }
+ }
+ }
+
+ /**
+ * When true, this property uses the current Thread to perform the import, otherwise when false
+ * (the default), a new Thread is created and started to manage the import.
+ *
+ * @return
+ */
+ public final boolean isUseCurrentThread() {
+ return useCurrentThread;
+ }
+
+ /**
+ * Sets whether the current Thread or a new Thread is created to perform the import, the default
+ * being false (new Thread created).
+ *
+ * @param useCurrentThread
+ */
+ public final void setUseCurrentThread(boolean useCurrentThread) {
+ this.useCurrentThread = useCurrentThread;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/log4j/receivers/xml/UtilLoggingEntityResolver.java b/src/main/java/org/apache/log4j/receivers/xml/UtilLoggingEntityResolver.java
new file mode 100644
index 0000000..422535e
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/xml/UtilLoggingEntityResolver.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.receivers.xml;
+
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+
+import java.io.ByteArrayInputStream;
+
+
+/**
+ * An {@link EntityResolver} specifically designed to return
+ * an empty InputSource for logger.dtd.
+ *
+ */
+public final class UtilLoggingEntityResolver implements EntityResolver {
+
+ /**
+ * Create new instance.
+ */
+ public UtilLoggingEntityResolver() {
+ super();
+ }
+
+
+ /** {@inheritDoc} */
+ public InputSource resolveEntity(final String publicId,
+ final String systemId) {
+ if (systemId.endsWith("logger.dtd")) {
+ return new InputSource(new ByteArrayInputStream(new byte[0]));
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/src/main/java/org/apache/log4j/receivers/xml/UtilLoggingXMLDecoder.java b/src/main/java/org/apache/log4j/receivers/xml/UtilLoggingXMLDecoder.java
new file mode 100644
index 0000000..1e9b5cb
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/xml/UtilLoggingXMLDecoder.java
@@ -0,0 +1,460 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.receivers.xml;
+
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.apache.log4j.helpers.UtilLoggingLevel;
+
+import org.apache.log4j.receivers.spi.Decoder;
+import org.apache.log4j.spi.LocationInfo;
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.spi.ThrowableInformation;
+import org.apache.log4j.xml.SAXErrorHandler;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+
+import javax.swing.*;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import java.awt.*;
+import java.io.*;
+import java.net.URL;
+import java.util.*;
+import java.util.zip.ZipInputStream;
+
+
+/**
+ * Decodes JDK 1.4's java.util.logging package events
+ * delivered via XML (using the logger.dtd).
+ *
+ * @author Scott Deboy (sdeboy@apache.org)
+ * @author Paul Smith (psmith@apache.org)
+ *
+ */
+public class UtilLoggingXMLDecoder implements Decoder {
+ //NOTE: xml section is only handed on first delivery of events
+ //on this first delivery of events, there is no end tag for the log element
+ /**
+ * Document prolog.
+ */
+ private static final String BEGIN_PART =
+ "<log>";
+ /**
+ * Document close.
+ */
+ private static final String END_PART = "</log>";
+ /**
+ * Document builder.
+ */
+ private DocumentBuilder docBuilder;
+ /**
+ * Additional properties.
+ */
+ private Map additionalProperties = new HashMap();
+ /**
+ * Partial event.
+ */
+ private String partialEvent;
+ /**
+ * Record end.
+ */
+ private static final String RECORD_END = "</record>";
+ /**
+ * Owner.
+ */
+ private Component owner = null;
+
+ private static final String ENCODING = "UTF-8";
+
+ /**
+ * Create new instance.
+ * @param o owner
+ */
+ public UtilLoggingXMLDecoder(final Component o) {
+ this();
+ this.owner = o;
+ }
+
+ /**
+ * Create new instance.
+ */
+ public UtilLoggingXMLDecoder() {
+ DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+ dbf.setValidating(false);
+
+ try {
+ docBuilder = dbf.newDocumentBuilder();
+ docBuilder.setErrorHandler(new SAXErrorHandler());
+ docBuilder.setEntityResolver(new UtilLoggingEntityResolver());
+ } catch (ParserConfigurationException pce) {
+ System.err.println("Unable to get document builder");
+ }
+ }
+
+ /**
+ * Sets an additionalProperty map, where each Key/Value pair is
+ * automatically added to each LoggingEvent as it is decoded.
+ *
+ * This is useful, say, to include the source file name of the Logging events
+ * @param properties additional properties
+ */
+ public void setAdditionalProperties(final Map properties) {
+ this.additionalProperties = properties;
+ }
+
+ /**
+ * Converts the LoggingEvent data in XML string format into an actual
+ * XML Document class instance.
+ * @param data XML fragment
+ * @return dom document
+ */
+ private Document parse(final String data) {
+ if (docBuilder == null || data == null) {
+ return null;
+ }
+
+ Document document = null;
+
+ try {
+ // we change the system ID to a valid URI so that Crimson won't
+ // complain. Indeed, "log4j.dtd" alone is not a valid URI which
+ // causes Crimson to barf. The Log4jEntityResolver only cares
+ // about the "log4j.dtd" ending.
+
+ /**
+ * resetting the length of the StringBuffer is dangerous, particularly
+ * on some JDK 1.4 impls, there's a known Bug that causes a memory leak
+ */
+ StringBuffer buf = new StringBuffer(1024);
+
+ if (!data.startsWith("<?xml")) {
+ buf.append(BEGIN_PART);
+ }
+
+ buf.append(data);
+
+ if (!data.endsWith(END_PART)) {
+ buf.append(END_PART);
+ }
+
+ InputSource inputSource =
+ new InputSource(new StringReader(buf.toString()));
+ document = docBuilder.parse(inputSource);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ return document;
+ }
+
+ /**
+ * Decodes a File into a Vector of LoggingEvents.
+ * @param url the url of a file containing events to decode
+ * @return Vector of LoggingEvents
+ * @throws IOException if IO error during processing.
+ */
+ public Vector decode(final URL url) throws IOException {
+ LineNumberReader reader;
+ boolean isZipFile = url.getPath().toLowerCase().endsWith(".zip");
+ InputStream inputStream;
+ if (isZipFile) {
+ inputStream = new ZipInputStream(url.openStream());
+ //move stream to next entry so we can read it
+ ((ZipInputStream)inputStream).getNextEntry();
+ } else {
+ inputStream = url.openStream();
+ }
+ if (owner != null) {
+ reader = new LineNumberReader(
+ new InputStreamReader(
+ new ProgressMonitorInputStream(owner,
+ "Loading " + url , inputStream), ENCODING));
+ } else {
+ reader = new LineNumberReader(new InputStreamReader(inputStream, ENCODING));
+ }
+ Vector v = new Vector();
+
+ String line;
+ Vector events;
+ try {
+ while ((line = reader.readLine()) != null) {
+ StringBuffer buffer = new StringBuffer(line);
+ for (int i = 0; i < 1000; i++) {
+ buffer.append(reader.readLine()).append("\n");
+ }
+ events = decodeEvents(buffer.toString());
+ if (events != null) {
+ v.addAll(events);
+ }
+ }
+ } finally {
+ partialEvent = null;
+ try {
+ if (reader != null) {
+ reader.close();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ return v;
+ }
+
+ /**
+ * Decodes a String representing a number of events into a
+ * Vector of LoggingEvents.
+ * @param document to decode events from
+ * @return Vector of LoggingEvents
+ */
+ public Vector decodeEvents(final String document) {
+
+ if (document != null) {
+
+ if (document.trim().equals("")) {
+ return null;
+ }
+
+ String newDoc;
+ String newPartialEvent = null;
+ //separate the string into the last portion ending with </record>
+ // (which will be processed) and the partial event which
+ // will be combined and processed in the next section
+
+ //if the document does not contain a record end,
+ // append it to the partial event string
+ if (document.lastIndexOf(RECORD_END) == -1) {
+ partialEvent = partialEvent + document;
+ return null;
+ }
+
+ if (document.lastIndexOf(RECORD_END) + RECORD_END.length()
+ < document.length()) {
+ newDoc = document.substring(0,
+ document.lastIndexOf(RECORD_END) + RECORD_END.length());
+ newPartialEvent = document.substring(
+ document.lastIndexOf(RECORD_END) + RECORD_END.length());
+ } else {
+ newDoc = document;
+ }
+ if (partialEvent != null) {
+ newDoc = partialEvent + newDoc;
+ }
+ partialEvent = newPartialEvent;
+
+ Document doc = parse(newDoc);
+ if (doc == null) {
+ return null;
+ }
+ return decodeEvents(doc);
+ }
+ return null;
+ }
+
+ /**
+ * Converts the string data into an XML Document, and then soaks out the
+ * relevant bits to form a new LoggingEvent instance which can be used
+ * by any Log4j element locally.
+ * @param data XML fragment
+ * @return a single LoggingEvent or null
+ */
+ public LoggingEvent decode(final String data) {
+ Document document = parse(data);
+
+ if (document == null) {
+ return null;
+ }
+
+ Vector events = decodeEvents(document);
+
+ if (events.size() > 0) {
+ return (LoggingEvent) events.firstElement();
+ }
+
+ return null;
+ }
+
+ /**
+ * Given a Document, converts the XML into a Vector of LoggingEvents.
+ * @param document XML document
+ * @return Vector of LoggingEvents
+ */
+ private Vector decodeEvents(final Document document) {
+ Vector events = new Vector();
+
+ NodeList eventList = document.getElementsByTagName("record");
+
+ for (int eventIndex = 0; eventIndex < eventList.getLength();
+ eventIndex++) {
+ Node eventNode = eventList.item(eventIndex);
+
+ Logger logger = null;
+ long timeStamp = 0L;
+ Level level = null;
+ String threadName = null;
+ Object message = null;
+ String ndc = null;
+ String[] exception = null;
+ String className = null;
+ String methodName = null;
+ String fileName = null;
+ String lineNumber = null;
+ Hashtable properties = new Hashtable();
+
+ //format of date: 2003-05-04T11:04:52
+ //ignore date or set as a property? using millis in constructor instead
+ NodeList list = eventNode.getChildNodes();
+ int listLength = list.getLength();
+
+ if (listLength == 0) {
+ continue;
+ }
+
+ for (int y = 0; y < listLength; y++) {
+ String tagName = list.item(y).getNodeName();
+
+ if (tagName.equalsIgnoreCase("logger")) {
+ logger = Logger.getLogger(getCData(list.item(y)));
+ }
+
+ if (tagName.equalsIgnoreCase("millis")) {
+ timeStamp = Long.parseLong(getCData(list.item(y)));
+ }
+
+ if (tagName.equalsIgnoreCase("level")) {
+ level = UtilLoggingLevel.toLevel(getCData(list.item(y)));
+ }
+
+ if (tagName.equalsIgnoreCase("thread")) {
+ threadName = getCData(list.item(y));
+ }
+
+ if (tagName.equalsIgnoreCase("sequence")) {
+ properties.put("log4jid", getCData(list.item(y)));
+ }
+
+ if (tagName.equalsIgnoreCase("message")) {
+ message = getCData(list.item(y));
+ }
+
+ if (tagName.equalsIgnoreCase("class")) {
+ className = getCData(list.item(y));
+ }
+
+ if (tagName.equalsIgnoreCase("method")) {
+ methodName = getCData(list.item(y));
+ }
+
+ if (tagName.equalsIgnoreCase("exception")) {
+ ArrayList exceptionList = new ArrayList();
+ NodeList exList = list.item(y).getChildNodes();
+ int exlistLength = exList.getLength();
+
+ for (int i2 = 0; i2 < exlistLength; i2++) {
+ Node exNode = exList.item(i2);
+ String exName = exList.item(i2).getNodeName();
+
+ if (exName.equalsIgnoreCase("message")) {
+ exceptionList.add(getCData(exList.item(i2)));
+ }
+
+ if (exName.equalsIgnoreCase("frame")) {
+ NodeList exList2 = exNode.getChildNodes();
+ int exlist2Length = exList2.getLength();
+
+ for (int i3 = 0; i3 < exlist2Length; i3++) {
+ exceptionList.add(getCData(exList2.item(i3)) + "\n");
+ }
+ }
+ }
+ if (exceptionList.size() > 0) {
+ exception =
+ (String[]) exceptionList.toArray(new String[exceptionList.size()]);
+ }
+ }
+ }
+
+ /**
+ * We add all the additional properties to the properties
+ * hashtable. Override properties that already exist
+ */
+ if (additionalProperties.size() > 0) {
+ if (properties == null) {
+ properties = new Hashtable(additionalProperties);
+ }
+ Iterator i = additionalProperties.entrySet().iterator();
+ while (i.hasNext()) {
+ Map.Entry e = (Map.Entry) i.next();
+ properties.put(e.getKey(), e.getValue());
+ }
+ }
+
+ LocationInfo info;
+ if ((fileName != null)
+ || (className != null)
+ || (methodName != null)
+ || (lineNumber != null)) {
+ info = new LocationInfo(fileName, className, methodName, lineNumber);
+ } else {
+ info = LocationInfo.NA_LOCATION_INFO;
+ }
+
+ ThrowableInformation throwableInfo = null;
+ if (exception != null) {
+ throwableInfo = new ThrowableInformation(exception);
+ }
+
+ LoggingEvent loggingEvent = new LoggingEvent(null,
+ logger, timeStamp, level, message,
+ threadName,
+ throwableInfo,
+ ndc,
+ info,
+ properties);
+
+ events.add(loggingEvent);
+
+ }
+ return events;
+ }
+
+ /**
+ * Get contents of CDATASection.
+ * @param n CDATASection
+ * @return text content of all text or CDATA children of node.
+ */
+ private String getCData(final Node n) {
+ StringBuffer buf = new StringBuffer();
+ NodeList nl = n.getChildNodes();
+
+ for (int x = 0; x < nl.getLength(); x++) {
+ Node innerNode = nl.item(x);
+
+ if (
+ (innerNode.getNodeType() == Node.TEXT_NODE)
+ || (innerNode.getNodeType() == Node.CDATA_SECTION_NODE)) {
+ buf.append(innerNode.getNodeValue());
+ }
+ }
+
+ return buf.toString();
+ }
+}
diff --git a/src/main/java/org/apache/log4j/receivers/xml/XMLDecoder.java b/src/main/java/org/apache/log4j/receivers/xml/XMLDecoder.java
new file mode 100644
index 0000000..124383d
--- /dev/null
+++ b/src/main/java/org/apache/log4j/receivers/xml/XMLDecoder.java
@@ -0,0 +1,488 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.receivers.xml;
+
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.apache.log4j.receivers.spi.Decoder;
+import org.apache.log4j.spi.LocationInfo;
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.spi.ThrowableInformation;
+import org.apache.log4j.xml.Log4jEntityResolver;
+import org.apache.log4j.xml.SAXErrorHandler;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+
+import javax.swing.*;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import java.awt.*;
+import java.io.*;
+import java.net.URL;
+import java.util.*;
+import java.util.zip.ZipInputStream;
+
+
+/**
+ * Decodes Logging Events in XML formated into elements that are used by
+ * Chainsaw.
+ *
+ * This decoder can process a collection of log4j:event nodes ONLY
+ * (no XML declaration nor eventSet node)
+ *
+ * NOTE: Only a single LoggingEvent is returned from the decode method
+ * even though the DTD supports multiple events nested in an eventSet.
+ *
+ * NOTE: This class has been created on the assumption that all XML log files
+ * are encoded in UTF-8. There is no current support for any other
+ * encoding format at this time.
+ *
+ * @author Scott Deboy (sdeboy@apache.org)
+ * @author Paul Smith (psmith@apache.org)
+ *
+ */
+public class XMLDecoder implements Decoder {
+
+ private static final String ENCODING = "UTF-8";
+
+ /**
+ * Document prolog.
+ */
+ private static final String BEGINPART =
+ "<?xml version=\"1.0\" encoding=\"" + ENCODING + "\" ?>"
+ + "<!DOCTYPE log4j:eventSet SYSTEM \"http://localhost/log4j.dtd\">"
+ + "<log4j:eventSet version=\"1.2\" "
+ + "xmlns:log4j=\"http://jakarta.apache.org/log4j/\">";
+ /**
+ * Document close.
+ */
+ private static final String ENDPART = "</log4j:eventSet>";
+ /**
+ * Record end.
+ */
+ private static final String RECORD_END = "</log4j:event>";
+
+ /**
+ * Document builder.
+ */
+ private DocumentBuilder docBuilder;
+ /**
+ * Additional properties.
+ */
+ private Map additionalProperties = new HashMap();
+ /**
+ * Partial event.
+ */
+ private String partialEvent;
+ /**
+ * Owner.
+ */
+ private Component owner = null;
+
+ /**
+ * Create new instance.
+ * @param o owner
+ */
+ public XMLDecoder(final Component o) {
+ this();
+ this.owner = o;
+ }
+
+ /**
+ * Create new instance.
+ */
+ public XMLDecoder() {
+ DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+ dbf.setValidating(false);
+
+ try {
+ docBuilder = dbf.newDocumentBuilder();
+ docBuilder.setErrorHandler(new SAXErrorHandler());
+ docBuilder.setEntityResolver(new Log4jEntityResolver());
+ } catch (ParserConfigurationException pce) {
+ System.err.println("Unable to get document builder");
+ }
+ }
+
+ /**
+ * Sets an additionalProperty map, where each Key/Value pair is
+ * automatically added to each LoggingEvent as it is decoded.
+ *
+ * This is useful, say, to include the source file name of the Logging events
+ * @param properties additional properties
+ */
+ public void setAdditionalProperties(final Map properties) {
+ this.additionalProperties = properties;
+ }
+
+ /**
+ * Converts the LoggingEvent data in XML string format into an actual
+ * XML Document class instance.
+ * @param data XML fragment
+ * @return dom document
+ */
+ private Document parse(final String data) {
+ if (docBuilder == null || data == null) {
+ return null;
+ }
+ Document document = null;
+
+ try {
+ // we change the system ID to a valid URI so that Crimson won't
+ // complain. Indeed, "log4j.dtd" alone is not a valid URI which
+ // causes Crimson to barf. The Log4jEntityResolver only cares
+ // about the "log4j.dtd" ending.
+
+ /**
+ * resetting the length of the StringBuffer is dangerous, particularly
+ * on some JDK 1.4 impls, there's a known Bug that causes a memory leak
+ */
+ StringBuffer buf = new StringBuffer(1024);
+
+ buf.append(BEGINPART);
+ buf.append(data);
+ buf.append(ENDPART);
+
+ InputSource inputSource =
+ new InputSource(new StringReader(buf.toString()));
+ document = docBuilder.parse(inputSource);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ return document;
+ }
+
+ /**
+ * Decodes a File into a Vector of LoggingEvents.
+ * @param url the url of a file containing events to decode
+ * @return Vector of LoggingEvents
+ * @throws IOException if IO error during processing.
+ */
+ public Vector decode(final URL url) throws IOException {
+ LineNumberReader reader;
+ boolean isZipFile = url.getPath().toLowerCase().endsWith(".zip");
+ InputStream inputStream;
+ if (isZipFile) {
+ inputStream = new ZipInputStream(url.openStream());
+ //move stream to next entry so we can read it
+ ((ZipInputStream)inputStream).getNextEntry();
+ } else {
+ inputStream = url.openStream();
+ }
+ if (owner != null) {
+ reader = new LineNumberReader(
+ new InputStreamReader(
+ new ProgressMonitorInputStream(owner,
+ "Loading " + url , inputStream), ENCODING));
+ } else {
+ reader = new LineNumberReader(new InputStreamReader(inputStream, ENCODING));
+ }
+
+ Vector v = new Vector();
+
+ String line;
+ Vector events;
+ try {
+ while ((line = reader.readLine()) != null) {
+ StringBuffer buffer = new StringBuffer(line);
+ for (int i = 0; i < 1000; i++) {
+ buffer.append(reader.readLine()).append("\n");
+ }
+ events = decodeEvents(buffer.toString());
+ if (events != null) {
+ v.addAll(events);
+ }
+ }
+ } finally {
+ partialEvent = null;
+ try {
+ if (reader != null) {
+ reader.close();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ return v;
+ }
+
+ /**
+ * Decodes a String representing a number of events into a
+ * Vector of LoggingEvents.
+ * @param document to decode events from
+ * @return Vector of LoggingEvents
+ */
+ public Vector decodeEvents(final String document) {
+ if (document != null) {
+ if (document.trim().equals("")) {
+ return null;
+ }
+ String newDoc = null;
+ String newPartialEvent = null;
+ //separate the string into the last portion ending with
+ // </log4j:event> (which will be processed) and the
+ // partial event which will be combined and
+ // processed in the next section
+
+ //if the document does not contain a record end,
+ // append it to the partial event string
+ if (document.lastIndexOf(RECORD_END) == -1) {
+ partialEvent = partialEvent + document;
+ return null;
+ }
+
+ if (document.lastIndexOf(RECORD_END)
+ + RECORD_END.length() < document.length()) {
+ newDoc = document.substring(0,
+ document.lastIndexOf(RECORD_END) + RECORD_END.length());
+ newPartialEvent = document.substring(
+ document.lastIndexOf(RECORD_END) + RECORD_END.length());
+ } else {
+ newDoc = document;
+ }
+ if (partialEvent != null) {
+ newDoc = partialEvent + newDoc;
+ }
+ partialEvent = newPartialEvent;
+ Document doc = parse(newDoc);
+ if (doc == null) {
+ return null;
+ }
+ return decodeEvents(doc);
+ }
+ return null;
+ }
+
+ /**
+ * Converts the string data into an XML Document, and then soaks out the
+ * relevant bits to form a new LoggingEvent instance which can be used
+ * by any Log4j element locally.
+ * @param data XML fragment
+ * @return a single LoggingEvent or null
+ */
+ public LoggingEvent decode(final String data) {
+ Document document = parse(data);
+
+ if (document == null) {
+ return null;
+ }
+
+ Vector events = decodeEvents(document);
+
+ if (events.size() > 0) {
+ return (LoggingEvent) events.firstElement();
+ }
+
+ return null;
+ }
+
+ /**
+ * Given a Document, converts the XML into a Vector of LoggingEvents.
+ * @param document XML document
+ * @return Vector of LoggingEvents
+ */
+ private Vector decodeEvents(final Document document) {
+ Vector events = new Vector();
+
+ Logger logger;
+ long timeStamp;
+ Level level;
+ String threadName;
+ Object message = null;
+ String ndc = null;
+ String[] exception = null;
+ String className = null;
+ String methodName = null;
+ String fileName = null;
+ String lineNumber = null;
+ Hashtable properties = null;
+
+ NodeList nl = document.getElementsByTagName("log4j:eventSet");
+ Node eventSet = nl.item(0);
+
+ NodeList eventList = eventSet.getChildNodes();
+
+ for (int eventIndex = 0; eventIndex < eventList.getLength();
+ eventIndex++) {
+ Node eventNode = eventList.item(eventIndex);
+ //ignore carriage returns in xml
+ if (eventNode.getNodeType() != Node.ELEMENT_NODE) {
+ continue;
+ }
+ logger = Logger.getLogger(eventNode.getAttributes().getNamedItem("logger").getNodeValue());
+ timeStamp = Long.parseLong(eventNode.getAttributes().getNamedItem("timestamp").getNodeValue());
+ level = Level.toLevel(eventNode.getAttributes().getNamedItem("level").getNodeValue());
+ threadName = eventNode.getAttributes().getNamedItem("thread").getNodeValue();
+
+ NodeList list = eventNode.getChildNodes();
+ int listLength = list.getLength();
+
+ if (listLength == 0) {
+ continue;
+ }
+
+ for (int y = 0; y < listLength; y++) {
+ String tagName = list.item(y).getNodeName();
+
+ if (tagName.equalsIgnoreCase("log4j:message")) {
+ message = getCData(list.item(y));
+ }
+
+ if (tagName.equalsIgnoreCase("log4j:NDC")) {
+ ndc = getCData(list.item(y));
+ }
+ //still support receiving of MDC and convert to properties
+ if (tagName.equalsIgnoreCase("log4j:MDC")) {
+ properties = new Hashtable();
+ NodeList propertyList = list.item(y).getChildNodes();
+ int propertyLength = propertyList.getLength();
+
+ for (int i = 0; i < propertyLength; i++) {
+ String propertyTag = propertyList.item(i).getNodeName();
+
+ if (propertyTag.equalsIgnoreCase("log4j:data")) {
+ Node property = propertyList.item(i);
+ String name =
+ property.getAttributes().getNamedItem("name").getNodeValue();
+ String value =
+ property.getAttributes().getNamedItem("value").getNodeValue();
+ properties.put(name, value);
+ }
+ }
+ }
+
+ if (tagName.equalsIgnoreCase("log4j:throwable")) {
+ String exceptionString = getCData(list.item(y));
+ if (exceptionString != null && !exceptionString.trim().equals("")) {
+ exception = new String[] {exceptionString.trim()
+ };
+ }
+ }
+
+ if (tagName.equalsIgnoreCase("log4j:locationinfo")) {
+ className =
+ list.item(y).getAttributes().getNamedItem("class").getNodeValue();
+ methodName =
+ list.item(y).getAttributes().getNamedItem("method").getNodeValue();
+ fileName =
+ list.item(y).getAttributes().getNamedItem("file").getNodeValue();
+ lineNumber =
+ list.item(y).getAttributes().getNamedItem("line").getNodeValue();
+ }
+
+ if (tagName.equalsIgnoreCase("log4j:properties")) {
+ if (properties == null) {
+ properties = new Hashtable();
+ }
+ NodeList propertyList = list.item(y).getChildNodes();
+ int propertyLength = propertyList.getLength();
+
+ for (int i = 0; i < propertyLength; i++) {
+ String propertyTag = propertyList.item(i).getNodeName();
+
+ if (propertyTag.equalsIgnoreCase("log4j:data")) {
+ Node property = propertyList.item(i);
+ String name =
+ property.getAttributes().getNamedItem("name").getNodeValue();
+ String value =
+ property.getAttributes().getNamedItem("value").getNodeValue();
+ properties.put(name, value);
+ }
+ }
+ }
+
+ /**
+ * We add all the additional properties to the properties
+ * hashtable. Override properties that already exist
+ */
+ if (additionalProperties.size() > 0) {
+ if (properties == null) {
+ properties = new Hashtable(additionalProperties);
+ }
+ Iterator i = additionalProperties.entrySet().iterator();
+ while (i.hasNext()) {
+ Map.Entry e = (Map.Entry) i.next();
+ properties.put(e.getKey(), e.getValue());
+ }
+ }
+ }
+
+ LocationInfo info;
+ if ((fileName != null)
+ || (className != null)
+ || (methodName != null)
+ || (lineNumber != null)) {
+ info = new LocationInfo(fileName, className, methodName, lineNumber);
+ } else {
+ info = LocationInfo.NA_LOCATION_INFO;
+ }
+ ThrowableInformation throwableInfo = null;
+ if (exception != null) {
+ throwableInfo = new ThrowableInformation(exception);
+ }
+
+ LoggingEvent loggingEvent = new LoggingEvent(null,
+ logger, timeStamp, level, message,
+ threadName,
+ throwableInfo,
+ ndc,
+ info,
+ properties);
+
+
+ events.add(loggingEvent);
+
+ message = null;
+ ndc = null;
+ exception = null;
+ className = null;
+ methodName = null;
+ fileName = null;
+ lineNumber = null;
+ properties = null;
+ }
+
+ return events;
+ }
+
+ /**
+ * Get contents of CDATASection.
+ * @param n CDATASection
+ * @return text content of all text or CDATA children of node.
+ */
+ private String getCData(final Node n) {
+ StringBuffer buf = new StringBuffer();
+ NodeList nl = n.getChildNodes();
+
+ for (int x = 0; x < nl.getLength(); x++) {
+ Node innerNode = nl.item(x);
+
+ if (
+ (innerNode.getNodeType() == Node.TEXT_NODE)
+ || (innerNode.getNodeType() == Node.CDATA_SECTION_NODE)) {
+ buf.append(innerNode.getNodeValue());
+ }
+ }
+
+ return buf.toString();
+ }
+}
diff --git a/src/test/java/org/apache/log4j/component/plugins/MockReceiver.java b/src/test/java/org/apache/log4j/component/plugins/MockReceiver.java
new file mode 100644
index 0000000..06a0462
--- /dev/null
+++ b/src/test/java/org/apache/log4j/component/plugins/MockReceiver.java
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.log4j.component.plugins;
+
+/**
+ * Mock receiver used by PluginConfiguratorTest.
+ */
+public final class MockReceiver extends PluginSkeleton {
+ /**
+ * Is active.
+ */
+ private boolean active = false;
+ /**
+ * Host name.
+ */
+ private String host;
+ /**
+ * Port.
+ */
+ private int port = 0;
+
+ /**
+ * Create new instance.
+ */
+ public MockReceiver() {
+ super();
+ }
+
+ /**
+ * Shutdown.
+ */
+ public void shutdown() {
+ active = false;
+ }
+
+ /**
+ * Is plugin active.
+ * @return true if active.
+ */
+ public boolean isActive() {
+ return active;
+ }
+
+ /**
+ * Activate options.
+ */
+ public void activateOptions() {
+ active = true;
+ }
+
+ /**
+ Get the remote host to connect to for logging events.
+ @return host
+ */
+ public String getHost() {
+ return host;
+ }
+
+ /**
+ * Configures the Host property, this will require activateOptions
+ * to be called for this to take effect.
+ * @param remoteHost address of remote host.
+ */
+ public void setHost(final String remoteHost) {
+ this.host = remoteHost;
+ }
+ /**
+ Set the remote host to connect to for logging events.
+ Equivalent to setHost.
+ @param remoteHost address of remote host.
+ */
+ public void setPort(final String remoteHost) {
+ host = remoteHost;
+ }
+
+ /**
+ Get the remote port to connect to for logging events.
+ @return port
+ */
+ public int getPort() {
+ return port;
+ }
+
+ /**
+ Set the remote port to connect to for logging events.
+ @param p port
+ */
+ public void setPort(final int p) {
+ this.port = p;
+ }
+
+}
diff --git a/src/test/java/org/apache/log4j/component/plugins/PluginTestCase.java b/src/test/java/org/apache/log4j/component/plugins/PluginTestCase.java
new file mode 100644
index 0000000..e706358
--- /dev/null
+++ b/src/test/java/org/apache/log4j/component/plugins/PluginTestCase.java
@@ -0,0 +1,619 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.component.plugins;
+
+import junit.framework.TestCase;
+import org.apache.log4j.*;
+import org.apache.log4j.LoggerRepositoryExImpl;
+import org.apache.log4j.util.Compare;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+
+
+public class PluginTestCase extends TestCase {
+
+ static String FILE = "plugins.PluginTestCase";
+ static String WITNESS = "witness/plugins.PluginTestCase";
+ private static boolean verbosePluginOutput = true;
+ private static HashMap repositoryMap = new HashMap();
+
+ PluginRegistry pluginRegistry;
+ public PluginTestCase(String name) {
+ super(name);
+ }
+
+ public void setUp() {
+ pluginRegistry = new LoggerRepositoryExImpl(
+ LogManager.getLoggerRepository()).getPluginRegistry();
+
+ // delete the output file if they happen to exist
+ new File(getOutputFile("test1")).delete();
+ }
+
+ private String getOutputFile(String caseName) {
+
+ return FILE + "." + caseName + ".txt";
+ }
+
+ private String getWitnessFile(String caseName) {
+
+ return WITNESS + "." + caseName + ".txt";
+ }
+
+ private void setupAppender(String caseName) throws IOException {
+
+ Logger root = Logger.getRootLogger();
+ root.removeAllAppenders();
+
+ // set up appender
+ FileAppender appender = new FileAppender(new SimpleLayout(),
+ getOutputFile(caseName), false);
+
+ //FileAppender appender = new FileAppender(new PatternLayout("%c{1}: %m%n"),
+ // getOutputFile(caseName), false);
+ root.addAppender(appender);
+ root.setLevel(Level.DEBUG);
+ }
+
+ // basic test of plugin in standalone mode
+ public void test1() throws Exception {
+
+ String testName = "test1";
+ Logger logger = Logger.getLogger(testName);
+
+ setupAppender(testName);
+
+ PluginTester plugin1 = new PluginTester1("plugin1", 1);
+ PluginTester plugin2 = new PluginTester1("plugin1", 2);
+ PluginTester plugin3 = new PluginTester2("plugin1", 3);
+ PluginTester plugin4 = new PluginTester2("plugin2", 4);
+ PluginTester retPlugin;
+
+ repositoryMap.clear();
+ repositoryMap.put(LogManager.getLoggerRepository(),
+ "default repository");
+
+ // test basic starting/stopping
+ logger.info("test 1.1 - basic starting/stopping");
+ logger.info("starting " + plugin1.getIdentifier());
+ pluginRegistry.addPlugin(plugin1);
+ logger.info("stopping " + plugin1.getIdentifier() +
+ " using plugin object");
+ pluginRegistry.stopPlugin(plugin1.getName());
+
+ // test restarting and starting when already started
+ logger.info("test 1.2 - restarting and starting when already started");
+ logger.info("restarting " + plugin1.getIdentifier());
+ pluginRegistry.addPlugin(plugin1);
+ logger.info("restarting " + plugin1.getIdentifier() + " again");
+ pluginRegistry.addPlugin(plugin1);
+
+ // test stopping and stopping when already stopped
+ logger.info("test 1.3- stopping and stopping when already stopped");
+ logger.info("stopping " + plugin1.getIdentifier());
+ pluginRegistry.stopPlugin(plugin1.getName());
+ logger.info("stopping " + plugin1.getIdentifier() + " again");
+ pluginRegistry.stopPlugin(plugin1.getName());
+
+ logger.info("test 1.4 - restarting then stopping by plugin name");
+ logger.info("starting " + plugin1.getIdentifier());
+ pluginRegistry.addPlugin(plugin1);
+ logger.info("stopping " + plugin1.getIdentifier() +
+ " using plugin name");
+ pluginRegistry.stopPlugin(plugin1.getName());
+
+// // test starting of an "equal" plugin
+// logger.info("test 1.5 - starting of an \"equal\" plugin");
+// logger.info("starting " + plugin1.getIdentifier());
+// retPlugin = (PluginTester) pluginRegistry.startPlugin(plugin1);
+// logger.info("returned plugin is " + retPlugin.getIdentifier());
+// logger.info("starting " + plugin2.getIdentifier());
+// retPlugin = (PluginTester) pluginRegistry.startPlugin(plugin2);
+// logger.info("returned plugin is " + retPlugin.getIdentifier());
+// logger.info("stopping " + retPlugin.getIdentifier());
+// pluginRegistry.stopPlugin(retPlugin.getName());
+//
+// // test starting an "equal" plugin after original stopped
+// logger.info(
+// "test 1.6 - starting an \"equal\" plugin after original stopped");
+// logger.info("starting " + plugin2.getIdentifier());
+// retPlugin = (PluginTester) pluginRegistry.startPlugin(plugin2);
+// logger.info("returned plugin is " + retPlugin.getIdentifier());
+// logger.info("stopping " + retPlugin.getIdentifier());
+// pluginRegistry.stopPlugin(retPlugin.getName());
+//
+// // test starting of an "unequal" plugin with same name
+// logger.info(
+// "test 1.7 - starting of an \"unequal\" plugin with same name");
+// logger.info("starting " + plugin1.getIdentifier());
+// retPlugin = (PluginTester) pluginRegistry.startPlugin(plugin1);
+// logger.info("returned plugin is " + retPlugin.getIdentifier());
+// logger.info("starting " + plugin3.getIdentifier());
+// retPlugin = (PluginTester) pluginRegistry.startPlugin(plugin3);
+// logger.info("returned plugin is " + retPlugin.getIdentifier());
+// logger.info("stopping " + retPlugin.getIdentifier());
+// pluginRegistry.stopPlugin(retPlugin.getName());
+//
+// // test starting of multiple plugins and stopAll
+// logger.info("test 1.8 - starting of multiple plugins and stopAll");
+// logger.info("starting " + plugin1.getIdentifier());
+// retPlugin = (PluginTester) pluginRegistry.startPlugin(plugin1);
+// logger.info("returned plugin is " + retPlugin.getIdentifier());
+// logger.info("starting " + plugin4.getIdentifier());
+// retPlugin = (PluginTester) pluginRegistry.startPlugin(plugin4);
+// logger.info("returned plugin is " + retPlugin.getIdentifier());
+// verbosePluginOutput = false;
+// logger.info("stopping all plugins");
+// pluginRegistry.stopAllPlugins();
+// verbosePluginOutput = true;
+// logger.info(plugin1.getIdentifier() + " is " +
+// (plugin1.isActive() ? "active" : "inactive"));
+// logger.info(plugin4.getIdentifier() + " is " +
+// (plugin4.isActive() ? "active" : "inactive"));
+// logger.info("stopping all plugins again");
+// pluginRegistry.stopAllPlugins();
+//
+// // test starting of multiple plugins and stopAll
+// logger.info(
+// "test 1.9 - starting of multiple plugins, stopping, and stopAll");
+// logger.info("starting " + plugin1.getIdentifier());
+// retPlugin = (PluginTester) pluginRegistry.startPlugin(plugin1);
+// logger.info("returned plugin is " + retPlugin.getIdentifier());
+// logger.info("starting " + plugin4.getIdentifier());
+// retPlugin = (PluginTester) pluginRegistry.startPlugin(plugin4);
+// logger.info("returned plugin is " + retPlugin.getIdentifier());
+ logger.info("stopping " + plugin1.getIdentifier() +
+ " using plugin object");
+ pluginRegistry.stopPlugin(plugin1.getName());
+ verbosePluginOutput = false;
+ logger.info("stopping all plugins");
+ pluginRegistry.stopAllPlugins();
+ verbosePluginOutput = true;
+ logger.info(plugin1.getIdentifier() + " is " +
+ (plugin1.isActive() ? "active" : "inactive"));
+ logger.info(plugin4.getIdentifier() + " is " +
+ (plugin4.isActive() ? "active" : "inactive"));
+ logger.info("stopping all plugins again");
+ //
+ // Warning about removing non-registered LoggerRepositoryEventListener
+ // goes to console on log4j 1.2 instead of log file with log4j 1.3.
+ //
+ pluginRegistry.stopAllPlugins();
+
+ assertTrue(Compare.compare(PluginTestCase.class,
+ getOutputFile(testName),
+ getWitnessFile(testName)));
+ }
+
+ // basic test of plugin with repositories
+ public void test2() throws Exception {
+//
+// String testName = "test2";
+// Logger logger = Logger.getLogger(testName);
+//
+// setupAppender(testName);
+//
+// PluginTester plugin1 = new PluginTester1("plugin1", 1);
+// PluginTester plugin2 = new PluginTester1("plugin2", 2);
+// PluginTester retPlugin;
+// LoggerRepository repo1 = new Hierarchy(new RootLogger(Level.DEBUG));
+// LoggerRepository repo2 = new Hierarchy(new RootLogger(Level.DEBUG));
+//
+// PluginRegistry pr1 = repo1.getPluginRegistry();
+// PluginRegistry pr2 = repo2.getPluginRegistry();
+//
+// repositoryMap.clear();
+// repositoryMap.put(repo1, "repository1");
+// repositoryMap.put(repo2, "repository2");
+//
+// logger.info("test 2.1 - starting plugins in multiple repositories");
+// logger.info("starting " + plugin1.getIdentifier() + " in " +
+// repositoryMap.get(repo1));
+// retPlugin = (PluginTester) pr1.startPlugin(plugin1);
+// logger.info(
+// "returned plugin is " + retPlugin.getIdentifier() + " in " +
+// repositoryMap.get(retPlugin.getLoggerRepository()));
+// logger.info("starting " + plugin2.getIdentifier() + " in " +
+// repositoryMap.get(repo2));
+// retPlugin = (PluginTester) pr2.startPlugin(plugin2);
+// logger.info(
+// "returned plugin is " + retPlugin.getIdentifier() + " in " +
+// repositoryMap.get(retPlugin.getLoggerRepository()));
+//
+// logger.info("test 2.2 - stopping plugins in multiple repositories");
+// logger.info("stopping " + plugin1.getIdentifier() + " in " +
+// repositoryMap.get(plugin1.getLoggerRepository()));
+// retPlugin = (PluginTester) pr1.stopPlugin(plugin1.getName());
+// logger.info(
+// "returned plugin is " + retPlugin.getIdentifier() + " in " +
+// repositoryMap.get(retPlugin.getLoggerRepository()));
+// logger.info("stopping " + plugin2.getIdentifier() + " in " +
+// repositoryMap.get(plugin2.getLoggerRepository()));
+// retPlugin = (PluginTester) pr2.stopPlugin(plugin2.getName());
+// logger.info(
+// "returned plugin is " + retPlugin.getIdentifier() + " in " +
+// repositoryMap.get(retPlugin.getLoggerRepository()));
+//
+// logger.info("test 2.3 - restarting plugins in different repositories");
+// logger.info("starting " + plugin1.getIdentifier() + " in " +
+// repositoryMap.get(repo2));
+// retPlugin = (PluginTester) pr2.startPlugin(plugin1);
+// logger.info(
+// "returned plugin is " + retPlugin.getIdentifier() + " in " +
+// repositoryMap.get(retPlugin.getLoggerRepository()));
+// logger.info("starting " + plugin2.getIdentifier() + " in " +
+// repositoryMap.get(repo1));
+// retPlugin = (PluginTester) pr1.startPlugin(plugin2);
+// logger.info(
+// "returned plugin is " + retPlugin.getIdentifier() + " in " +
+// repositoryMap.get(retPlugin.getLoggerRepository()));
+//
+// logger.info("test 2.4 - stopping plugins using stopAll");
+// logger.info("stopping all plugins in " + repositoryMap.get(repo1));
+// pr1.stopAllPlugins();
+// logger.info("stopping all plugins in " + repositoryMap.get(repo2));
+// pr2.stopAllPlugins();
+//
+// logger.info(
+// "test 2.5 - starting a plugin already active in another repository");
+// logger.info("starting " + plugin1.getIdentifier() + " in " +
+// repositoryMap.get(repo1));
+// retPlugin = (PluginTester) pr1.startPlugin(plugin1);
+// logger.info(
+// "returned plugin is " + retPlugin.getIdentifier() + " in " +
+// repositoryMap.get(retPlugin.getLoggerRepository()));
+// logger.info("starting " + plugin2.getIdentifier() + " in " +
+// repositoryMap.get(repo2));
+// retPlugin = (PluginTester) pr2.startPlugin(plugin2);
+// logger.info(
+// "returned plugin is " + retPlugin.getIdentifier() + " in " +
+// repositoryMap.get(retPlugin.getLoggerRepository()));
+// logger.info("restarting " + plugin1.getIdentifier() + " in " +
+// repositoryMap.get(repo2));
+// retPlugin = (PluginTester) pr2.startPlugin(plugin1);
+// logger.info(
+// "returned plugin is " + retPlugin.getIdentifier() + " in " +
+// repositoryMap.get(retPlugin.getLoggerRepository()));
+// logger.info("restarting " + plugin2.getIdentifier() + " in " +
+// repositoryMap.get(repo1));
+// retPlugin = (PluginTester) pr1.startPlugin(plugin2);
+// logger.info(
+// "returned plugin is " + retPlugin.getIdentifier() + " in " +
+// repositoryMap.get(retPlugin.getLoggerRepository()));
+//
+// logger.info("test 2.6 - handle repository reset");
+// logger.info("resetting " + repositoryMap.get(repo1));
+// repo1.resetConfiguration();
+// logger.info("resetting " + repositoryMap.get(repo2));
+// repo2.resetConfiguration();
+//
+// logger.info("test 2.7 - handle repository shutdown");
+// logger.info("starting " + plugin1.getIdentifier() + " in " +
+// repositoryMap.get(repo1));
+// retPlugin = (PluginTester) pr1.startPlugin(plugin1);
+// logger.info(
+// "returned plugin is " + retPlugin.getIdentifier() + " in " +
+// repositoryMap.get(retPlugin.getLoggerRepository()));
+// logger.info("starting " + plugin2.getIdentifier() + " in " +
+// repositoryMap.get(repo2));
+// retPlugin = (PluginTester) pr2.startPlugin(plugin2);
+// logger.info(
+// "returned plugin is " + retPlugin.getIdentifier() + " in " +
+// repositoryMap.get(retPlugin.getLoggerRepository()));
+// logger.info("shutting down " + repositoryMap.get(repo1));
+// repo1.shutdown();
+// logger.info("shutting down " + repositoryMap.get(repo2));
+// repo2.shutdown();
+//
+// assertTrue(Compare.compare(getOutputFile(testName),
+// getWitnessFile(testName)));
+ }
+
+ public void testPluginListeners() {
+
+ Plugin p = new PluginTester1("MyNewPlugin", 1);
+ PluginListenerLatch l = new PluginListenerLatch();
+ pluginRegistry.stopAllPlugins();
+ pluginRegistry.addPluginListener(l);
+ pluginRegistry.addPlugin(p);
+
+ PluginEvent e = l.LastEvent;
+
+ assertTrue("PluginListener should have been notified of start",
+ l.StartLatch);
+ assertTrue("PluginListener stop latch should not be activated",
+ !l.StopLatch);
+ assertTrue("PluginListener should be given reference to Plugin",
+ e.getPlugin() == p);
+
+ l.reset();
+ pluginRegistry.stopAllPlugins();
+ assertTrue("PluginListener should have been notified of stop",
+ l.StopLatch);
+ assertTrue("PluginListener should not have been notified of start",
+ !l.StartLatch);
+ assertTrue("PluginListener should be given reference to Plugin",
+ l.LastEvent.getPlugin() == p);
+ assertTrue(
+ "PluginListener should have received a distinct event object",
+ l.LastEvent != e);
+ }
+
+ public void testPropertyChangeListeners() {
+
+ Plugin plugin = new PluginTester1("PluginTest1", 1);
+
+ final PropertyChangeListenerLatch l = new PropertyChangeListenerLatch();
+ plugin.addPropertyChangeListener(l);
+
+ /**
+ * Test the basic properties and ensure they get latched by notification
+ */
+ plugin.setName("NewName");
+ assertTrue("PropertyChange latch should have been detected",
+ l.isLatched());
+ assertTrue("Old value unexpected: '" + l.getLastEvent().getOldValue() +
+ "'", l.getLastEvent().getOldValue().equals("PluginTest1"));
+ assertTrue("New value unexpected: '" + l.getLastEvent().getNewValue() +
+ "'", l.getLastEvent().getNewValue().equals("NewName"));
+
+ l.reset();
+
+ plugin.removePropertyChangeListener(l);
+ plugin.setName("SecondNewName");
+
+ assertTrue("Should not have been notified/latched", !l.isLatched());
+
+ l.reset();
+
+ /**
+ * Test when only listening for specific property
+ */
+ plugin.addPropertyChangeListener("name", l);
+
+ plugin.setName("NewName2");
+ assertTrue("PropertyChange latch should have been detected",
+ l.isLatched());
+ assertTrue("Old value unexpected: '" + l.getLastEvent().getOldValue() +
+ "'", l.getLastEvent().getOldValue().equals("SecondNewName"));
+ assertTrue("New value unexpected: '" + l.getLastEvent().getNewValue() +
+ "'", l.getLastEvent().getNewValue().equals("NewName2"));
+
+ plugin.removePropertyChangeListener("name", l);
+
+ l.reset();
+
+ /**
+ * setup some assertions before continuing testing to make sure the test code isn't broken
+ */
+ assertTrue("Plugin should not be active just yet", !plugin.isActive());
+ assertTrue("Latch should not be latched", !l.isLatched());
+
+ plugin.addPropertyChangeListener("active", l);
+
+ pluginRegistry.addPlugin(plugin);
+/*
+ assertTrue(
+ "Should have been notified of activation when pluginRegistry.start(plugin)",
+ l.isLatched());
+ assertTrue("Active old value should have been false",
+ l.getLastEvent().getOldValue().equals(Boolean.FALSE));
+ assertTrue("Active New value should have been true",
+ l.getLastEvent().getNewValue().equals(Boolean.TRUE));
+
+ pluginRegistry.stopAllPlugins();
+ l.reset();
+ assertTrue("Latch should have been reset", !l.isLatched());
+*/
+
+ /**
+ * start afresh
+ */
+/*
+ plugin = new PluginTester1("LoggerRepositoryProperty", 2);
+
+ LoggerRepository oldValue = plugin.getLoggerRepository();
+ plugin.addPropertyChangeListener("loggerRepository", l);
+
+ LoggerRepository rep = new Hierarchy(new RootLogger(Level.DEBUG));
+ plugin.setLoggerRepository(rep);
+
+ assertTrue("Should be notified of LoggerRepository property change",
+ l.isLatched());
+ assertTrue("LoggerRepository Old value mismatch",
+ l.getLastEvent().getOldValue() == oldValue);
+ assertTrue("LoggerRepository New vale mismatch",
+ l.getLastEvent().getNewValue() == rep);
+*/
+ }
+
+ private static class PluginListenerLatch implements PluginListener {
+
+ private boolean StartLatch;
+ private boolean StopLatch;
+ private PluginEvent LastEvent;
+
+ /* (non-Javadoc)
+ * @see org.apache.log4j.plugins.PluginListener#pluginStarted(org.apache.log4j.plugins.PluginEvent)
+ */
+ public void pluginStarted(PluginEvent e) {
+ StartLatch = true;
+ LastEvent = e;
+ }
+
+ /* (non-Javadoc)
+ * @see org.apache.log4j.plugins.PluginListener#pluginStopped(org.apache.log4j.plugins.PluginEvent)
+ */
+ public void pluginStopped(PluginEvent e) {
+ StopLatch = true;
+ LastEvent = e;
+ }
+
+ void reset() {
+ StartLatch = false;
+ StopLatch = false;
+ LastEvent = null;
+ }
+ }
+
+ private static class PropertyChangeListenerLatch
+ implements PropertyChangeListener {
+
+ boolean latch = false;
+ PropertyChangeEvent lastEvent = null;
+
+ /* (non-Javadoc)
+ * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
+ */
+ public void propertyChange(PropertyChangeEvent evt) {
+ latch = true;
+ lastEvent = evt;
+ }
+
+ boolean isLatched() {
+
+ return latch;
+ }
+
+ void reset() {
+ latch = false;
+ lastEvent = null;
+ }
+
+ PropertyChangeEvent getLastEvent() {
+
+ return lastEvent;
+ }
+ }
+
+ /**
+ Class to test the Plugin and PluginRegistry functionality. */
+ private static class PluginTester extends PluginSkeleton {
+
+ protected Logger logger;
+ private boolean active = false;
+ public int id;
+
+ public synchronized boolean isActive() {
+ logger.debug(this.getIdentifier() + " is " +
+ (active ? "active" : "inactive"));
+
+ return active;
+ }
+
+ private synchronized boolean setActive(boolean _active) {
+
+ boolean oldValue = active;
+
+ if (active != _active) {
+ active = _active;
+ firePropertyChange("active", oldValue, active);
+
+ return true;
+ } else {
+
+ return false;
+ }
+ }
+
+ public String getIdentifier() {
+
+ if (verbosePluginOutput) {
+
+ return this.getName() + "-id" + id;
+ } else {
+
+ return "plugin in " +
+ repositoryMap.get(this.getLoggerRepository());
+ }
+ }
+
+ public void activateOptions() {
+
+ if (setActive(true)) {
+ logger.debug(this.getIdentifier() + " activated");
+ } else {
+ logger.debug(this.getIdentifier() + " already activated");
+ }
+ }
+
+ public void shutdown() {
+
+ if (setActive(false)) {
+ logger.debug(this.getIdentifier() + " shutdown");
+ } else {
+ logger.debug(this.getIdentifier() + " already shutdown");
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.apache.log4j.plugins.Plugin#isEquivalent(org.apache.log4j.plugins.Plugin)
+ */
+ public boolean isEquivalent(Plugin testPlugin) {
+
+ boolean equiv = super.isEquivalent(testPlugin);
+
+ if (equiv) {
+ logger.debug("plugin equal");
+ } else if (!(testPlugin.getClass() == this.getClass())) {
+ logger.debug(
+ "plugin not equal, different class: " +
+ this.getClass().getName() + " != " +
+ testPlugin.getClass().getName());
+
+ } else if (!this.getName().equals(testPlugin.getName())) {
+ logger.debug(
+ "plugin not equal, different name: " + this.getName() +
+ " != " + testPlugin.getName());
+
+ } else if (!this.getLoggerRepository().equals(
+ testPlugin.getLoggerRepository())) {
+ logger.debug(
+ "plugin not equal, different repository: " +
+ repositoryMap.get(this.getLoggerRepository()) + " != " +
+ repositoryMap.get(testPlugin.getLoggerRepository()));
+ }
+
+ return equiv;
+ }
+ }
+
+ /**
+ Class to test the Plugin and PluginRegistry functionality. */
+ private static class PluginTester1 extends PluginTester {
+ public PluginTester1(String _name, int _id) {
+ logger = Logger.getLogger(this.getClass());
+ setName(_name);
+ id = _id;
+ }
+ }
+
+ /**
+ Class to test the Plugin and PluginRegistry functionality. */
+ private static class PluginTester2 extends PluginTester {
+ public PluginTester2(String _name, int _id) {
+ logger = Logger.getLogger(this.getClass());
+ setName(_name);
+ id = _id;
+ }
+ }
+}
diff --git a/src/test/java/org/apache/log4j/component/util/Compare.java b/src/test/java/org/apache/log4j/component/util/Compare.java
new file mode 100644
index 0000000..279c546
--- /dev/null
+++ b/src/test/java/org/apache/log4j/component/util/Compare.java
@@ -0,0 +1,132 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.component.util;
+
+import java.io.*;
+
+
+public class Compare {
+ static final int B1_NULL = -1;
+ static final int B2_NULL = -2;
+
+ private static final InputStream open(
+ final Class testClass,
+ final String fileName) throws IOException {
+ String resourceName = fileName;
+ if (fileName.startsWith("witness/")) {
+ resourceName = fileName.substring(fileName.lastIndexOf('/') + 1);
+ }
+ InputStream is = testClass.getResourceAsStream(resourceName);
+ if (is == null) {
+ File file = new File(fileName);
+ if (file.exists()) {
+ is = new FileInputStream(file);
+ } else {
+ throw new FileNotFoundException("Resource "
+ + resourceName + " not found");
+ }
+ }
+ return is;
+ }
+
+ public static boolean compare(Class testClass,
+ final String file1,
+ final String file2)
+ throws IOException {
+ BufferedReader in1 = new BufferedReader(new FileReader(file1));
+ BufferedReader in2 = new BufferedReader(new InputStreamReader(
+ open(testClass, file2)));
+ try {
+ return compare(testClass, file1, file2, in1, in2);
+ } finally {
+ in1.close();
+ in2.close();
+ }
+ }
+
+ public static boolean compare(
+ Class testClass, String file1, String file2, BufferedReader in1, BufferedReader in2) throws IOException {
+
+ String s1;
+ int lineCounter = 0;
+
+ while ((s1 = in1.readLine()) != null) {
+ lineCounter++;
+
+ String s2 = in2.readLine();
+
+ if (!s1.equals(s2)) {
+ System.out.println(
+ "Files [" + file1 + "] and [" + file2 + "] differ on line "
+ + lineCounter);
+ System.out.println("One reads: [" + s1 + "].");
+ System.out.println("Other reads:[" + s2 + "].");
+ outputFile(testClass, file1);
+ outputFile(testClass, file2);
+
+ return false;
+ }
+ }
+
+ // the second file is longer
+ if (in2.read() != -1) {
+ System.out.println(
+ "File [" + file2 + "] longer than file [" + file1 + "].");
+ outputFile(testClass, file1);
+ outputFile(testClass, file2);
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ *
+ * Prints file on the console.
+ *
+ */
+ private static void outputFile(Class testClass, String file)
+ throws IOException {
+ InputStream is = open(testClass, file);
+ BufferedReader in1 = new BufferedReader(new InputStreamReader(is));
+
+ String s1;
+ int lineCounter = 0;
+ System.out.println("--------------------------------");
+ System.out.println("Contents of " + file + ":");
+
+ while ((s1 = in1.readLine()) != null) {
+ lineCounter++;
+ System.out.print(lineCounter);
+
+ if (lineCounter < 10) {
+ System.out.print(" : ");
+ } else if (lineCounter < 100) {
+ System.out.print(" : ");
+ } else if (lineCounter < 1000) {
+ System.out.print(" : ");
+ } else {
+ System.out.print(": ");
+ }
+
+ System.out.println(s1);
+ }
+ in1.close();
+ }
+}
diff --git a/src/test/java/org/apache/log4j/receivers/VectorAppender.java b/src/test/java/org/apache/log4j/receivers/VectorAppender.java
new file mode 100644
index 0000000..54c461f
--- /dev/null
+++ b/src/test/java/org/apache/log4j/receivers/VectorAppender.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.receivers;
+
+import org.apache.log4j.AppenderSkeleton;
+import org.apache.log4j.spi.LoggingEvent;
+
+import java.util.Vector;
+
+/**
+ An appender that appends logging events to a vector.
+ @author Ceki Gülcü
+*/
+public class VectorAppender extends AppenderSkeleton {
+ public Vector vector;
+
+ long delay = 0;
+
+ public VectorAppender() {
+ super(true);
+ vector = new Vector();
+ }
+
+
+ /**
+ This method is called by the {@link AppenderSkeleton#doAppend}
+ method.
+
+ */
+ public void append(LoggingEvent event) {
+ if(delay > 0) {
+ try {
+ Thread.sleep(delay);
+ } catch (Exception e) {
+ }
+ }
+
+ vector.addElement(event);
+ }
+
+ /**
+ * Returns a vector of {@link LoggingEvent}.
+ */
+ public Vector getVector() {
+ return vector;
+ }
+
+ public synchronized void close() {
+ if (this.closed) {
+ return;
+ }
+
+ this.closed = true;
+ }
+
+ public boolean isClosed() {
+ return closed;
+ }
+
+ public boolean requiresLayout() {
+ return false;
+ }
+
+ /**
+ * Returns a delay to log.
+ */
+ public long getDelay() {
+ return delay;
+ }
+
+ /**
+ * Sets a delay to log.
+ */
+ public void setDelay(long delay) {
+ this.delay = delay;
+ }
+
+}
diff --git a/src/test/java/org/apache/log4j/receivers/db/FullCycleDBTest.java b/src/test/java/org/apache/log4j/receivers/db/FullCycleDBTest.java
new file mode 100644
index 0000000..030758a
--- /dev/null
+++ b/src/test/java/org/apache/log4j/receivers/db/FullCycleDBTest.java
@@ -0,0 +1,327 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.log4j.receivers.db;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.apache.log4j.Hierarchy;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.apache.log4j.LoggerRepositoryExImpl;
+import org.apache.log4j.MDC;
+import org.apache.log4j.component.helpers.Constants;
+import org.apache.log4j.receivers.VectorAppender;
+import org.apache.log4j.spi.LocationInfo;
+import org.apache.log4j.spi.LoggerRepository;
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.spi.RootLogger;
+import org.apache.log4j.xml.DOMConfigurator;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Vector;
+
+
+/**
+ * This test case writes a few events into a databases and reads them
+ * back comparing the event written and read back.
+ *
+ * <p>It relies heavily on the proper configuration of its environment
+ * in joran config files as well system properties.
+ * </p>
+ *
+ * <p>See also the Ant build file in the tests/ directory.</p>
+ *
+ * @author Ceki Gülcü
+ */
+public class FullCycleDBTest
+ extends TestCase {
+
+ Vector witnessEvents;
+ Hierarchy lrWrite;
+ LoggerRepository lrRead;
+ String appendConfigFile = null;
+ String readConfigFile = null;
+
+
+ /*
+ * @see TestCase#setUp()
+ */
+ protected void setUp()
+ throws Exception {
+ super.setUp();
+ appendConfigFile = "append-with-drivermanager1.xml";
+ readConfigFile = "read-with-drivermanager1.xml";
+
+ witnessEvents = new Vector();
+ lrWrite = new Hierarchy(new RootLogger(Level.DEBUG));
+ lrRead = new LoggerRepositoryExImpl(new Hierarchy(new RootLogger(Level.DEBUG)));
+
+
+ //
+ // attempt to define tables in in-memory database
+ // will throw exception if already defined.
+ //
+ Class.forName("org.hsqldb.jdbcDriver");
+ Connection connection = DriverManager.getConnection("jdbc:hsqldb:mem:testdb");
+ try {
+ Statement s = connection.createStatement();
+ s.executeUpdate("CREATE TABLE logging_event " +
+ "( sequence_number BIGINT NOT NULL, " +
+ " timestamp BIGINT NOT NULL, " +
+ " rendered_message LONGVARCHAR NOT NULL, " +
+ " logger_name VARCHAR NOT NULL, " +
+ " level_string VARCHAR NOT NULL, " +
+ " ndc LONGVARCHAR, " +
+ " thread_name VARCHAR, " +
+ " reference_flag SMALLINT, " +
+ " caller_filename VARCHAR, " +
+ " caller_class VARCHAR, " +
+ " caller_method VARCHAR, " +
+ " caller_line CHAR(4), " +
+ " event_id INT NOT NULL IDENTITY)");
+ s.executeUpdate("CREATE TABLE logging_event_property " +
+ "( event_id INT NOT NULL, " +
+ " mapped_key VARCHAR(254) NOT NULL, " +
+ " mapped_value LONGVARCHAR, " +
+ " PRIMARY KEY(event_id, mapped_key), " +
+ " FOREIGN KEY (event_id) REFERENCES logging_event(event_id))");
+ s.executeUpdate("CREATE TABLE logging_event_exception" +
+ " ( event_id INT NOT NULL, " +
+ " i SMALLINT NOT NULL," +
+ " trace_line VARCHAR NOT NULL," +
+ " PRIMARY KEY(event_id, i)," +
+ " FOREIGN KEY (event_id) REFERENCES logging_event(event_id))");
+ } catch(SQLException ex) {
+ String s = ex.toString();
+ } finally {
+ connection.close();
+ }
+
+ }
+
+
+ /*
+ * @see TestCase#tearDown()
+ */
+ protected void tearDown()
+ throws Exception {
+ super.tearDown();
+ lrRead.shutdown();
+ witnessEvents = null;
+ }
+
+ /**
+ * Constructor for DBReeceiverTest.
+ * @param arg0
+ */
+ public FullCycleDBTest(String arg0) {
+ super(arg0);
+ }
+
+
+ /**
+ * This test starts by writing a single event to a DB using DBAppender
+ * and then reads it back using DBReceiver.
+ *
+ * DB related information is specified within the configuration files.
+ * @throws Exception
+ */
+ public void testSingleOutput()
+ throws Exception {
+ DOMConfigurator jc1 = new DOMConfigurator();
+ InputStream is = FullCycleDBTest.class.getResourceAsStream(appendConfigFile);
+ jc1.doConfigure(is, lrWrite);
+ is.close();
+
+ long startTime = System.currentTimeMillis();
+ System.out.println("***startTime is "+startTime);
+
+ // Write out just one log message
+ Logger out = lrWrite.getLogger("testSingleOutput.out");
+ out.debug("some message"+startTime);
+
+ VectorAppender witnessAppender = (VectorAppender) lrWrite.getRootLogger().getAppender("VECTOR");
+ witnessEvents = witnessAppender.getVector();
+ assertEquals(1, witnessEvents.size());
+
+ // We have to close all appenders before starting to read
+ lrWrite.shutdown();
+
+ // now read it back
+ readBack(readConfigFile, startTime);
+
+ }
+
+ /**
+ * This test starts by writing a single event to a DB using DBAppender
+ * and then reads it back using DBReceiver.
+ *
+ * The written event includes MDC and repository properties as well as
+ * exception info.
+ *
+ * DB related information is specified within the configuration files.
+ * @throws Exception
+ */
+ public void testAllFields() throws IOException {
+ DOMConfigurator jc1 = new DOMConfigurator();
+ InputStream is = FullCycleDBTest.class.getResourceAsStream(appendConfigFile);
+ jc1.doConfigure(is, lrWrite);
+ is.close();
+
+ long startTime = System.currentTimeMillis();
+
+ // Write out just one log message
+ MDC.put("key1", "value1-" + startTime);
+ MDC.put("key2", "value2-"+startTime);
+ Map mdcMap = MDC.getContext();
+// LogLog.info("**********"+mdcMap.size());
+
+ // Write out just one log message
+ Logger out = lrWrite.getLogger("out"+startTime);
+
+ out.debug("some message"+startTime);
+ MDC.put("key3", "value2-" + startTime);
+ out.error("some error message"+startTime, new Exception("testing"));
+
+ // we clear the MDC to avoid interference with the events read back from
+ // the db
+ MDC.remove("key1");
+ MDC.remove("key2");
+ MDC.remove("key3");
+
+ VectorAppender witnessAppender = (VectorAppender) lrWrite.getRootLogger().getAppender("VECTOR");
+ witnessEvents = witnessAppender.getVector();
+ assertEquals(2, witnessEvents.size());
+
+ // We have to close all appenders just before starting to read
+ lrWrite.shutdown();
+
+ readBack(readConfigFile, startTime);
+ }
+
+
+ void readBack(String configfile, long startTime) throws IOException {
+ DOMConfigurator jc2 = new DOMConfigurator();
+ InputStream is = FullCycleDBTest.class.getResourceAsStream(configfile);
+ jc2.doConfigure(is, lrRead);
+ is.close();
+
+ // wait a little to allow events to be read
+ try { Thread.sleep(3100); } catch(Exception e) {}
+ VectorAppender va = (VectorAppender) lrRead.getRootLogger().getAppender("VECTOR");
+ Vector returnedEvents = getRelevantEventsFromVA(va, startTime);
+
+ compareEvents(witnessEvents, returnedEvents);
+
+ }
+
+ void compareEvents(Vector l, Vector r) {
+ assertNotNull("left vector of events should not be null");
+ assertEquals(l.size(), r.size());
+
+ for(int i = 0; i < r.size(); i++) {
+ LoggingEvent le = (LoggingEvent) l.get(i);
+ LoggingEvent re = (LoggingEvent) r.get(i);
+ assertEquals(le.getMessage(), re.getMessage());
+ assertEquals(le.getLoggerName(), re.getLoggerName());
+ assertEquals(le.getLevel(), re.getLevel());
+ assertEquals(le.getThreadName(), re.getThreadName());
+ if(re.getTimeStamp() < le.getTimeStamp()) {
+ fail("Returned event cannot preceed witness timestamp");
+ }
+
+ Map sourceMap = re.getProperties();
+ Map remap;
+ if (sourceMap == null) {
+ remap = new HashMap();
+ } else {
+ remap = new HashMap(sourceMap);
+ if (remap.containsKey(Constants.LOG4J_ID_KEY)) {
+ remap.remove(Constants.LOG4J_ID_KEY);
+ }
+ }
+ if(le.getProperties() == null || le.getProperties().size() == 0) {
+ if(remap.size() != 0) {
+ System.out.println("properties are "+remap);
+ fail("Returned event should have been empty");
+ }
+ } else {
+ assertEquals(le.getProperties(), remap);
+ }
+ comprareStringArrays( le.getThrowableStrRep(), re.getThrowableStrRep());
+ compareLocationInfo(le, re);
+ }
+ }
+
+ void comprareStringArrays(String[] la, String[] ra) {
+ if((la == null) && (ra == null)) {
+ return;
+ }
+ assertEquals(la.length, ra.length);
+ for(int i = 0; i < la.length; i++) {
+ assertEquals(la[i], ra[i]);
+ }
+ }
+
+ void compareLocationInfo(LoggingEvent l, LoggingEvent r) {
+ if(l.locationInformationExists()) {
+ assertEquals(l.getLocationInformation().fullInfo, r.getLocationInformation().fullInfo);
+ } else {
+ assertEquals(LocationInfo.NA_LOCATION_INFO, r.getLocationInformation());
+ }
+ }
+
+ Vector getRelevantEventsFromVA(VectorAppender va, long startTime) {
+ assertNotNull(va);
+ Vector v = va.getVector();
+ Vector r = new Vector();
+ // remove all elements older than startTime
+ for(Iterator i = v.iterator(); i.hasNext(); ) {
+ LoggingEvent event = (LoggingEvent) i.next();
+ if(startTime > event.getTimeStamp()) {
+ System.out.println("***Removing event with timestamp "+event.getTimeStamp());
+ } else {
+ System.out.println("***Keeping event with timestamo"+event.getTimeStamp());
+ r.add(event);
+ }
+ }
+ return r;
+ }
+
+ void dump(Vector v) {
+ for(int i = 0; i < v.size(); i++) {
+ LoggingEvent le = (LoggingEvent) v.get(i);
+ System.out.println("---"+le.getLevel()+" "+le.getLoggerName()+" "+le.getMessage());
+ }
+ }
+
+ public static Test XXsuite() {
+ TestSuite suite = new TestSuite();
+ suite.addTest(new FullCycleDBTest("testSingleOutput"));
+ suite.addTest(new FullCycleDBTest("testAllFields"));
+ return suite;
+ }
+}
diff --git a/src/test/java/org/apache/log4j/receivers/helpers/UtilLoggingLevelTest.java b/src/test/java/org/apache/log4j/receivers/helpers/UtilLoggingLevelTest.java
new file mode 100644
index 0000000..9e7705c
--- /dev/null
+++ b/src/test/java/org/apache/log4j/receivers/helpers/UtilLoggingLevelTest.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.receivers.helpers;
+
+import junit.framework.TestCase;
+
+
+/**
+ * Unit tests for UtilLoggingLevel.
+ */
+
+public class UtilLoggingLevelTest extends TestCase {
+
+ /**
+ * Create new instance of test.
+ *
+ * @param testName test name
+ */
+ public UtilLoggingLevelTest(final String testName) {
+ super(testName);
+ }
+
+ /**
+ * Test toLevel("fiNeSt").
+ */
+ public void testToLevelFINEST() {
+ assertSame(UtilLoggingLevel.FINEST, UtilLoggingLevel.toLevel("fiNeSt"));
+ }
+
+}
+
diff --git a/src/test/java/org/apache/log4j/receivers/rewrite/RewriteAppenderTest.java b/src/test/java/org/apache/log4j/receivers/rewrite/RewriteAppenderTest.java
new file mode 100644
index 0000000..6167021
--- /dev/null
+++ b/src/test/java/org/apache/log4j/receivers/rewrite/RewriteAppenderTest.java
@@ -0,0 +1,134 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.log4j.receivers.rewrite;
+
+import junit.framework.TestCase;
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+import org.apache.log4j.MDC;
+import org.apache.log4j.util.Compare;
+import org.apache.log4j.xml.DOMConfigurator;
+import org.w3c.dom.Document;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import java.io.InputStream;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.TreeMap;
+
+public class RewriteAppenderTest extends TestCase {
+ public RewriteAppenderTest(final String name) {
+ super(name);
+ }
+
+ public void setUp() {
+ LogManager.getLoggerRepository().resetConfiguration();
+ Hashtable context = MDC.getContext();
+ if (context != null) {
+ context.clear();
+ }
+ }
+
+ public void tearDown() {
+ LogManager.getLoggerRepository().shutdown();
+ }
+
+ public void configure(final String resourceName) throws Exception {
+ InputStream is = RewriteAppenderTest.class.getResourceAsStream(resourceName);
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setNamespaceAware(false);
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ Document doc = builder.parse(is);
+ DOMConfigurator.configure(doc.getDocumentElement());
+ }
+
+
+ public void testMapPolicy() throws Exception {
+ configure("map.xml");
+ Logger logger = Logger.getLogger(RewriteAppenderTest.class);
+ logger.info("Message 0");
+ MDC.put("p1", "Hola");
+
+ Map msg = new TreeMap();
+ msg.put("p1", "Hello");
+ msg.put("p2", "World");
+ msg.put("x1", "Mundo");
+ logger.info(msg);
+ msg.put("message", "Message 1");
+ logger.info(msg);
+ assertTrue(Compare.compare(RewriteAppenderTest.class, "temp", "map.log"));
+ }
+
+ private static class BaseBean {
+ private final Object p2;
+ private final Object x1;
+
+ public BaseBean(final Object p2,
+ final Object x1) {
+ this.p2 = p2;
+ this.x1 = x1;
+ }
+
+ public Object getP2() {
+ return p2;
+ }
+
+ public Object getX1() {
+ return x1;
+ }
+
+ public String toString() {
+ return "I am bean.";
+ }
+ }
+
+ private static class MessageBean extends BaseBean {
+ private final Object msg;
+
+ public MessageBean(final Object msg,
+ final Object p2,
+ final Object x1) {
+ super(p2, x1);
+ this.msg = msg;
+ }
+
+ public Object getMessage() {
+ return msg;
+ }
+ }
+
+ public void testReflectionPolicy() throws Exception {
+ configure("reflection.xml");
+ Logger logger = Logger.getLogger(RewriteAppenderTest.class);
+ logger.info("Message 0");
+ logger.info(new BaseBean("Hello", "World" ));
+ MDC.put("p1", "Hola");
+ MDC.put("p2", "p2");
+ logger.info(new MessageBean("Welcome to The Hub", "Hello", "World" ));
+ assertTrue(Compare.compare(RewriteAppenderTest.class, "temp", "reflection.log"));
+ }
+
+ public void testPropertyPolicy() throws Exception {
+ configure("property.xml");
+ Logger logger = Logger.getLogger(RewriteAppenderTest.class);
+ logger.info("Message 0");
+ MDC.put("p1", "Hola");
+ logger.info("Message 1");
+ assertTrue(Compare.compare(RewriteAppenderTest.class, "temp", "property.log"));
+ }
+}
diff --git a/src/test/java/org/apache/log4j/receivers/util/Compare.java b/src/test/java/org/apache/log4j/receivers/util/Compare.java
new file mode 100644
index 0000000..98aa0c7
--- /dev/null
+++ b/src/test/java/org/apache/log4j/receivers/util/Compare.java
@@ -0,0 +1,195 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.receivers.util;
+
+import java.io.*;
+import java.util.zip.GZIPInputStream;
+
+
+public class Compare {
+ static final int B1_NULL = -1;
+ static final int B2_NULL = -2;
+
+ private static final InputStream open(
+ final Class testClass,
+ final String fileName) throws IOException {
+ String resourceName = fileName;
+ if (fileName.startsWith("witness/")) {
+ resourceName = fileName.substring(fileName.lastIndexOf('/') + 1);
+ }
+ InputStream is = testClass.getResourceAsStream(resourceName);
+ if (is == null) {
+ File file = new File(fileName);
+ if (file.exists()) {
+ is = new FileInputStream(file);
+ } else {
+ throw new FileNotFoundException("Resource "
+ + resourceName + " not found");
+ }
+ }
+ return is;
+ }
+
+ public static boolean compare(Class testClass,
+ final String file1,
+ final String file2)
+ throws IOException {
+ BufferedReader in1 = new BufferedReader(new FileReader(file1));
+ BufferedReader in2 = new BufferedReader(new InputStreamReader(
+ open(testClass, file2)));
+ try {
+ return compare(testClass, file1, file2, in1, in2);
+ } finally {
+ in1.close();
+ in2.close();
+ }
+ }
+
+ public static boolean compare(
+ Class testClass, String file1, String file2, BufferedReader in1, BufferedReader in2) throws IOException {
+
+ String s1;
+ int lineCounter = 0;
+
+ while ((s1 = in1.readLine()) != null) {
+ lineCounter++;
+
+ String s2 = in2.readLine();
+
+ if (!s1.equals(s2)) {
+ System.out.println(
+ "Files [" + file1 + "] and [" + file2 + "] differ on line "
+ + lineCounter);
+ System.out.println("One reads: [" + s1 + "].");
+ System.out.println("Other reads:[" + s2 + "].");
+ outputFile(testClass, file1);
+ outputFile(testClass, file2);
+
+ return false;
+ }
+ }
+
+ // the second file is longer
+ if (in2.read() != -1) {
+ System.out.println(
+ "File [" + file2 + "] longer than file [" + file1 + "].");
+ outputFile(testClass, file1);
+ outputFile(testClass, file2);
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ *
+ * Prints file on the console.
+ *
+ */
+ private static void outputFile(Class testClass, String file)
+ throws IOException {
+ InputStream is = open(testClass, file);
+ BufferedReader in1 = new BufferedReader(new InputStreamReader(is));
+
+ String s1;
+ int lineCounter = 0;
+ System.out.println("--------------------------------");
+ System.out.println("Contents of " + file + ":");
+
+ while ((s1 = in1.readLine()) != null) {
+ lineCounter++;
+ System.out.print(lineCounter);
+
+ if (lineCounter < 10) {
+ System.out.print(" : ");
+ } else if (lineCounter < 100) {
+ System.out.print(" : ");
+ } else if (lineCounter < 1000) {
+ System.out.print(" : ");
+ } else {
+ System.out.print(": ");
+ }
+
+ System.out.println(s1);
+ }
+ in1.close();
+ }
+
+
+ public static boolean gzCompare(final Class testClass,
+ final String actual,
+ final String expected)
+ throws FileNotFoundException, IOException {
+ String resourceName = expected;
+ int lastSlash = expected.lastIndexOf("/");
+ if (lastSlash >= 0) {
+ resourceName = expected.substring(lastSlash + 1);
+ }
+ InputStream resourceStream = testClass.getResourceAsStream(resourceName);
+ if (resourceStream == null) {
+ throw new FileNotFoundException("Could not locate resource " + resourceName);
+ }
+
+ BufferedReader in1 = new BufferedReader(new InputStreamReader(new GZIPInputStream(new FileInputStream(actual))));
+ BufferedReader in2 = new BufferedReader(new InputStreamReader(new GZIPInputStream(resourceStream)));
+ try {
+ return gzCompare(testClass, actual, expected, in1, in2);
+ } finally {
+ in1.close();
+ in2.close();
+ }
+ }
+
+ public static boolean gzCompare(Class testClass, String file1, String file2, BufferedReader in1, BufferedReader in2) throws IOException {
+
+ String s1;
+ int lineCounter = 0;
+
+ while ((s1 = in1.readLine()) != null) {
+ lineCounter++;
+
+ String s2 = in2.readLine();
+
+ if (!s1.equals(s2)) {
+ System.out.println(
+ "Files [" + file1 + "] and [" + file2 + "] differ on line "
+ + lineCounter);
+ System.out.println("One reads: [" + s1 + "].");
+ System.out.println("Other reads:[" + s2 + "].");
+ outputFile(testClass, file1);
+ outputFile(testClass, file2);
+
+ return false;
+ }
+ }
+
+ // the second file is longer
+ if (in2.read() != -1) {
+ System.out.println(
+ "File [" + file2 + "] longer than file [" + file1 + "].");
+ outputFile(testClass, file1);
+ outputFile(testClass, file2);
+
+ return false;
+ }
+
+ return true;
+ }
+
+}
diff --git a/src/test/java/org/apache/log4j/receivers/xml/XMLDecoderTest.java b/src/test/java/org/apache/log4j/receivers/xml/XMLDecoderTest.java
new file mode 100644
index 0000000..2fef84b
--- /dev/null
+++ b/src/test/java/org/apache/log4j/receivers/xml/XMLDecoderTest.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.log4j.receivers.xml;
+
+import junit.framework.TestCase;
+
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.nio.CharBuffer;
+import java.util.Vector;
+
+/**
+ * Tests for XMLDecoder.
+ *
+ */
+public class XMLDecoderTest extends TestCase {
+
+
+ /**
+ * Constructor for XMLDecoderTest.
+ * @param arg0 test name.
+ */
+ public XMLDecoderTest(String arg0) {
+ super(arg0);
+ }
+
+ public String getStringFromResource(final String resourceName,
+ final int maxSize) throws Exception {
+ InputStream is = XMLDecoderTest.class.getResourceAsStream(resourceName);
+ if (is == null) {
+ throw new FileNotFoundException(resourceName);
+ }
+ InputStreamReader reader = new InputStreamReader(is, "UTF-8");
+ CharBuffer cb = CharBuffer.allocate(maxSize);
+ for(int chars = reader.read(cb);
+ chars != -1;
+ chars = reader.read(cb));
+ cb.flip();
+ return cb.toString();
+ }
+
+ public void testDecodeEventsString1() throws Exception {
+ String xmlStr = getStringFromResource("xmlLayout.1.xml", 10000);
+ XMLDecoder decoder = new XMLDecoder();
+ Vector events = decoder.decodeEvents(xmlStr);
+ assertEquals(17, events.size());
+ }
+
+ public void testDecodeEventsString2() throws Exception {
+ String xmlStr = getStringFromResource("xsltLayout.1.xml", 10000);
+ XMLDecoder decoder = new XMLDecoder();
+ Vector events = decoder.decodeEvents(xmlStr);
+ assertEquals(15, events.size());
+ }
+
+ public void testDecodeEventsURL1() throws Exception {
+ URL resource = XMLDecoderTest.class.getResource("xmlLayout.1.xml");
+ XMLDecoder decoder = new XMLDecoder();
+ Vector events = decoder.decode(resource);
+ assertEquals(17, events.size());
+ }
+
+ public void testDecodeEventsURL2() throws Exception {
+ URL resource = XMLDecoderTest.class.getResource("xsltLayout.1.xml");
+ XMLDecoder decoder = new XMLDecoder();
+ Vector events = decoder.decode(resource);
+ assertEquals(15, events.size());
+ }
+
+}
diff --git a/src/test/resources/org/apache/log4j/component/plugins/plugins.PluginTestCase.test1.txt b/src/test/resources/org/apache/log4j/component/plugins/plugins.PluginTestCase.test1.txt
new file mode 100644
index 0000000..8b13af0
--- /dev/null
+++ b/src/test/resources/org/apache/log4j/component/plugins/plugins.PluginTestCase.test1.txt
@@ -0,0 +1,23 @@
+INFO - test 1.1 - basic starting/stopping
+INFO - starting plugin1-id1
+INFO - stopping plugin1-id1 using plugin object
+DEBUG - plugin1-id1 already shutdown
+INFO - test 1.2 - restarting and starting when already started
+INFO - restarting plugin1-id1
+INFO - restarting plugin1-id1 again
+DEBUG - plugin1-id1 already shutdown
+INFO - test 1.3- stopping and stopping when already stopped
+INFO - stopping plugin1-id1
+DEBUG - plugin1-id1 already shutdown
+INFO - stopping plugin1-id1 again
+INFO - test 1.4 - restarting then stopping by plugin name
+INFO - starting plugin1-id1
+INFO - stopping plugin1-id1 using plugin name
+DEBUG - plugin1-id1 already shutdown
+INFO - stopping plugin1-id1 using plugin object
+INFO - stopping all plugins
+DEBUG - plugin1-id1 is inactive
+INFO - plugin1-id1 is inactive
+DEBUG - plugin2-id4 is inactive
+INFO - plugin2-id4 is inactive
+INFO - stopping all plugins again
diff --git a/src/test/resources/org/apache/log4j/receivers/db/append-with-drivermanager1.xml b/src/test/resources/org/apache/log4j/receivers/db/append-with-drivermanager1.xml
new file mode 100644
index 0000000..5d701b5
--- /dev/null
+++ b/src/test/resources/org/apache/log4j/receivers/db/append-with-drivermanager1.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<!DOCTYPE log4j:configuration SYSTEM 'http://logging.apache.org/log4j/1.2/log4j.dtd'>
+
+<log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/' debug="true">
+
+ <appender name="DB" class="org.apache.log4j.db.DBAppender">
+ <param name="locationInfo" value="true"/>
+ <connectionSource class="org.apache.log4j.receivers.db.DriverManagerConnectionSource">
+ <param name="driverClass" value="org.hsqldb.jdbcDriver"/>
+ <param name="url" value="jdbc:hsqldb:mem:testdb"/>
+ <param name="user" value="sa"/>
+ <param name="password" value=""/>
+ </connectionSource>
+ </appender>
+
+ <appender name="VECTOR" class="org.apache.log4j.receivers.VectorAppender">
+ </appender>
+
+ <!-- Prevent internal log4j DEBUG messages from polluting the output. -->
+ <logger name="org.apache.log4j.joran"><level value="INFO" /></logger>
+ <logger name="org.apache.log4j.config"><level value="INFO" /></logger>
+ <logger name="org.apache.log4j.db.DBAppender"><level value="INFO" /></logger>
+
+ <root>
+ <level value ="debug"/>
+ <appender-ref ref="DB" />
+ <appender-ref ref="VECTOR" />
+ </root>
+</log4j:configuration>
+
+
diff --git a/src/test/resources/org/apache/log4j/receivers/db/read-with-drivermanager1.xml b/src/test/resources/org/apache/log4j/receivers/db/read-with-drivermanager1.xml
new file mode 100644
index 0000000..3e77c49
--- /dev/null
+++ b/src/test/resources/org/apache/log4j/receivers/db/read-with-drivermanager1.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<!DOCTYPE log4j:configuration SYSTEM 'http://logging.apache.org/log4j/1.2/log4j.dtd'>
+
+<log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/' debug="true">
+
+ <appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
+ <layout class="org.apache.log4j.PatternLayout">
+ <param name="ConversionPattern" value="READING %relative %level %logger - %message%n"/>
+ </layout>
+ </appender>
+
+ <appender name="VECTOR" class="org.apache.log4j.VectorAppender">
+ </appender>
+
+ <plugin name="DB" class="org.apache.log4j.db.DBReceiver">
+ <connectionSource class="org.apache.log4j.db.DriverManagerConnectionSource">
+ <param name="driverClass" value="org.hsqldb.jdbcDriver"/>
+ <param name="url" value="jdbc:hsqldb:mem:testdb"/>
+ <param name="user" value="sa"/>
+ <param name="password" value=""/>
+ </connectionSource>
+ </plugin>
+
+
+ <!-- Prevent internal log4j DEBUG messages from polluting the output. -->
+ <logger name="org.apache.log4j.joran"><level value="INFO" /></logger>
+ <logger name="org.apache.log4j.config"><level value="INFO" /></logger>
+ <logger name="org.apache.log4j.db"><level value="INFO" /></logger>
+
+ <root>
+ <level value="debug"/>
+ <appender-ref ref="VECTOR" />
+ <appender-ref ref="CONSOLE" />
+ </root>
+</log4j:configuration>
+
+
+
diff --git a/src/test/resources/org/apache/log4j/receivers/rewrite/map.log b/src/test/resources/org/apache/log4j/receivers/rewrite/map.log
new file mode 100644
index 0000000..3ce933f
--- /dev/null
+++ b/src/test/resources/org/apache/log4j/receivers/rewrite/map.log
@@ -0,0 +1,3 @@
+INFO org.apache.log4j.rewrite.RewriteAppenderTest - p1: p2: Message 0
+INFO org.apache.log4j.rewrite.RewriteAppenderTest - p1:Hello p2:World {p1=Hello, p2=World, x1=Mundo}
+INFO org.apache.log4j.rewrite.RewriteAppenderTest - p1:Hello p2:World Message 1
diff --git a/src/test/resources/org/apache/log4j/receivers/rewrite/map.xml b/src/test/resources/org/apache/log4j/receivers/rewrite/map.xml
new file mode 100644
index 0000000..7cb60b7
--- /dev/null
+++ b/src/test/resources/org/apache/log4j/receivers/rewrite/map.xml
@@ -0,0 +1,38 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/'>
+ <appender name="F1" class="org.apache.log4j.FileAppender">
+ <param name="file" value="temp"/>
+ <param name="append" value="false"/>
+ <layout class="org.apache.log4j.PatternLayout">
+ <param name="ConversionPattern" value="%p %c - p1:%X{p1} p2:%X{p2} %m%n"/>
+ </layout>
+ </appender>
+
+
+ <appender name="A1" class="org.apache.log4j.rewrite.RewriteAppender">
+ <appender-ref ref="F1"/>
+ <rewritePolicy class="org.apache.log4j.rewrite.MapRewritePolicy"/>
+ </appender>
+
+ <root>
+ <level value ="debug" />
+ <appender-ref ref="A1" />
+ </root>
+
+</log4j:configuration>
diff --git a/src/test/resources/org/apache/log4j/receivers/rewrite/property.log b/src/test/resources/org/apache/log4j/receivers/rewrite/property.log
new file mode 100644
index 0000000..9aa2c49
--- /dev/null
+++ b/src/test/resources/org/apache/log4j/receivers/rewrite/property.log
@@ -0,0 +1,2 @@
+INFO org.apache.log4j.rewrite.RewriteAppenderTest - p1:Hello p2:World Message 0
+INFO org.apache.log4j.rewrite.RewriteAppenderTest - p1:Hola p2:World Message 1
diff --git a/src/test/resources/org/apache/log4j/receivers/rewrite/property.xml b/src/test/resources/org/apache/log4j/receivers/rewrite/property.xml
new file mode 100644
index 0000000..13a04f8
--- /dev/null
+++ b/src/test/resources/org/apache/log4j/receivers/rewrite/property.xml
@@ -0,0 +1,40 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/'>
+ <appender name="F1" class="org.apache.log4j.FileAppender">
+ <param name="file" value="temp"/>
+ <param name="append" value="false"/>
+ <layout class="org.apache.log4j.PatternLayout">
+ <param name="ConversionPattern" value="%p %c - p1:%X{p1} p2:%X{p2} %m%n"/>
+ </layout>
+ </appender>
+
+
+ <appender name="A1" class="org.apache.log4j.rewrite.RewriteAppender">
+ <appender-ref ref="F1"/>
+ <rewritePolicy class="org.apache.log4j.rewrite.PropertyRewritePolicy">
+ <param name="properties" value="p1=Hello,p2=World,x1=3.1415"/>
+ </rewritePolicy>
+ </appender>
+
+ <root>
+ <level value ="debug" />
+ <appender-ref ref="A1" />
+ </root>
+
+</log4j:configuration>
diff --git a/src/test/resources/org/apache/log4j/receivers/rewrite/reflection.log b/src/test/resources/org/apache/log4j/receivers/rewrite/reflection.log
new file mode 100644
index 0000000..da0b52f
--- /dev/null
+++ b/src/test/resources/org/apache/log4j/receivers/rewrite/reflection.log
@@ -0,0 +1,3 @@
+INFO org.apache.log4j.rewrite.RewriteAppenderTest - p1: p2: Message 0
+INFO org.apache.log4j.rewrite.RewriteAppenderTest - p1: p2:Hello I am bean.
+INFO org.apache.log4j.rewrite.RewriteAppenderTest - p1:Hola p2:Hello Welcome to The Hub
diff --git a/src/test/resources/org/apache/log4j/receivers/rewrite/reflection.xml b/src/test/resources/org/apache/log4j/receivers/rewrite/reflection.xml
new file mode 100644
index 0000000..643850b
--- /dev/null
+++ b/src/test/resources/org/apache/log4j/receivers/rewrite/reflection.xml
@@ -0,0 +1,38 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/'>
+ <appender name="F1" class="org.apache.log4j.FileAppender">
+ <param name="file" value="temp"/>
+ <param name="append" value="false"/>
+ <layout class="org.apache.log4j.PatternLayout">
+ <param name="ConversionPattern" value="%p %c - p1:%X{p1} p2:%X{p2} %m%n"/>
+ </layout>
+ </appender>
+
+
+ <appender name="A1" class="org.apache.log4j.rewrite.RewriteAppender">
+ <appender-ref ref="F1"/>
+ <rewritePolicy class="org.apache.log4j.rewrite.ReflectionRewritePolicy"/>
+ </appender>
+
+ <root>
+ <level value ="debug" />
+ <appender-ref ref="A1" />
+ </root>
+
+</log4j:configuration>
diff --git a/src/test/resources/org/apache/log4j/receivers/xml/xmlLayout.1.xml b/src/test/resources/org/apache/log4j/receivers/xml/xmlLayout.1.xml
new file mode 100644
index 0000000..06dc106
--- /dev/null
+++ b/src/test/resources/org/apache/log4j/receivers/xml/xmlLayout.1.xml
@@ -0,0 +1,162 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<log4j:event logger="org.apache.log4j.xml.XMLLayoutTestCase$X" timestamp="1187196936354" level="INFO" thread="main">
+<log4j:message><![CDATA[in X() constructor]]></log4j:message>
+</log4j:event>
+
+<log4j:event logger="org.apache.log4j.xml.XMLLayoutTestCase" timestamp="1187196936358" level="TRACE" thread="main">
+<log4j:message><![CDATA[Message 0]]></log4j:message>
+</log4j:event>
+
+<log4j:event logger="root" timestamp="1187196936358" level="TRACE" thread="main">
+<log4j:message><![CDATA[Message 0]]></log4j:message>
+</log4j:event>
+
+<log4j:event logger="org.apache.log4j.xml.XMLLayoutTestCase" timestamp="1187196936358" level="DEBUG" thread="main">
+<log4j:message><![CDATA[Message 1]]></log4j:message>
+</log4j:event>
+
+<log4j:event logger="root" timestamp="1187196936363" level="DEBUG" thread="main">
+<log4j:message><![CDATA[Message 1]]></log4j:message>
+</log4j:event>
+
+<log4j:event logger="org.apache.log4j.xml.XMLLayoutTestCase" timestamp="1187196936363" level="INFO" thread="main">
+<log4j:message><![CDATA[Message 2]]></log4j:message>
+</log4j:event>
+
+<log4j:event logger="root" timestamp="1187196936363" level="INFO" thread="main">
+<log4j:message><![CDATA[Message 2]]></log4j:message>
+</log4j:event>
+
+<log4j:event logger="org.apache.log4j.xml.XMLLayoutTestCase" timestamp="1187196936363" level="WARN" thread="main">
+<log4j:message><![CDATA[Message 3]]></log4j:message>
+</log4j:event>
+
+<log4j:event logger="root" timestamp="1187196936363" level="WARN" thread="main">
+<log4j:message><![CDATA[Message 3]]></log4j:message>
+</log4j:event>
+
+<log4j:event logger="org.apache.log4j.xml.XMLLayoutTestCase" timestamp="1187196936363" level="ERROR" thread="main">
+<log4j:message><![CDATA[Message 4]]></log4j:message>
+</log4j:event>
+
+<log4j:event logger="root" timestamp="1187196936363" level="ERROR" thread="main">
+<log4j:message><![CDATA[Message 4]]></log4j:message>
+</log4j:event>
+
+<log4j:event logger="org.apache.log4j.xml.XMLLayoutTestCase" timestamp="1187196936363" level="FATAL" thread="main">
+<log4j:message><![CDATA[Message 5]]></log4j:message>
+</log4j:event>
+
+<log4j:event logger="root" timestamp="1187196936363" level="FATAL" thread="main">
+<log4j:message><![CDATA[Message 5]]></log4j:message>
+</log4j:event>
+
+<log4j:event logger="org.apache.log4j.xml.XMLLayoutTestCase" timestamp="1187196936364" level="DEBUG" thread="main">
+<log4j:message><![CDATA[Message 6]]></log4j:message>
+<log4j:throwable><![CDATA[java.lang.Exception: Just testing
+ at org.apache.log4j.xml.XMLLayoutTestCase.common(XMLLayoutTestCase.java:219)
+ at org.apache.log4j.xml.XMLLayoutTestCase.basic(XMLLayoutTestCase.java:64)
+ at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
+ at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
+ at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
+ at java.lang.reflect.Method.invoke(Method.java:585)
+ at junit.framework.TestCase.runTest(TestCase.java:154)
+ at junit.framework.TestCase.runBare(TestCase.java:127)
+ at junit.framework.TestResult$1.protect(TestResult.java:106)
+ at junit.framework.TestResult.runProtected(TestResult.java:124)
+ at junit.framework.TestResult.run(TestResult.java:109)
+ at junit.framework.TestCase.run(TestCase.java:118)
+ at junit.framework.TestSuite.runTest(TestSuite.java:208)
+ at junit.framework.TestSuite.run(TestSuite.java:203)
+ at junit.textui.TestRunner.doRun(TestRunner.java:116)
+ at com.intellij.rt.execution.junit.IdeaTestRunner.doRun(IdeaTestRunner.java:69)
+ at junit.textui.TestRunner.doRun(TestRunner.java:109)
+ at com.intellij.rt.execution.junit.IdeaTestRunner.startRunnerWithArgs(IdeaTestRunner.java:24)
+ at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:118)
+ at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:40)
+]]></log4j:throwable>
+</log4j:event>
+
+<log4j:event logger="root" timestamp="1187196936368" level="DEBUG" thread="main">
+<log4j:message><![CDATA[Message 6]]></log4j:message>
+<log4j:throwable><![CDATA[java.lang.Exception: Just testing
+ at org.apache.log4j.xml.XMLLayoutTestCase.common(XMLLayoutTestCase.java:219)
+ at org.apache.log4j.xml.XMLLayoutTestCase.basic(XMLLayoutTestCase.java:64)
+ at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
+ at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
+ at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
+ at java.lang.reflect.Method.invoke(Method.java:585)
+ at junit.framework.TestCase.runTest(TestCase.java:154)
+ at junit.framework.TestCase.runBare(TestCase.java:127)
+ at junit.framework.TestResult$1.protect(TestResult.java:106)
+ at junit.framework.TestResult.runProtected(TestResult.java:124)
+ at junit.framework.TestResult.run(TestResult.java:109)
+ at junit.framework.TestCase.run(TestCase.java:118)
+ at junit.framework.TestSuite.runTest(TestSuite.java:208)
+ at junit.framework.TestSuite.run(TestSuite.java:203)
+ at junit.textui.TestRunner.doRun(TestRunner.java:116)
+ at com.intellij.rt.execution.junit.IdeaTestRunner.doRun(IdeaTestRunner.java:69)
+ at junit.textui.TestRunner.doRun(TestRunner.java:109)
+ at com.intellij.rt.execution.junit.IdeaTestRunner.startRunnerWithArgs(IdeaTestRunner.java:24)
+ at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:118)
+ at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:40)
+]]></log4j:throwable>
+</log4j:event>
+
+<log4j:event logger="org.apache.log4j.xml.XMLLayoutTestCase" timestamp="1187196936369" level="ERROR" thread="main">
+<log4j:message><![CDATA[Message 7]]></log4j:message>
+<log4j:throwable><![CDATA[java.lang.Exception: Just testing
+ at org.apache.log4j.xml.XMLLayoutTestCase.common(XMLLayoutTestCase.java:219)
+ at org.apache.log4j.xml.XMLLayoutTestCase.basic(XMLLayoutTestCase.java:64)
+ at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
+ at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
+ at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
+ at java.lang.reflect.Method.invoke(Method.java:585)
+ at junit.framework.TestCase.runTest(TestCase.java:154)
+ at junit.framework.TestCase.runBare(TestCase.java:127)
+ at junit.framework.TestResult$1.protect(TestResult.java:106)
+ at junit.framework.TestResult.runProtected(TestResult.java:124)
+ at junit.framework.TestResult.run(TestResult.java:109)
+ at junit.framework.TestCase.run(TestCase.java:118)
+ at junit.framework.TestSuite.runTest(TestSuite.java:208)
+ at junit.framework.TestSuite.run(TestSuite.java:203)
+ at junit.textui.TestRunner.doRun(TestRunner.java:116)
+]]></log4j:throwable>
+</log4j:event>
+
+<log4j:event logger="root" timestamp="1187196936371" level="ERROR" thread="main">
+<log4j:message><![CDATA[Message 7]]></log4j:message>
+<log4j:throwable><![CDATA[java.lang.Exception: Just testing
+ at org.apache.log4j.xml.XMLLayoutTestCase.common(XMLLayoutTestCase.java:219)
+ at org.apache.log4j.xml.XMLLayoutTestCase.basic(XMLLayoutTestCase.java:64)
+ at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
+ at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
+ at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
+ at java.lang.reflect.Method.invoke(Method.java:585)
+ at junit.framework.TestCase.runTest(TestCase.java:154)
+ at junit.framework.TestCase.runBare(TestCase.java:127)
+ at junit.framework.TestResult$1.protect(TestResult.java:106)
+ at junit.framework.TestResult.runProtected(TestResult.java:124)
+ at junit.framework.TestResult.run(TestResult.java:109)
+ at junit.framework.TestCase.run(TestCase.java:118)
+ at junit.framework.TestSuite.runTest(TestSuite.java:208)
+ at junit.framework.TestSuite.run(TestSuite.java:203)
+ at junit.textui.TestRunner.doRun(TestRunner.java:116)
+]]></log4j:throwable>
+</log4j:event>
+
diff --git a/src/test/resources/org/apache/log4j/receivers/xml/xsltLayout.1.xml b/src/test/resources/org/apache/log4j/receivers/xml/xsltLayout.1.xml
new file mode 100644
index 0000000..596e84d
--- /dev/null
+++ b/src/test/resources/org/apache/log4j/receivers/xml/xsltLayout.1.xml
@@ -0,0 +1,139 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<log4j:event xmlns:log4j="http://jakarta.apache.org/log4j/" logger="org.apache.log4j.xml.XSLTLayoutTestCase$X" time="2007-08-15T16:42:31.821Z" timestamp="1187196151821" level="INFO" thread="main">
+<log4j:message>in X() constructor</log4j:message>
+</log4j:event>
+<log4j:event xmlns:log4j="http://jakarta.apache.org/log4j/" logger="org.apache.log4j.xml.XSLTLayoutTestCase" time="2007-08-15T16:42:32.252Z" timestamp="1187196152252" level="DEBUG" thread="main">
+<log4j:message>Message 0</log4j:message>
+</log4j:event>
+<log4j:event xmlns:log4j="http://jakarta.apache.org/log4j/" logger="root" time="2007-08-15T16:42:32.254Z" timestamp="1187196152254" level="DEBUG" thread="main">
+<log4j:message>Message 0</log4j:message>
+</log4j:event>
+<log4j:event xmlns:log4j="http://jakarta.apache.org/log4j/" logger="org.apache.log4j.xml.XSLTLayoutTestCase" time="2007-08-15T16:42:32.263Z" timestamp="1187196152263" level="INFO" thread="main">
+<log4j:message>Message 1</log4j:message>
+</log4j:event>
+<log4j:event xmlns:log4j="http://jakarta.apache.org/log4j/" logger="root" time="2007-08-15T16:42:32.264Z" timestamp="1187196152264" level="INFO" thread="main">
+<log4j:message>Message 1</log4j:message>
+</log4j:event>
+<log4j:event xmlns:log4j="http://jakarta.apache.org/log4j/" logger="org.apache.log4j.xml.XSLTLayoutTestCase" time="2007-08-15T16:42:32.268Z" timestamp="1187196152268" level="WARN" thread="main">
+<log4j:message>Message 2</log4j:message>
+</log4j:event>
+<log4j:event xmlns:log4j="http://jakarta.apache.org/log4j/" logger="root" time="2007-08-15T16:42:32.269Z" timestamp="1187196152269" level="WARN" thread="main">
+<log4j:message>Message 2</log4j:message>
+</log4j:event>
+<log4j:event xmlns:log4j="http://jakarta.apache.org/log4j/" logger="org.apache.log4j.xml.XSLTLayoutTestCase" time="2007-08-15T16:42:32.271Z" timestamp="1187196152271" level="ERROR" thread="main">
+<log4j:message>Message 3</log4j:message>
+</log4j:event>
+<log4j:event xmlns:log4j="http://jakarta.apache.org/log4j/" logger="root" time="2007-08-15T16:42:32.272Z" timestamp="1187196152272" level="ERROR" thread="main">
+<log4j:message>Message 3</log4j:message>
+</log4j:event>
+<log4j:event xmlns:log4j="http://jakarta.apache.org/log4j/" logger="org.apache.log4j.xml.XSLTLayoutTestCase" time="2007-08-15T16:42:32.273Z" timestamp="1187196152273" level="FATAL" thread="main">
+<log4j:message>Message 4</log4j:message>
+</log4j:event>
+<log4j:event xmlns:log4j="http://jakarta.apache.org/log4j/" logger="root" time="2007-08-15T16:42:32.275Z" timestamp="1187196152275" level="FATAL" thread="main">
+<log4j:message>Message 4</log4j:message>
+</log4j:event>
+<log4j:event xmlns:log4j="http://jakarta.apache.org/log4j/" logger="org.apache.log4j.xml.XSLTLayoutTestCase" time="2007-08-15T16:42:32.279Z" timestamp="1187196152279" level="DEBUG" thread="main">
+<log4j:message>Message 5</log4j:message>
+<log4j:throwable>java.lang.Exception: Just testing
+ at org.apache.log4j.xml.XSLTLayoutTestCase.common(XSLTLayoutTestCase.java:201)
+ at org.apache.log4j.xml.XSLTLayoutTestCase.testBasic(XSLTLayoutTestCase.java:65)
+ at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
+ at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
+ at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
+ at java.lang.reflect.Method.invoke(Method.java:585)
+ at junit.framework.TestCase.runTest(TestCase.java:154)
+ at junit.framework.TestCase.runBare(TestCase.java:127)
+ at junit.framework.TestResult$1.protect(TestResult.java:106)
+ at junit.framework.TestResult.runProtected(TestResult.java:124)
+ at junit.framework.TestResult.run(TestResult.java:109)
+ at junit.framework.TestCase.run(TestCase.java:118)
+ at junit.framework.TestSuite.runTest(TestSuite.java:208)
+ at junit.framework.TestSuite.run(TestSuite.java:203)
+ at junit.textui.TestRunner.doRun(TestRunner.java:116)
+ at com.intellij.rt.execution.junit.IdeaTestRunner.doRun(IdeaTestRunner.java:69)
+ at junit.textui.TestRunner.doRun(TestRunner.java:109)
+ at com.intellij.rt.execution.junit.IdeaTestRunner.startRunnerWithArgs(IdeaTestRunner.java:24)
+ at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:118)
+ at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:40)
+</log4j:throwable>
+</log4j:event>
+<log4j:event xmlns:log4j="http://jakarta.apache.org/log4j/" logger="root" time="2007-08-15T16:42:32.304Z" timestamp="1187196152304" level="DEBUG" thread="main">
+<log4j:message>Message 5</log4j:message>
+<log4j:throwable>java.lang.Exception: Just testing
+ at org.apache.log4j.xml.XSLTLayoutTestCase.common(XSLTLayoutTestCase.java:201)
+ at org.apache.log4j.xml.XSLTLayoutTestCase.testBasic(XSLTLayoutTestCase.java:65)
+ at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
+ at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
+ at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
+ at java.lang.reflect.Method.invoke(Method.java:585)
+ at junit.framework.TestCase.runTest(TestCase.java:154)
+ at junit.framework.TestCase.runBare(TestCase.java:127)
+ at junit.framework.TestResult$1.protect(TestResult.java:106)
+ at junit.framework.TestResult.runProtected(TestResult.java:124)
+ at junit.framework.TestResult.run(TestResult.java:109)
+ at junit.framework.TestCase.run(TestCase.java:118)
+ at junit.framework.TestSuite.runTest(TestSuite.java:208)
+ at junit.framework.TestSuite.run(TestSuite.java:203)
+ at junit.textui.TestRunner.doRun(TestRunner.java:116)
+ at com.intellij.rt.execution.junit.IdeaTestRunner.doRun(IdeaTestRunner.java:69)
+ at junit.textui.TestRunner.doRun(TestRunner.java:109)
+ at com.intellij.rt.execution.junit.IdeaTestRunner.startRunnerWithArgs(IdeaTestRunner.java:24)
+ at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:118)
+ at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:40)
+</log4j:throwable>
+</log4j:event>
+<log4j:event xmlns:log4j="http://jakarta.apache.org/log4j/" logger="org.apache.log4j.xml.XSLTLayoutTestCase" time="2007-08-15T16:42:32.308Z" timestamp="1187196152308" level="ERROR" thread="main">
+<log4j:message>Message 6</log4j:message>
+<log4j:throwable>java.lang.Exception: Just testing
+ at org.apache.log4j.xml.XSLTLayoutTestCase.common(XSLTLayoutTestCase.java:201)
+ at org.apache.log4j.xml.XSLTLayoutTestCase.testBasic(XSLTLayoutTestCase.java:65)
+ at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
+ at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
+ at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
+ at java.lang.reflect.Method.invoke(Method.java:585)
+ at junit.framework.TestCase.runTest(TestCase.java:154)
+ at junit.framework.TestCase.runBare(TestCase.java:127)
+ at junit.framework.TestResult$1.protect(TestResult.java:106)
+ at junit.framework.TestResult.runProtected(TestResult.java:124)
+ at junit.framework.TestResult.run(TestResult.java:109)
+ at junit.framework.TestCase.run(TestCase.java:118)
+ at junit.framework.TestSuite.runTest(TestSuite.java:208)
+ at junit.framework.TestSuite.run(TestSuite.java:203)
+ at junit.textui.TestRunner.doRun(TestRunner.java:116)
+</log4j:throwable>
+</log4j:event>
+<log4j:event xmlns:log4j="http://jakarta.apache.org/log4j/" logger="root" time="2007-08-15T16:42:32.312Z" timestamp="1187196152312" level="ERROR" thread="main">
+<log4j:message>Message 6</log4j:message>
+<log4j:throwable>java.lang.Exception: Just testing
+ at org.apache.log4j.xml.XSLTLayoutTestCase.common(XSLTLayoutTestCase.java:201)
+ at org.apache.log4j.xml.XSLTLayoutTestCase.testBasic(XSLTLayoutTestCase.java:65)
+ at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
+ at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
+ at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
+ at java.lang.reflect.Method.invoke(Method.java:585)
+ at junit.framework.TestCase.runTest(TestCase.java:154)
+ at junit.framework.TestCase.runBare(TestCase.java:127)
+ at junit.framework.TestResult$1.protect(TestResult.java:106)
+ at junit.framework.TestResult.runProtected(TestResult.java:124)
+ at junit.framework.TestResult.run(TestResult.java:109)
+ at junit.framework.TestCase.run(TestCase.java:118)
+ at junit.framework.TestSuite.runTest(TestSuite.java:208)
+ at junit.framework.TestSuite.run(TestSuite.java:203)
+ at junit.textui.TestRunner.doRun(TestRunner.java:116)
+</log4j:throwable>
+</log4j:event>