2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
18 package org.apache.log4j.net;
20 import org.apache.log4j.AppenderSkeleton;
21 import org.apache.log4j.helpers.LogLog;
22 import org.apache.log4j.spi.ErrorCode;
23 import org.apache.log4j.spi.LoggingEvent;
25 import javax.jms.JMSException;
26 import javax.jms.ObjectMessage;
27 import javax.jms.Session;
28 import javax.jms.Topic;
29 import javax.jms.TopicConnection;
30 import javax.jms.TopicConnectionFactory;
31 import javax.jms.TopicPublisher;
32 import javax.jms.TopicSession;
33 import javax.naming.Context;
34 import javax.naming.InitialContext;
35 import javax.naming.NameNotFoundException;
36 import javax.naming.NamingException;
37 import java.util.Properties;
40 * A simple appender that publishes events to a JMS Topic. The events
41 * are serialized and transmitted as JMS message type {@link
44 * <p>JMS {@link Topic topics} and {@link TopicConnectionFactory topic
45 * connection factories} are administered objects that are retrieved
46 * using JNDI messaging which in turn requires the retrieval of a JNDI
49 * <p>There are two common methods for retrieving a JNDI {@link
50 * Context}. If a file resource named <em>jndi.properties</em> is
51 * available to the JNDI API, it will use the information found
52 * therein to retrieve an initial JNDI context. To obtain an initial
53 * context, your code will simply call:
56 InitialContext jndiContext = new InitialContext();
59 * <p>Calling the no-argument <code>InitialContext()</code> method
60 * will also work from within Enterprise Java Beans (EJBs) because it
61 * is part of the EJB contract for application servers to provide each
62 * bean an environment naming context (ENC).
64 * <p>In the second approach, several predetermined properties are set
65 * and these properties are passed to the <code>InitialContext</code>
66 * constructor to connect to the naming service provider. For example,
67 * to connect to JBoss naming service one would write:
70 Properties env = new Properties( );
71 env.put(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory");
72 env.put(Context.PROVIDER_URL, "jnp://hostname:1099");
73 env.put(Context.URL_PKG_PREFIXES, "org.jboss.naming:org.jnp.interfaces");
74 InitialContext jndiContext = new InitialContext(env);
77 * where <em>hostname</em> is the host where the JBoss application
80 * <p>To connect to the the naming service of Weblogic application
81 * server one would write:
84 Properties env = new Properties( );
85 env.put(Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory");
86 env.put(Context.PROVIDER_URL, "t3://localhost:7001");
87 InitialContext jndiContext = new InitialContext(env);
90 * <p>Other JMS providers will obviously require different values.
92 * The initial JNDI context can be obtained by calling the
93 * no-argument <code>InitialContext()</code> method in EJBs. Only
94 * clients running in a separate JVM need to be concerned about the
95 * <em>jndi.properties</em> file and calling {@link
96 * InitialContext#InitialContext()} or alternatively correctly
97 * setting the different properties before calling {@link
98 * InitialContext#InitialContext(java.util.Hashtable)} method.
101 @author Ceki Gülcü */
102 public class JMSAppender extends AppenderSkeleton {
104 String securityPrincipalName;
105 String securityCredentials;
106 String initialContextFactoryName;
107 String urlPkgPrefixes;
109 String topicBindingName;
110 String tcfBindingName;
113 boolean locationInfo;
115 TopicConnection topicConnection;
116 TopicSession topicSession;
117 TopicPublisher topicPublisher;
124 The <b>TopicConnectionFactoryBindingName</b> option takes a
125 string value. Its value will be used to lookup the appropriate
126 <code>TopicConnectionFactory</code> from the JNDI context.
129 void setTopicConnectionFactoryBindingName(String tcfBindingName) {
130 this.tcfBindingName = tcfBindingName;
134 Returns the value of the <b>TopicConnectionFactoryBindingName</b> option.
137 String getTopicConnectionFactoryBindingName() {
138 return tcfBindingName;
142 The <b>TopicBindingName</b> option takes a
143 string value. Its value will be used to lookup the appropriate
144 <code>Topic</code> from the JNDI context.
147 void setTopicBindingName(String topicBindingName) {
148 this.topicBindingName = topicBindingName;
152 Returns the value of the <b>TopicBindingName</b> option.
155 String getTopicBindingName() {
156 return topicBindingName;
161 Returns value of the <b>LocationInfo</b> property which
162 determines whether location (stack) info is sent to the remote
165 boolean getLocationInfo() {
170 * Options are activated and become effective only after calling
172 public void activateOptions() {
173 TopicConnectionFactory topicConnectionFactory;
178 LogLog.debug("Getting initial context.");
179 if(initialContextFactoryName != null) {
180 Properties env = new Properties( );
181 env.put(Context.INITIAL_CONTEXT_FACTORY, initialContextFactoryName);
182 if(providerURL != null) {
183 env.put(Context.PROVIDER_URL, providerURL);
185 LogLog.warn("You have set InitialContextFactoryName option but not the "
186 +"ProviderURL. This is likely to cause problems.");
188 if(urlPkgPrefixes != null) {
189 env.put(Context.URL_PKG_PREFIXES, urlPkgPrefixes);
192 if(securityPrincipalName != null) {
193 env.put(Context.SECURITY_PRINCIPAL, securityPrincipalName);
194 if(securityCredentials != null) {
195 env.put(Context.SECURITY_CREDENTIALS, securityCredentials);
197 LogLog.warn("You have set SecurityPrincipalName option but not the "
198 +"SecurityCredentials. This is likely to cause problems.");
201 jndi = new InitialContext(env);
203 jndi = new InitialContext();
206 LogLog.debug("Looking up ["+tcfBindingName+"]");
207 topicConnectionFactory = (TopicConnectionFactory) lookup(jndi, tcfBindingName);
208 LogLog.debug("About to create TopicConnection.");
209 if(userName != null) {
210 topicConnection = topicConnectionFactory.createTopicConnection(userName,
213 topicConnection = topicConnectionFactory.createTopicConnection();
216 LogLog.debug("Creating TopicSession, non-transactional, "
217 +"in AUTO_ACKNOWLEDGE mode.");
218 topicSession = topicConnection.createTopicSession(false,
219 Session.AUTO_ACKNOWLEDGE);
221 LogLog.debug("Looking up topic name ["+topicBindingName+"].");
222 Topic topic = (Topic) lookup(jndi, topicBindingName);
224 LogLog.debug("Creating TopicPublisher.");
225 topicPublisher = topicSession.createPublisher(topic);
227 LogLog.debug("Starting TopicConnection.");
228 topicConnection.start();
231 } catch(JMSException e) {
232 errorHandler.error("Error while activating options for appender named ["+name+
233 "].", e, ErrorCode.GENERIC_FAILURE);
234 } catch(NamingException e) {
235 errorHandler.error("Error while activating options for appender named ["+name+
236 "].", e, ErrorCode.GENERIC_FAILURE);
237 } catch(RuntimeException e) {
238 errorHandler.error("Error while activating options for appender named ["+name+
239 "].", e, ErrorCode.GENERIC_FAILURE);
243 protected Object lookup(Context ctx, String name) throws NamingException {
245 return ctx.lookup(name);
246 } catch(NameNotFoundException e) {
247 LogLog.error("Could not find name ["+name+"].");
252 protected boolean checkEntryConditions() {
255 if(this.topicConnection == null) {
256 fail = "No TopicConnection";
257 } else if(this.topicSession == null) {
258 fail = "No TopicSession";
259 } else if(this.topicPublisher == null) {
260 fail = "No TopicPublisher";
264 errorHandler.error(fail +" for JMSAppender named ["+name+"].");
272 Close this JMSAppender. Closing releases all resources used by the
273 appender. A closed appender cannot be re-opened. */
274 public synchronized void close() {
275 // The synchronized modifier avoids concurrent append and close operations
281 LogLog.debug("Closing appender ["+name+"].");
285 if(topicSession != null) {
286 topicSession.close();
288 if(topicConnection != null) {
289 topicConnection.close();
291 } catch(JMSException e) {
292 LogLog.error("Error while closing JMSAppender ["+name+"].", e);
293 } catch(RuntimeException e) {
294 LogLog.error("Error while closing JMSAppender ["+name+"].", e);
296 // Help garbage collection
297 topicPublisher = null;
299 topicConnection = null;
303 This method called by {@link AppenderSkeleton#doAppend} method to
304 do most of the real appending work. */
305 public void append(LoggingEvent event) {
306 if(!checkEntryConditions()) {
311 ObjectMessage msg = topicSession.createObjectMessage();
313 event.getLocationInformation();
315 msg.setObject(event);
316 topicPublisher.publish(msg);
317 } catch(JMSException e) {
318 errorHandler.error("Could not publish message in JMSAppender ["+name+"].", e,
319 ErrorCode.GENERIC_FAILURE);
320 } catch(RuntimeException e) {
321 errorHandler.error("Could not publish message in JMSAppender ["+name+"].", e,
322 ErrorCode.GENERIC_FAILURE);
327 * Returns the value of the <b>InitialContextFactoryName</b> option.
328 * See {@link #setInitialContextFactoryName} for more details on the
329 * meaning of this option.
331 public String getInitialContextFactoryName() {
332 return initialContextFactoryName;
336 * Setting the <b>InitialContextFactoryName</b> method will cause
337 * this <code>JMSAppender</code> instance to use the {@link
338 * InitialContext#InitialContext(Hashtable)} method instead of the
339 * no-argument constructor. If you set this option, you should also
340 * at least set the <b>ProviderURL</b> option.
342 * @see #setProviderURL(String)
344 public void setInitialContextFactoryName(String initialContextFactoryName) {
345 this.initialContextFactoryName = initialContextFactoryName;
348 public String getProviderURL() {
352 public void setProviderURL(String providerURL) {
353 this.providerURL = providerURL;
356 String getURLPkgPrefixes( ) {
357 return urlPkgPrefixes;
360 public void setURLPkgPrefixes(String urlPkgPrefixes ) {
361 this.urlPkgPrefixes = urlPkgPrefixes;
364 public String getSecurityCredentials() {
365 return securityCredentials;
368 public void setSecurityCredentials(String securityCredentials) {
369 this.securityCredentials = securityCredentials;
373 public String getSecurityPrincipalName() {
374 return securityPrincipalName;
377 public void setSecurityPrincipalName(String securityPrincipalName) {
378 this.securityPrincipalName = securityPrincipalName;
381 public String getUserName() {
386 * The user name to use when {@link
387 * TopicConnectionFactory#createTopicConnection(String, String)
388 * creating a topic session}. If you set this option, you should
389 * also set the <b>Password</b> option. See {@link
390 * #setPassword(String)}.
392 public void setUserName(String userName) {
393 this.userName = userName;
396 public String getPassword() {
401 * The paswword to use when creating a topic session.
403 public void setPassword(String password) {
404 this.password = password;
409 If true, the information sent to the remote subscriber will
410 include caller's location information. By default no location
411 information is sent to the subscriber. */
412 public void setLocationInfo(boolean locationInfo) {
413 this.locationInfo = locationInfo;
417 * Returns the TopicConnection used for this appender. Only valid after
418 * activateOptions() method has been invoked.
420 protected TopicConnection getTopicConnection() {
421 return topicConnection;
425 * Returns the TopicSession used for this appender. Only valid after
426 * activateOptions() method has been invoked.
428 protected TopicSession getTopicSession() {
433 * Returns the TopicPublisher used for this appender. Only valid after
434 * activateOptions() method has been invoked.
436 protected TopicPublisher getTopicPublisher() {
437 return topicPublisher;
441 * The JMSAppender sends serialized events and consequently does not
444 public boolean requiresLayout() {