Final tidy up of addition of other receivers from log4j 1.3.
Reviewed by: Curt and Scott
git-svn-id: https://svn.apache.org/repos/asf/logging/sandbox/log4j/receivers@530249 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/pom.xml b/pom.xml
index 5da003c..bf9fd1c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -116,7 +116,12 @@
<groupId>oro</groupId>
<artifactId>oro</artifactId>
<version>2.0.8</version>
- </dependency>
+ </dependency>
+ <dependency>
+ <groupId>javax.jms</groupId>
+ <artifactId>jms</artifactId>
+ <version>1.1</version>
+ </dependency>
</dependencies>
<reporting>
<plugins>
diff --git a/src/main/java/org/apache/log4j/db/ConnectionSource.java b/src/main/java/org/apache/log4j/db/ConnectionSource.java
new file mode 100644
index 0000000..0578734
--- /dev/null
+++ b/src/main/java/org/apache/log4j/db/ConnectionSource.java
@@ -0,0 +1,69 @@
+/*
+ * 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.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/db/ConnectionSourceSkeleton.java b/src/main/java/org/apache/log4j/db/ConnectionSourceSkeleton.java
new file mode 100644
index 0000000..fd4b014
--- /dev/null
+++ b/src/main/java/org/apache/log4j/db/ConnectionSourceSkeleton.java
@@ -0,0 +1,110 @@
+/*
+ * 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.db.dialect.Util;
+import org.apache.log4j.spi.ComponentBase;
+
+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 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() {
+ try {
+ Connection connection = getConnection();
+ if (connection == null) {
+ getLogger().warn("Could not get a conneciton");
+ return;
+ }
+ DatabaseMetaData meta = connection.getMetaData();
+ Util util = new Util();
+ util.setLoggerRepository(repository);
+ 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);
+ }
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Does this connection support batch updates?
+ */
+ public final boolean supportsBatchUpdates() {
+ return supportsBatchUpdates;
+ }
+}
diff --git a/src/main/java/org/apache/log4j/db/CustomSQLDBReceiver.java b/src/main/java/org/apache/log4j/db/CustomSQLDBReceiver.java
new file mode 100644
index 0000000..9e0a9b8
--- /dev/null
+++ b/src/main/java/org/apache/log4j/db/CustomSQLDBReceiver.java
@@ -0,0 +1,447 @@
+/*
+ * 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 java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Hashtable;
+import java.util.StringTokenizer;
+
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.apache.log4j.plugins.Pauseable;
+import org.apache.log4j.plugins.Receiver;
+import org.apache.log4j.scheduler.Job;
+import org.apache.log4j.scheduler.Scheduler;
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.spi.LoggerRepositoryEx;
+import org.apache.log4j.spi.ThrowableInformation;
+import org.apache.log4j.spi.LocationInfo;
+
+/**
+ * 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, EXCEPTION
+ * <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:
+ * <p>
+ * select logger as LOGGER, timestamp as TIMESTAMP, level as LEVEL, thread as
+ * THREAD, message as MESSAGE, ndc as NDC, mdc as MDC, class as CLASS, method as
+ * METHOD, file as FILE, line as LINE,
+ * concat("{{application,databaselogs,hostname,mymachine, log4jid,",
+ * COUNTER,"}}") as PROPERTIES, "" as EXCEPTION from logtable
+ * <p>
+ * @author Scott Deboy <sdeboy@apache.org>
+ * <p>
+ */
+public class CustomSQLDBReceiver extends Receiver implements Pauseable {
+
+ 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 Job 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(Connection connection) {
+ 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() {
+ Connection connection = null;
+
+ 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[] exception = 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());
+ }
+ }
+
+ exception = new String[] { rs.getString("EXCEPTION") };
+ 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 name = tok2.nextToken();
+ String value = tok2.nextToken();
+ if (name.equals(LOG4J_ID_KEY)) {
+ try {
+ int thisInt = Integer.parseInt(value);
+ value = String.valueOf(thisInt);
+ if (thisInt > lastID) {
+ lastID = thisInt;
+ }
+ } catch (Exception e) {
+ }
+ }
+ properties.put(name, value);
+ }
+ }
+
+ Level levelImpl = Level.toLevel(level);
+
+
+ LocationInfo locationInfo = new LocationInfo(fileName,
+ className, methodName, lineNumber);
+
+ ThrowableInformation throwableInfo = new ThrowableInformation(
+ exception);
+
+ 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(connection);
+ }
+
+ // if paused, loop prior to executing sql query
+ synchronized (this) {
+ while (isPaused()) {
+ try {
+ wait(1000);
+ } catch (InterruptedException ie) {
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/org/apache/log4j/db/DBAppender.java b/src/main/java/org/apache/log4j/db/DBAppender.java
new file mode 100644
index 0000000..e8bbf5d
--- /dev/null
+++ b/src/main/java/org/apache/log4j/db/DBAppender.java
@@ -0,0 +1,406 @@
+/*
+ * 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.db.dialect.SQLDialect;
+import org.apache.log4j.db.dialect.Util;
+import org.apache.log4j.helpers.LogLog;
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.spi.LocationInfo;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+import java.util.Iterator;
+import java.util.Set;
+import java.lang.reflect.*;
+
+
+/**
+ * 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.joran.JoranConfigurator JoranConfigurator}. Example
+ * scripts can be found in the <em>tests/input/db</em> directory.
+ *
+ * @author Ceki Gülcü
+ * @author Ray DeCampo
+ * @since 1.3
+ */
+public class DBAppender extends AppenderSkeleton {
+ 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() {
+ getLogger().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) {
+ getLogger().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 =
+ 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) {
+ getLogger().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) {
+ getLogger().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) {
+ getLogger().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) {
+ getLogger().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;
+ }
+
+ /** Here Be Dragons.
+ * This code is necessary because this class originally came from the log4j 1.3 area, before being backported to 1.3
+ * In 1.3, Appenders had their own logger, but 1.2 does not have this, so instead we embed a 'compatible' mechanism here
+ * The goal was to keep the code as similar as possible, to avoid as many conflicts as possble.
+ * Code suggested by Curt Arnold (and not Curty Arnold... ;) )
+ */
+
+ private static final class LogLogger {
+ public void debug(final String msg) {
+ LogLog.debug(msg);
+ }
+ public void warn(final String msg, final Throwable ex) {
+ LogLog.warn(msg, ex);
+ }
+ public void warn(final String msg) {
+ LogLog.warn(msg);
+ }
+ public void error(final String msg, final Throwable ex) {
+ LogLog.error(msg, ex);
+ }
+ }
+
+ private static final LogLogger getLogger() {
+ return new LogLogger();
+ }
+
+}
diff --git a/src/main/java/org/apache/log4j/db/DBHelper.java b/src/main/java/org/apache/log4j/db/DBHelper.java
new file mode 100644
index 0000000..b400506
--- /dev/null
+++ b/src/main/java/org/apache/log4j/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.db;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Set;
+
+import org.apache.log4j.spi.LoggingEvent;
+
+/**
+ * @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/db/DBReceiver.java b/src/main/java/org/apache/log4j/db/DBReceiver.java
new file mode 100644
index 0000000..69effde
--- /dev/null
+++ b/src/main/java/org/apache/log4j/db/DBReceiver.java
@@ -0,0 +1,118 @@
+/*
+ * 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.plugins.Pauseable;
+import org.apache.log4j.plugins.Receiver;
+import org.apache.log4j.scheduler.Scheduler;
+import org.apache.log4j.spi.LoggerRepository;
+import org.apache.log4j.spi.LoggerRepositoryEx;
+
+/**
+ *
+ * @author Scott Deboy <sdeboy@apache.org>
+ * @author Ceki Gülcü
+ *
+ */
+public class DBReceiver extends Receiver implements Pauseable {
+ /**
+ * 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;
+ }
+}
diff --git a/src/main/java/org/apache/log4j/db/DBReceiverJob.java b/src/main/java/org/apache/log4j/db/DBReceiverJob.java
new file mode 100644
index 0000000..d21f1dc
--- /dev/null
+++ b/src/main/java/org/apache/log4j/db/DBReceiverJob.java
@@ -0,0 +1,234 @@
+/*
+ * 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.Level;
+import org.apache.log4j.Logger;
+import org.apache.log4j.helpers.Constants;
+import org.apache.log4j.scheduler.Job;
+import org.apache.log4j.spi.ComponentBase;
+import org.apache.log4j.spi.LoggerRepository;
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.spi.ThrowableInformation;
+import org.apache.log4j.spi.LocationInfo;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+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 = 0;
+
+ 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(Constants.LOG4J_ID_KEY, 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
+ * @param event
+ * @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/db/DataSourceConnectionSource.java b/src/main/java/org/apache/log4j/db/DataSourceConnectionSource.java
new file mode 100644
index 0000000..1994bd0
--- /dev/null
+++ b/src/main/java/org/apache/log4j/db/DataSourceConnectionSource.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.db;
+
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+import javax.sql.DataSource;
+
+
+/**
+ * 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 {
+
+ 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;
+ }
+
+
+}
diff --git a/src/main/java/org/apache/log4j/db/DriverManagerConnectionSource.java b/src/main/java/org/apache/log4j/db/DriverManagerConnectionSource.java
new file mode 100644
index 0000000..9bfdf65
--- /dev/null
+++ b/src/main/java/org/apache/log4j/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.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/db/JNDIConnectionSource.java b/src/main/java/org/apache/log4j/db/JNDIConnectionSource.java
new file mode 100644
index 0000000..7073738
--- /dev/null
+++ b/src/main/java/org/apache/log4j/db/JNDIConnectionSource.java
@@ -0,0 +1,143 @@
+/*
+ * 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 java.sql.Connection;
+import java.sql.SQLException;
+
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+
+// PortableRemoteObject was introduced in JDK 1.3. We won't use it.
+// import javax.rmi.PortableRemoteObject;
+import javax.sql.DataSource;
+
+
+/**
+ * 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/db/dialect/HSQLDBDialect.java b/src/main/java/org/apache/log4j/db/dialect/HSQLDBDialect.java
new file mode 100644
index 0000000..b558cd3
--- /dev/null
+++ b/src/main/java/org/apache/log4j/db/dialect/HSQLDBDialect.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed 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.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/db/dialect/MsSQLDialect.java b/src/main/java/org/apache/log4j/db/dialect/MsSQLDialect.java
new file mode 100644
index 0000000..9b94e72
--- /dev/null
+++ b/src/main/java/org/apache/log4j/db/dialect/MsSQLDialect.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed 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.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/db/dialect/MySQLDialect.java b/src/main/java/org/apache/log4j/db/dialect/MySQLDialect.java
new file mode 100644
index 0000000..f61db19
--- /dev/null
+++ b/src/main/java/org/apache/log4j/db/dialect/MySQLDialect.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed 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.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/db/dialect/OracleDialect.java b/src/main/java/org/apache/log4j/db/dialect/OracleDialect.java
new file mode 100644
index 0000000..c040413
--- /dev/null
+++ b/src/main/java/org/apache/log4j/db/dialect/OracleDialect.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed 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.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/db/dialect/PostgreSQLDialect.java b/src/main/java/org/apache/log4j/db/dialect/PostgreSQLDialect.java
new file mode 100644
index 0000000..0eff123
--- /dev/null
+++ b/src/main/java/org/apache/log4j/db/dialect/PostgreSQLDialect.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed 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.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/db/dialect/SQLDialect.java b/src/main/java/org/apache/log4j/db/dialect/SQLDialect.java
new file mode 100644
index 0000000..b24e5aa
--- /dev/null
+++ b/src/main/java/org/apache/log4j/db/dialect/SQLDialect.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed 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.dialect;
+
+/**
+ * @author ceki
+ *
+ */
+public interface SQLDialect {
+
+ public String getSelectInsertId();
+
+}
diff --git a/src/main/java/org/apache/log4j/db/dialect/Util.java b/src/main/java/org/apache/log4j/db/dialect/Util.java
new file mode 100644
index 0000000..05f5ba3
--- /dev/null
+++ b/src/main/java/org/apache/log4j/db/dialect/Util.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 1999,2006 The Apache Software Foundation.
+ *
+ * Licensed 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.dialect;
+
+import org.apache.log4j.db.ConnectionSource;
+import org.apache.log4j.spi.ComponentBase;
+
+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/db/dialect/db2.sql b/src/main/java/org/apache/log4j/db/dialect/db2.sql
new file mode 100644
index 0000000..1ff655d
--- /dev/null
+++ b/src/main/java/org/apache/log4j/db/dialect/db2.sql
@@ -0,0 +1,49 @@
+# 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/db/dialect/db2l.sql b/src/main/java/org/apache/log4j/db/dialect/db2l.sql
new file mode 100644
index 0000000..c3dd3fe
--- /dev/null
+++ b/src/main/java/org/apache/log4j/db/dialect/db2l.sql
@@ -0,0 +1,47 @@
+# 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/db/dialect/hsqldb.sql b/src/main/java/org/apache/log4j/db/dialect/hsqldb.sql
new file mode 100644
index 0000000..cc841f5
--- /dev/null
+++ b/src/main/java/org/apache/log4j/db/dialect/hsqldb.sql
@@ -0,0 +1,46 @@
+// 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/db/dialect/mssql.sql b/src/main/java/org/apache/log4j/db/dialect/mssql.sql
new file mode 100644
index 0000000..d041b34
--- /dev/null
+++ b/src/main/java/org/apache/log4j/db/dialect/mssql.sql
@@ -0,0 +1,45 @@
+-- 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/db/dialect/mysql.sql b/src/main/java/org/apache/log4j/db/dialect/mysql.sql
new file mode 100644
index 0000000..34910e6
--- /dev/null
+++ b/src/main/java/org/apache/log4j/db/dialect/mysql.sql
@@ -0,0 +1,54 @@
+# 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;
\ No newline at end of file
diff --git a/src/main/java/org/apache/log4j/db/dialect/oracle.sql b/src/main/java/org/apache/log4j/db/dialect/oracle.sql
new file mode 100644
index 0000000..81fe9c5
--- /dev/null
+++ b/src/main/java/org/apache/log4j/db/dialect/oracle.sql
@@ -0,0 +1,61 @@
+-- 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)
+ );
+
\ No newline at end of file
diff --git a/src/main/java/org/apache/log4j/db/dialect/postgresql.sql b/src/main/java/org/apache/log4j/db/dialect/postgresql.sql
new file mode 100644
index 0000000..464a6ac
--- /dev/null
+++ b/src/main/java/org/apache/log4j/db/dialect/postgresql.sql
@@ -0,0 +1,48 @@
+# 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/db/package.html b/src/main/java/org/apache/log4j/db/package.html
new file mode 100644
index 0000000..805ed88
--- /dev/null
+++ b/src/main/java/org/apache/log4j/db/package.html
@@ -0,0 +1,20 @@
+
+<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/net/JMSReceiver.java b/src/main/java/org/apache/log4j/net/JMSReceiver.java
new file mode 100644
index 0000000..b26541a
--- /dev/null
+++ b/src/main/java/org/apache/log4j/net/JMSReceiver.java
@@ -0,0 +1,302 @@
+/*
+ * 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.net;
+
+import java.io.FileInputStream;
+import java.util.Properties;
+
+import javax.jms.Message;
+import javax.jms.MessageListener;
+import javax.jms.TopicConnection;
+import javax.jms.Topic;
+import javax.jms.TopicConnectionFactory;
+import javax.jms.TopicSubscriber;
+import javax.jms.Session;
+import javax.jms.TopicSession;
+import javax.jms.ObjectMessage;
+
+import javax.naming.InitialContext;
+import javax.naming.Context;
+import javax.naming.NameNotFoundException;
+import javax.naming.NamingException;
+
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.plugins.Plugin;
+import org.apache.log4j.plugins.Receiver;
+
+/**
+ 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
+ @since 1.3
+*/
+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/net/JMSReceiverBeanInfo.java b/src/main/java/org/apache/log4j/net/JMSReceiverBeanInfo.java
new file mode 100644
index 0000000..eec19d3
--- /dev/null
+++ b/src/main/java/org/apache/log4j/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.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/net/MulticastReceiver.java b/src/main/java/org/apache/log4j/net/MulticastReceiver.java
new file mode 100644
index 0000000..4788b03
--- /dev/null
+++ b/src/main/java/org/apache/log4j/net/MulticastReceiver.java
@@ -0,0 +1,256 @@
+/*
+ * 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.net;
+
+import org.apache.log4j.plugins.Pauseable;
+import org.apache.log4j.plugins.Receiver;
+import org.apache.log4j.spi.Decoder;
+import org.apache.log4j.spi.LoggingEvent;
+
+import java.io.IOException;
+
+import java.net.DatagramPacket;
+import java.net.InetAddress;
+import java.net.MulticastSocket;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+
+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 boolean isActive = false;
+ 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;
+
+ 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() {
+ isActive = false;
+ handlerThread.interrupt();
+ receiverThread.interrupt();
+ socket.close();
+ }
+
+ public void setAddress(String address) {
+ this.address = address;
+ }
+
+ public boolean isPaused() {
+ return paused;
+ }
+
+ public void setPaused(boolean b) {
+ paused = b;
+ }
+
+ /**
+ Returns true if this receiver is active. */
+ public synchronized boolean isActive() {
+ return isActive;
+ }
+
+ 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 {
+ isActive = true;
+ socket = new MulticastSocket(port);
+ socket.joinGroup(addr);
+ receiverThread = new MulticastReceiverThread();
+ receiverThread.start();
+ handlerThread = new MulticastHandlerThread();
+ handlerThread.start();
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ }
+ }
+
+ 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() {
+ isActive = true;
+
+ byte[] b = new byte[PACKET_LENGTH];
+ DatagramPacket p = new DatagramPacket(b, b.length);
+
+ while (isActive) {
+ 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/net/MulticastReceiverBeanInfo.java b/src/main/java/org/apache/log4j/net/MulticastReceiverBeanInfo.java
new file mode 100644
index 0000000..32ff8df
--- /dev/null
+++ b/src/main/java/org/apache/log4j/net/MulticastReceiverBeanInfo.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.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),
+ };
+ } catch (Exception e) {
+ }
+
+ return null;
+ }
+}
diff --git a/src/main/java/org/apache/log4j/net/UDPReceiver.java b/src/main/java/org/apache/log4j/net/UDPReceiver.java
new file mode 100644
index 0000000..3783fc7
--- /dev/null
+++ b/src/main/java/org/apache/log4j/net/UDPReceiver.java
@@ -0,0 +1,253 @@
+/*
+ * 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.net;
+
+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;
+
+import org.apache.log4j.plugins.Pauseable;
+import org.apache.log4j.plugins.Receiver;
+import org.apache.log4j.spi.Decoder;
+import org.apache.log4j.spi.LoggingEvent;
+
+
+/**
+ * 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;
+
+ 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 synchronized void shutdown() {
+ if(closed == true) {
+ return;
+ }
+ closed = true;
+ // Closing the datagram socket will unblock the UDPReceiverThread if it is
+ // was waiting to receive data from the socket.
+ socket.close();
+
+ 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();
+ } 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();
+ }
+
+ 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/net/XMLSocketNode.java b/src/main/java/org/apache/log4j/net/XMLSocketNode.java
new file mode 100644
index 0000000..86718b1
--- /dev/null
+++ b/src/main/java/org/apache/log4j/net/XMLSocketNode.java
@@ -0,0 +1,210 @@
+/*
+ * 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.net;
+
+import org.apache.log4j.*;
+import org.apache.log4j.helpers.Constants;
+import org.apache.log4j.plugins.Receiver;
+import org.apache.log4j.spi.*;
+
+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 remoteInfo =
+ socket.getInetAddress().getHostName() + ":" + 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();
+
+ //if machinename property was not set (the case if properties
+ //not supported by the DTD), use remoteinfo as machine name
+ if (e.getProperty(Constants.HOSTNAME_KEY) == null) {
+ e.setProperty(Constants.HOSTNAME_KEY, remoteInfo);
+ }
+
+ // 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/net/XMLSocketReceiver.java b/src/main/java/org/apache/log4j/net/XMLSocketReceiver.java
new file mode 100644
index 0000000..17a0041
--- /dev/null
+++ b/src/main/java/org/apache/log4j/net/XMLSocketReceiver.java
@@ -0,0 +1,275 @@
+/*
+ * 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.net;
+
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.List;
+import java.util.Vector;
+
+import org.apache.log4j.plugins.Pauseable;
+import org.apache.log4j.plugins.Plugin;
+import org.apache.log4j.plugins.Receiver;
+import org.apache.log4j.spi.LoggerRepository;
+import org.apache.log4j.spi.LoggingEvent;
+
+
+/**
+ 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>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>
+
+ @since 1.3
+*/
+public class XMLSocketReceiver extends Receiver implements Runnable, PortBased, Pauseable {
+ protected boolean active = false;
+ 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;
+
+ 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;
+ }
+
+ 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));
+ }
+
+ /**
+ Returns true if this receiver is active. */
+ public synchronized boolean isActive() {
+ return active;
+ }
+
+ /**
+ Starts the SocketReceiver with the current options. */
+ public void activateOptions() {
+ if (!isActive()) {
+ rThread = new Thread(this);
+ rThread.setDaemon(true);
+ rThread.start();
+ active = true;
+ }
+ }
+
+ /**
+ 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();
+ }
+
+ /**
+ * 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);
+ }
+ }
+
+
+}