1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.log4j.net;
19
20 import org.apache.log4j.AppenderSkeleton;
21 import org.apache.log4j.spi.LoggingEvent;
22 import org.apache.log4j.spi.ErrorCode;
23 import org.apache.log4j.helpers.LogLog;
24
25 import java.util.Properties;
26 import javax.jms.TopicConnection;
27 import javax.jms.TopicConnectionFactory;
28 import javax.jms.Topic;
29 import javax.jms.TopicPublisher;
30 import javax.jms.TopicSession;
31 import javax.jms.Session;
32 import javax.jms.ObjectMessage;
33 import javax.naming.InitialContext;
34 import javax.naming.Context;
35 import javax.naming.NameNotFoundException;
36 import javax.naming.NamingException;
37
38 /***
39 * A simple appender that publishes events to a JMS Topic. The events
40 * are serialized and transmitted as JMS message type {@link
41 * ObjectMessage}.
42
43 * <p>JMS {@link Topic topics} and {@link TopicConnectionFactory topic
44 * connection factories} are administered objects that are retrieved
45 * using JNDI messaging which in turn requires the retreival of a JNDI
46 * {@link Context}.
47
48 * <p>There are two common methods for retrieving a JNDI {@link
49 * Context}. If a file resource named <em>jndi.properties</em> is
50 * available to the JNDI API, it will use the information found
51 * therein to retrieve an initial JNDI context. To obtain an initial
52 * context, your code will simply call:
53
54 <pre>
55 InitialContext jndiContext = new InitialContext();
56 </pre>
57
58 * <p>Calling the no-argument <code>InitialContext()</code> method
59 * will also work from within Enterprise Java Beans (EJBs) because it
60 * is part of the EJB contract for application servers to provide each
61 * bean an environment naming context (ENC).
62
63 * <p>In the second approach, several predetermined properties are set
64 * and these properties are passed to the <code>InitialContext</code>
65 * contructor to connect to the naming service provider. For example,
66 * to connect to JBoss naming service one would write:
67
68 <pre>
69 Properties env = new Properties( );
70 env.put(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory");
71 env.put(Context.PROVIDER_URL, "jnp://hostname:1099");
72 env.put(Context.URL_PKG_PREFIXES, "org.jboss.naming:org.jnp.interfaces");
73 InitialContext jndiContext = new InitialContext(env);
74 </pre>
75
76 * where <em>hostname</em> is the host where the JBoss applicaiton
77 * server is running.
78 *
79 * <p>To connect to the the naming service of Weblogic application
80 * server one would write:
81
82 <pre>
83 Properties env = new Properties( );
84 env.put(Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory");
85 env.put(Context.PROVIDER_URL, "t3://localhost:7001");
86 InitialContext jndiContext = new InitialContext(env);
87 </pre>
88
89 * <p>Other JMS providers will obviously require different values.
90 *
91 * The initial JNDI context can be obtained by calling the
92 * no-argument <code>InitialContext()</code> method in EJBs. Only
93 * clients running in a separate JVM need to be concerned about the
94 * <em>jndi.properties</em> file and calling {@link
95 * InitialContext#InitialContext()} or alternatively correctly
96 * setting the different properties before calling {@link
97 * InitialContext#InitialContext(java.util.Hashtable)} method.
98
99
100 @author Ceki Gülcü */
101 public class JMSAppender extends AppenderSkeleton {
102
103 String securityPrincipalName;
104 String securityCredentials;
105 String initialContextFactoryName;
106 String urlPkgPrefixes;
107 String providerURL;
108 String topicBindingName;
109 String tcfBindingName;
110 String userName;
111 String password;
112 boolean locationInfo;
113
114 TopicConnection topicConnection;
115 TopicSession topicSession;
116 TopicPublisher topicPublisher;
117
118 public
119 JMSAppender() {
120 }
121
122 /***
123 The <b>TopicConnectionFactoryBindingName</b> option takes a
124 string value. Its value will be used to lookup the appropriate
125 <code>TopicConnectionFactory</code> from the JNDI context.
126 */
127 public
128 void setTopicConnectionFactoryBindingName(String tcfBindingName) {
129 this.tcfBindingName = tcfBindingName;
130 }
131
132 /***
133 Returns the value of the <b>TopicConnectionFactoryBindingName</b> option.
134 */
135 public
136 String getTopicConnectionFactoryBindingName() {
137 return tcfBindingName;
138 }
139
140 /***
141 The <b>TopicBindingName</b> option takes a
142 string value. Its value will be used to lookup the appropriate
143 <code>Topic</code> from the JNDI context.
144 */
145 public
146 void setTopicBindingName(String topicBindingName) {
147 this.topicBindingName = topicBindingName;
148 }
149
150 /***
151 Returns the value of the <b>TopicBindingName</b> option.
152 */
153 public
154 String getTopicBindingName() {
155 return topicBindingName;
156 }
157
158
159 /***
160 Returns value of the <b>LocationInfo</b> property which
161 determines whether location (stack) info is sent to the remote
162 subscriber. */
163 public
164 boolean getLocationInfo() {
165 return locationInfo;
166 }
167
168 /***
169 * Options are activated and become effective only after calling
170 * this method.*/
171 public void activateOptions() {
172 TopicConnectionFactory topicConnectionFactory;
173
174 try {
175 Context jndi;
176
177 LogLog.debug("Getting initial context.");
178 if(initialContextFactoryName != null) {
179 Properties env = new Properties( );
180 env.put(Context.INITIAL_CONTEXT_FACTORY, initialContextFactoryName);
181 if(providerURL != null) {
182 env.put(Context.PROVIDER_URL, providerURL);
183 } else {
184 LogLog.warn("You have set InitialContextFactoryName option but not the "
185 +"ProviderURL. This is likely to cause problems.");
186 }
187 if(urlPkgPrefixes != null) {
188 env.put(Context.URL_PKG_PREFIXES, urlPkgPrefixes);
189 }
190
191 if(securityPrincipalName != null) {
192 env.put(Context.SECURITY_PRINCIPAL, securityPrincipalName);
193 if(securityCredentials != null) {
194 env.put(Context.SECURITY_CREDENTIALS, securityCredentials);
195 } else {
196 LogLog.warn("You have set SecurityPrincipalName option but not the "
197 +"SecurityCredentials. This is likely to cause problems.");
198 }
199 }
200 jndi = new InitialContext(env);
201 } else {
202 jndi = new InitialContext();
203 }
204
205 LogLog.debug("Looking up ["+tcfBindingName+"]");
206 topicConnectionFactory = (TopicConnectionFactory) lookup(jndi, tcfBindingName);
207 LogLog.debug("About to create TopicConnection.");
208 if(userName != null) {
209 topicConnection = topicConnectionFactory.createTopicConnection(userName,
210 password);
211 } else {
212 topicConnection = topicConnectionFactory.createTopicConnection();
213 }
214
215 LogLog.debug("Creating TopicSession, non-transactional, "
216 +"in AUTO_ACKNOWLEDGE mode.");
217 topicSession = topicConnection.createTopicSession(false,
218 Session.AUTO_ACKNOWLEDGE);
219
220 LogLog.debug("Looking up topic name ["+topicBindingName+"].");
221 Topic topic = (Topic) lookup(jndi, topicBindingName);
222
223 LogLog.debug("Creating TopicPublisher.");
224 topicPublisher = topicSession.createPublisher(topic);
225
226 LogLog.debug("Starting TopicConnection.");
227 topicConnection.start();
228
229 jndi.close();
230 } catch(Exception e) {
231 errorHandler.error("Error while activating options for appender named ["+name+
232 "].", e, ErrorCode.GENERIC_FAILURE);
233 }
234 }
235
236 protected Object lookup(Context ctx, String name) throws NamingException {
237 try {
238 return ctx.lookup(name);
239 } catch(NameNotFoundException e) {
240 LogLog.error("Could not find name ["+name+"].");
241 throw e;
242 }
243 }
244
245 protected boolean checkEntryConditions() {
246 String fail = null;
247
248 if(this.topicConnection == null) {
249 fail = "No TopicConnection";
250 } else if(this.topicSession == null) {
251 fail = "No TopicSession";
252 } else if(this.topicPublisher == null) {
253 fail = "No TopicPublisher";
254 }
255
256 if(fail != null) {
257 errorHandler.error(fail +" for JMSAppender named ["+name+"].");
258 return false;
259 } else {
260 return true;
261 }
262 }
263
264 /***
265 Close this JMSAppender. Closing releases all resources used by the
266 appender. A closed appender cannot be re-opened. */
267 public synchronized void close() {
268
269
270 if(this.closed)
271 return;
272
273 LogLog.debug("Closing appender ["+name+"].");
274 this.closed = true;
275
276 try {
277 if(topicSession != null)
278 topicSession.close();
279 if(topicConnection != null)
280 topicConnection.close();
281 } catch(Exception e) {
282 LogLog.error("Error while closing JMSAppender ["+name+"].", e);
283 }
284
285 topicPublisher = null;
286 topicSession = null;
287 topicConnection = null;
288 }
289
290 /***
291 This method called by {@link AppenderSkeleton#doAppend} method to
292 do most of the real appending work. */
293 public void append(LoggingEvent event) {
294 if(!checkEntryConditions()) {
295 return;
296 }
297
298 try {
299 ObjectMessage msg = topicSession.createObjectMessage();
300 if(locationInfo) {
301 event.getLocationInformation();
302 }
303 msg.setObject(event);
304 topicPublisher.publish(msg);
305 } catch(Exception e) {
306 errorHandler.error("Could not publish message in JMSAppender ["+name+"].", e,
307 ErrorCode.GENERIC_FAILURE);
308 }
309 }
310
311 /***
312 * Returns the value of the <b>InitialContextFactoryName</b> option.
313 * See {@link #setInitialContextFactoryName} for more details on the
314 * meaning of this option.
315 * */
316 public String getInitialContextFactoryName() {
317 return initialContextFactoryName;
318 }
319
320 /***
321 * Setting the <b>InitialContextFactoryName</b> method will cause
322 * this <code>JMSAppender</code> instance to use the {@link
323 * InitialContext#InitialContext(Hashtable)} method instead of the
324 * no-argument constructor. If you set this option, you should also
325 * at least set the <b>ProviderURL</b> option.
326 *
327 * <p>See also {@link #setProviderURL(String)}.
328 * */
329 public void setInitialContextFactoryName(String initialContextFactoryName) {
330 this.initialContextFactoryName = initialContextFactoryName;
331 }
332
333 public String getProviderURL() {
334 return providerURL;
335 }
336
337 public void setProviderURL(String providerURL) {
338 this.providerURL = providerURL;
339 }
340
341 String getURLPkgPrefixes( ) {
342 return urlPkgPrefixes;
343 }
344
345 public void setURLPkgPrefixes(String urlPkgPrefixes ) {
346 this.urlPkgPrefixes = urlPkgPrefixes;
347 }
348
349 public String getSecurityCredentials() {
350 return securityCredentials;
351 }
352
353 public void setSecurityCredentials(String securityCredentials) {
354 this.securityCredentials = securityCredentials;
355 }
356
357
358 public String getSecurityPrincipalName() {
359 return securityPrincipalName;
360 }
361
362 public void setSecurityPrincipalName(String securityPrincipalName) {
363 this.securityPrincipalName = securityPrincipalName;
364 }
365
366 public String getUserName() {
367 return userName;
368 }
369
370 /***
371 * The user name to use when {@link
372 * TopicConnectionFactory#createTopicConnection(String, String)
373 * creating a topic session}. If you set this option, you should
374 * also set the <b>Password</b> option. See {@link
375 * #setPassword(String)}.
376 * */
377 public void setUserName(String userName) {
378 this.userName = userName;
379 }
380
381 public String getPassword() {
382 return password;
383 }
384
385 /***
386 * The paswword to use when creating a topic session.
387 */
388 public void setPassword(String password) {
389 this.password = password;
390 }
391
392
393 /***
394 If true, the information sent to the remote subscriber will
395 include caller's location information. By default no location
396 information is sent to the subscriber. */
397 public void setLocationInfo(boolean locationInfo) {
398 this.locationInfo = locationInfo;
399 }
400
401 /***
402 * Returns the TopicConnection used for this appender. Only valid after
403 * activateOptions() method has been invoked.
404 */
405 protected TopicConnection getTopicConnection() {
406 return topicConnection;
407 }
408
409 /***
410 * Returns the TopicSession used for this appender. Only valid after
411 * activateOptions() method has been invoked.
412 */
413 protected TopicSession getTopicSession() {
414 return topicSession;
415 }
416
417 /***
418 * Returns the TopicPublisher used for this appender. Only valid after
419 * activateOptions() method has been invoked.
420 */
421 protected TopicPublisher getTopicPublisher() {
422 return topicPublisher;
423 }
424
425 /***
426 * The JMSAppender sends serialized events and consequently does not
427 * require a layout.
428 */
429 public boolean requiresLayout() {
430 return false;
431 }
432 }