/* * 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.AppenderSkeleton; import org.apache.log4j.spi.LoggingEvent; import org.apache.log4j.spi.ErrorCode; import org.apache.log4j.helpers.LogLog; import java.util.Properties; import javax.jms.TopicConnection; import javax.jms.TopicConnectionFactory; import javax.jms.Topic; import javax.jms.TopicPublisher; import javax.jms.TopicSession; import javax.jms.Session; import javax.jms.ObjectMessage; import javax.naming.InitialContext; import javax.naming.Context; 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 * ObjectMessage}. *
JMS {@link Topic topics} and {@link TopicConnectionFactory topic * connection factories} are administered objects that are retrieved * using JNDI messaging which in turn requires the retreival of a JNDI * {@link Context}. *
There are two common methods for retrieving a JNDI {@link * Context}. If a file resource named jndi.properties 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:
InitialContext jndiContext = new InitialContext();*
Calling the no-argument InitialContext()
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).
*
In the second approach, several predetermined properties are set
* and these properties are passed to the InitialContext
* contructor to connect to the naming service provider. For example,
* to connect to JBoss naming service one would write:
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);* where hostname is the host where the JBoss applicaiton * server is running. * *
To connect to the the naming service of Weblogic application * server one would write:
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);*
Other JMS providers will obviously require different values.
*
* The initial JNDI context can be obtained by calling the
* no-argument InitialContext()
method in EJBs. Only
* clients running in a separate JVM need to be concerned about the
* jndi.properties 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ülcü */
public class JMSAppender extends AppenderSkeleton {
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;
public
JMSAppender() {
}
/**
The TopicConnectionFactoryBindingName option takes a
string value. Its value will be used to lookup the appropriate
TopicConnectionFactory
from the JNDI context.
*/
public
void setTopicConnectionFactoryBindingName(String tcfBindingName) {
this.tcfBindingName = tcfBindingName;
}
/**
Returns the value of the TopicConnectionFactoryBindingName option.
*/
public
String getTopicConnectionFactoryBindingName() {
return tcfBindingName;
}
/**
The TopicBindingName option takes a
string value. Its value will be used to lookup the appropriate
Topic
from the JNDI context.
*/
public
void setTopicBindingName(String topicBindingName) {
this.topicBindingName = topicBindingName;
}
/**
Returns the value of the TopicBindingName option.
*/
public
String getTopicBindingName() {
return topicBindingName;
}
/**
Returns value of the LocationInfo property which
determines whether location (stack) 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;
LogLog.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 {
LogLog.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 {
LogLog.warn("You have set SecurityPrincipalName option but not the "
+"SecurityCredentials. This is likely to cause problems.");
}
}
jndi = new InitialContext(env);
} else {
jndi = new InitialContext();
}
LogLog.debug("Looking up ["+tcfBindingName+"]");
topicConnectionFactory = (TopicConnectionFactory) lookup(jndi, tcfBindingName);
LogLog.debug("About to create TopicConnection.");
if(userName != null) {
topicConnection = topicConnectionFactory.createTopicConnection(userName,
password);
} else {
topicConnection = topicConnectionFactory.createTopicConnection();
}
LogLog.debug("Creating TopicSession, non-transactional, "
+"in AUTO_ACKNOWLEDGE mode.");
topicSession = topicConnection.createTopicSession(false,
Session.AUTO_ACKNOWLEDGE);
LogLog.debug("Looking up topic name ["+topicBindingName+"].");
Topic topic = (Topic) lookup(jndi, topicBindingName);
LogLog.debug("Creating TopicPublisher.");
topicPublisher = topicSession.createPublisher(topic);
LogLog.debug("Starting TopicConnection.");
topicConnection.start();
jndi.close();
} catch(Exception e) {
errorHandler.error("Error while activating options for appender named ["+name+
"].", e, ErrorCode.GENERIC_FAILURE);
}
}
protected Object lookup(Context ctx, String name) throws NamingException {
try {
return ctx.lookup(name);
} catch(NameNotFoundException e) {
LogLog.error("Could not find name ["+name+"].");
throw e;
}
}
protected boolean checkEntryConditions() {
String fail = null;
if(this.topicConnection == null) {
fail = "No TopicConnection";
} else if(this.topicSession == null) {
fail = "No TopicSession";
} else if(this.topicPublisher == null) {
fail = "No TopicPublisher";
}
if(fail != null) {
errorHandler.error(fail +" for JMSAppender named ["+name+"].");
return false;
} else {
return true;
}
}
/**
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;
LogLog.debug("Closing appender ["+name+"].");
this.closed = true;
try {
if(topicSession != null)
topicSession.close();
if(topicConnection != null)
topicConnection.close();
} catch(Exception e) {
LogLog.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(!checkEntryConditions()) {
return;
}
try {
ObjectMessage msg = topicSession.createObjectMessage();
if(locationInfo) {
event.getLocationInformation();
}
msg.setObject(event);
topicPublisher.publish(msg);
} catch(Exception e) {
errorHandler.error("Could not publish message in JMSAppender ["+name+"].", e,
ErrorCode.GENERIC_FAILURE);
}
}
/**
* Returns the value of the InitialContextFactoryName option.
* See {@link #setInitialContextFactoryName} for more details on the
* meaning of this option.
* */
public String getInitialContextFactoryName() {
return initialContextFactoryName;
}
/**
* Setting the InitialContextFactoryName method will cause
* this JMSAppender
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 ProviderURL option.
*
*
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 * TopicConnectionFactory#createTopicConnection(String, String) * creating a topic session}. If you set this option, you should * also set the Password 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. By default no location information is sent to the subscriber. */ public void setLocationInfo(boolean locationInfo) { this.locationInfo = locationInfo; } /** * Returns the TopicConnection used for this appender. Only valid after * activateOptions() method has been invoked. */ protected TopicConnection getTopicConnection() { return topicConnection; } /** * Returns the TopicSession used for this appender. Only valid after * activateOptions() method has been invoked. */ protected TopicSession getTopicSession() { return topicSession; } /** * Returns the TopicPublisher used for this appender. Only valid after * activateOptions() method has been invoked. */ protected TopicPublisher getTopicPublisher() { return topicPublisher; } /** * The JMSAppender sends serialized events and consequently does not * require a layout. */ public boolean requiresLayout() { return false; } }