blob: e1e5658c2f8839fa8fea0b59a0c010d1faefd7ed [file] [log] [blame]
/*
* 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.net;
import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.spi.LoggingEvent;
import java.util.Properties;
import javax.jms.ObjectMessage;
import javax.jms.Session;
import javax.jms.Topic;
import javax.jms.TopicConnection;
import javax.jms.TopicConnectionFactory;
import javax.jms.TopicPublisher;
import javax.jms.TopicSession;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NameNotFoundException;
import javax.naming.NamingException;
/**
* A simple appender that publishes events to a JMS Topic. The events
* are serialized and transmitted as JMS message type {@link
* javax.jms.ObjectMessage}.
* <p>JMS {@link javax.jms.Topic topics} and {@link javax.jms.TopicConnectionFactory topic
* connection factories} are administered objects that are retrieved
* using JNDI messaging which in turn requires the retreival of a JNDI
* {@link Context}.
* <p>There are two common methods for retrieving a JNDI {@link
* Context}. If a file resource named <em>jndi.properties</em> is
* available to the JNDI API, it will use the information found
* therein to retrieve an initial JNDI context. To obtain an initial
* context, your code will simply call:
<pre>
InitialContext jndiContext = new InitialContext();
</pre>
* <p>Calling the no-argument <code>InitialContext()</code> method
* will also work from within Enterprise Java Beans (EJBs) because it
* is part of the EJB contract for application servers to provide each
* bean an environment naming context (ENC).
* <p>In the second approach, several predetermined properties are set
* and these properties are passed to the <code>InitialContext</code>
* contructor to connect to the naming service provider. For example,
* to connect to JBoss naming service one would write:
<pre>
Properties env = new Properties( );
env.put(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory");
env.put(Context.PROVIDER_URL, "jnp://hostname:1099");
env.put(Context.URL_PKG_PREFIXES, "org.jboss.naming:org.jnp.interfaces");
InitialContext jndiContext = new InitialContext(env);
</pre>
* where <em>hostname</em> is the host where the JBoss applicaiton
* server is running.
*
* <p>To connect to the the naming service of Weblogic application
* server one would write:
<pre>
Properties env = new Properties( );
env.put(Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory");
env.put(Context.PROVIDER_URL, "t3://localhost:7001");
InitialContext jndiContext = new InitialContext(env);
</pre>
* <p>Other JMS providers will obviously require different values.
*
* The initial JNDI context can be obtained by calling the
* no-argument <code>InitialContext()</code> method in EJBs. Only
* clients running in a separate JVM need to be concerned about the
* <em>jndi.properties</em> file and calling {@link
* InitialContext#InitialContext()} or alternatively correctly
* setting the different properties before calling {@link
* InitialContext#InitialContext(java.util.Hashtable)} method.
@author Ceki G&uuml;lc&uuml; */
public class JMSAppender extends AppenderSkeleton {
static int SUCCESSIVE_FAILURE_LIMIT = 3;
String securityPrincipalName;
String securityCredentials;
String initialContextFactoryName;
String urlPkgPrefixes;
String providerURL;
String topicBindingName;
String tcfBindingName;
String userName;
String password;
boolean locationInfo;
TopicConnection topicConnection;
TopicSession topicSession;
TopicPublisher topicPublisher;
boolean inOrder = false;
int successiveFailureCount = 0;
public JMSAppender() {
}
/**
The <b>TopicConnectionFactoryBindingName</b> option takes a
string value. Its value will be used to lookup the appropriate
<code>TopicConnectionFactory</code> from the JNDI context.
*/
public void setTopicConnectionFactoryBindingName(String tcfBindingName) {
this.tcfBindingName = tcfBindingName;
}
/**
Returns the value of the <b>TopicConnectionFactoryBindingName</b> option.
*/
public String getTopicConnectionFactoryBindingName() {
return tcfBindingName;
}
/**
The <b>TopicBindingName</b> option takes a
string value. Its value will be used to lookup the appropriate
<code>Topic</code> from the JNDI context.
*/
public void setTopicBindingName(String topicBindingName) {
this.topicBindingName = topicBindingName;
}
/**
Returns the value of the <b>TopicBindingName</b> option.
*/
public String getTopicBindingName() {
return topicBindingName;
}
/**
* Returns value of the <b>LocationInfo</b> property which determines whether
* caller's location info is sent to the remote subscriber.
* */
public boolean getLocationInfo() {
return locationInfo;
}
/**
* Options are activated and become effective only after calling
* this method.*/
public void activateOptions() {
TopicConnectionFactory topicConnectionFactory;
try {
Context jndi;
getLogger().debug("Getting initial context.");
if (initialContextFactoryName != null) {
Properties env = new Properties();
env.put(Context.INITIAL_CONTEXT_FACTORY, initialContextFactoryName);
if (providerURL != null) {
env.put(Context.PROVIDER_URL, providerURL);
} else {
getLogger().warn(
"You have set InitialContextFactoryName option but not the "
+ "ProviderURL. This is likely to cause problems.");
}
if (urlPkgPrefixes != null) {
env.put(Context.URL_PKG_PREFIXES, urlPkgPrefixes);
}
if (securityPrincipalName != null) {
env.put(Context.SECURITY_PRINCIPAL, securityPrincipalName);
if (securityCredentials != null) {
env.put(Context.SECURITY_CREDENTIALS, securityCredentials);
} else {
getLogger().warn(
"You have set SecurityPrincipalName option but not the "
+ "SecurityCredentials. This is likely to cause problems.");
}
}
jndi = new InitialContext(env);
} else {
jndi = new InitialContext();
}
getLogger().debug("Looking up [{}]", tcfBindingName);
topicConnectionFactory =
(TopicConnectionFactory) lookup(jndi, tcfBindingName);
getLogger().debug("About to create TopicConnection.");
if (userName != null) {
this.topicConnection =
topicConnectionFactory.createTopicConnection(userName, password);
} else {
this.topicConnection = topicConnectionFactory.createTopicConnection();
}
getLogger().debug(
"Creating TopicSession, non-transactional, "
+ "in AUTO_ACKNOWLEDGE mode.");
this.topicSession =
topicConnection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
getLogger().debug("Looking up topic name [" + topicBindingName + "].");
Topic topic = (Topic) lookup(jndi, topicBindingName);
getLogger().debug("Creating TopicPublisher.");
this.topicPublisher = topicSession.createPublisher(topic);
getLogger().debug("Starting TopicConnection.");
topicConnection.start();
jndi.close();
} catch (Exception e) {
getLogger().error(
"Error while activating options for appender named [" + name + "].", e);
}
if (this.topicConnection != null && this.topicSession != null && this.topicPublisher == null) {
inOrder = true;
} else {
inOrder = false;
}
}
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;
}
}
/**
Close this JMSAppender. Closing releases all resources used by the
appender. A closed appender cannot be re-opened. */
public synchronized void close() {
// The synchronized modifier avoids concurrent append and close operations
if (this.closed) {
return;
}
getLogger().debug("Closing appender [" + name + "].");
this.closed = true;
try {
if (topicSession != null) {
topicSession.close();
}
if (topicConnection != null) {
topicConnection.close();
}
} catch (Exception e) {
getLogger().error("Error while closing JMSAppender [" + name + "].", e);
}
// Help garbage collection
topicPublisher = null;
topicSession = null;
topicConnection = null;
}
/**
This method called by {@link AppenderSkeleton#doAppend} method to
do most of the real appending work. */
public void append(LoggingEvent event) {
if (!inOrder) {
return;
}
try {
ObjectMessage msg = topicSession.createObjectMessage();
if (locationInfo) {
event.getLocationInformation();
}
msg.setObject(event);
topicPublisher.publish(msg);
successiveFailureCount = 0;
} catch (Exception e) {
successiveFailureCount++;
if(successiveFailureCount > SUCCESSIVE_FAILURE_LIMIT) {
inOrder = false;
}
getLogger().error(
"Could not publish message in JMSAppender [" + name + "].", e);
}
}
/**
* Returns the value of the <b>InitialContextFactoryName</b> option.
* See {@link #setInitialContextFactoryName} for more details on the
* meaning of this option.
* */
public String getInitialContextFactoryName() {
return initialContextFactoryName;
}
/**
* Setting the <b>InitialContextFactoryName</b> method will cause
* this <code>JMSAppender</code> instance to use the {@link
* InitialContext#InitialContext(Hashtable)} method instead of the
* no-argument constructor. If you set this option, you should also
* at least set the <b>ProviderURL</b> option.
*
* <p>See also {@link #setProviderURL(String)}.
* */
public void setInitialContextFactoryName(String initialContextFactoryName) {
this.initialContextFactoryName = initialContextFactoryName;
}
public String getProviderURL() {
return providerURL;
}
public void setProviderURL(String providerURL) {
this.providerURL = providerURL;
}
String getURLPkgPrefixes() {
return urlPkgPrefixes;
}
public void setURLPkgPrefixes(String urlPkgPrefixes) {
this.urlPkgPrefixes = urlPkgPrefixes;
}
public String getSecurityCredentials() {
return securityCredentials;
}
public void setSecurityCredentials(String securityCredentials) {
this.securityCredentials = securityCredentials;
}
public String getSecurityPrincipalName() {
return securityPrincipalName;
}
public void setSecurityPrincipalName(String securityPrincipalName) {
this.securityPrincipalName = securityPrincipalName;
}
public String getUserName() {
return userName;
}
/**
* The user name to use when {@link
* javax.jms.TopicConnectionFactory#createTopicConnection(String, String)}
* creating a topic session}. If you set this option, you should
* also set the <b>Password</b> option. See {@link
* #setPassword(String)}.
* */
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
/**
* The paswword to use when creating a topic session.
*/
public void setPassword(String password) {
this.password = password;
}
/**
* If true, the information sent to the remote subscriber will include
* caller's location information. Due to performance concerns, by default no
* location information is sent to the subscriber.
* */
public void setLocationInfo(boolean locationInfo) {
this.locationInfo = locationInfo;
}
}