7e1e62ead5edce3bde0437997bee8cec476dc1b3
[jalview.git] / srcjar2 / org / apache / log4j / net / JMSAppender.java
1 /*
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
8  * 
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  * 
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.
16  */
17
18 package org.apache.log4j.net;
19
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;
24
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;
38
39 /**
40  * A simple appender that publishes events to a JMS Topic. The events
41  * are serialized and transmitted as JMS message type {@link
42  * ObjectMessage}.
43
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
47  * {@link Context}.
48
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:
54
55    <pre>
56    InitialContext jndiContext = new InitialContext();
57    </pre>
58   
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).
63     
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:
68
69 <pre>
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);
75 </pre>
76
77    * where <em>hostname</em> is the host where the JBoss application
78    * server is running.
79    *
80    * <p>To connect to the the naming service of Weblogic application
81    * server one would write:
82
83 <pre>
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);
88 </pre>
89
90   * <p>Other JMS providers will obviously require different values.
91   * 
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.
99
100
101    @author Ceki G&uuml;lc&uuml; */
102 public class JMSAppender extends AppenderSkeleton {
103
104   String securityPrincipalName;
105   String securityCredentials;
106   String initialContextFactoryName;
107   String urlPkgPrefixes;
108   String providerURL;
109   String topicBindingName;
110   String tcfBindingName;
111   String userName;
112   String password;
113   boolean locationInfo;
114
115   TopicConnection  topicConnection;
116   TopicSession topicSession;
117   TopicPublisher  topicPublisher;
118
119   public
120   JMSAppender() {
121   }
122
123   /**
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.
127    */
128   public
129   void setTopicConnectionFactoryBindingName(String tcfBindingName) {
130     this.tcfBindingName = tcfBindingName;
131   }
132
133   /**
134      Returns the value of the <b>TopicConnectionFactoryBindingName</b> option.
135    */
136   public
137   String getTopicConnectionFactoryBindingName() {
138     return tcfBindingName;
139   }
140
141   /**
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.
145    */
146   public
147   void setTopicBindingName(String topicBindingName) {
148     this.topicBindingName = topicBindingName;
149   }
150
151   /**
152      Returns the value of the <b>TopicBindingName</b> option.
153    */
154   public
155   String getTopicBindingName() {
156     return topicBindingName;
157   }
158
159
160   /**
161      Returns value of the <b>LocationInfo</b> property which
162      determines whether location (stack) info is sent to the remote
163      subscriber. */
164   public
165   boolean getLocationInfo() {
166     return locationInfo;
167   }
168
169   /**
170    *  Options are activated and become effective only after calling
171    *  this method.*/
172   public void activateOptions() {
173     TopicConnectionFactory  topicConnectionFactory;
174
175     try {
176       Context jndi;
177
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);
184         } else {
185           LogLog.warn("You have set InitialContextFactoryName option but not the "
186                      +"ProviderURL. This is likely to cause problems.");
187         }
188         if(urlPkgPrefixes != null) {
189           env.put(Context.URL_PKG_PREFIXES, urlPkgPrefixes);
190         }
191         
192         if(securityPrincipalName != null) {
193           env.put(Context.SECURITY_PRINCIPAL, securityPrincipalName);
194           if(securityCredentials != null) {
195             env.put(Context.SECURITY_CREDENTIALS, securityCredentials);
196           } else {
197             LogLog.warn("You have set SecurityPrincipalName option but not the "
198                         +"SecurityCredentials. This is likely to cause problems.");
199           }
200         }       
201         jndi = new InitialContext(env);
202       } else {
203         jndi = new InitialContext();
204       }
205
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, 
211                                                                        password); 
212       } else {
213         topicConnection = topicConnectionFactory.createTopicConnection();
214       }
215
216       LogLog.debug("Creating TopicSession, non-transactional, "
217                    +"in AUTO_ACKNOWLEDGE mode.");
218       topicSession = topicConnection.createTopicSession(false,
219                                                         Session.AUTO_ACKNOWLEDGE);
220
221       LogLog.debug("Looking up topic name ["+topicBindingName+"].");
222       Topic topic = (Topic) lookup(jndi, topicBindingName);
223
224       LogLog.debug("Creating TopicPublisher.");
225       topicPublisher = topicSession.createPublisher(topic);
226       
227       LogLog.debug("Starting TopicConnection.");
228       topicConnection.start();
229
230       jndi.close();
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);
240     }
241   }
242
243   protected Object lookup(Context ctx, String name) throws NamingException {
244     try {
245       return ctx.lookup(name);
246     } catch(NameNotFoundException e) {
247       LogLog.error("Could not find name ["+name+"].");
248       throw e;
249     }
250   }
251
252   protected boolean checkEntryConditions() {
253     String fail = null;
254
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";
261     }
262
263     if(fail != null) {
264       errorHandler.error(fail +" for JMSAppender named ["+name+"].");
265       return false;
266     } else {
267       return true;
268     }
269   }
270
271   /**
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
276
277     if(this.closed) {
278         return;
279     }
280
281     LogLog.debug("Closing appender ["+name+"].");
282     this.closed = true;
283
284     try {
285       if(topicSession != null) {
286         topicSession.close();
287     }
288       if(topicConnection != null) {
289         topicConnection.close();
290     }
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);
295     }
296     // Help garbage collection
297     topicPublisher = null;
298     topicSession = null;
299     topicConnection = null;
300   }
301
302   /**
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()) {
307       return;
308     }
309
310     try {
311       ObjectMessage msg = topicSession.createObjectMessage();
312       if(locationInfo) {
313         event.getLocationInformation();
314       }
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);
323     }
324   }
325
326   /**
327    * Returns the value of the <b>InitialContextFactoryName</b> option.
328    * See {@link #setInitialContextFactoryName} for more details on the
329    * meaning of this option.
330    * */
331   public String getInitialContextFactoryName() {
332     return initialContextFactoryName;    
333   }
334   
335   /**
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.
341    * 
342    * @see #setProviderURL(String)
343    * */
344   public void setInitialContextFactoryName(String initialContextFactoryName) {
345     this.initialContextFactoryName = initialContextFactoryName;
346   }
347
348   public String getProviderURL() {
349     return providerURL;    
350   }
351
352   public void setProviderURL(String providerURL) {
353     this.providerURL = providerURL;
354   }
355
356   String getURLPkgPrefixes( ) {
357     return urlPkgPrefixes;
358   }
359
360   public void setURLPkgPrefixes(String urlPkgPrefixes ) {
361     this.urlPkgPrefixes = urlPkgPrefixes;
362   }
363   
364   public String getSecurityCredentials() {
365     return securityCredentials;    
366   }
367
368   public void setSecurityCredentials(String securityCredentials) {
369     this.securityCredentials = securityCredentials;
370   }
371   
372   
373   public String getSecurityPrincipalName() {
374     return securityPrincipalName;    
375   }
376
377   public void setSecurityPrincipalName(String securityPrincipalName) {
378     this.securityPrincipalName = securityPrincipalName;
379   }
380
381   public String getUserName() {
382     return userName;    
383   }
384
385   /**
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)}.
391    * */
392   public void setUserName(String userName) {
393     this.userName = userName;
394   }
395
396   public String getPassword() {
397     return password;    
398   }
399
400   /**
401    * The paswword to use when creating a topic session.  
402    */
403   public void setPassword(String password) {
404     this.password = password;
405   }
406
407
408   /**
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;
414   }
415
416   /**
417    * Returns the TopicConnection used for this appender.  Only valid after
418    * activateOptions() method has been invoked.
419    */
420   protected TopicConnection  getTopicConnection() {
421     return topicConnection;
422   }
423
424   /**
425    * Returns the TopicSession used for this appender.  Only valid after
426    * activateOptions() method has been invoked.
427    */
428   protected TopicSession  getTopicSession() {
429     return topicSession;
430   }
431
432   /**
433    * Returns the TopicPublisher used for this appender.  Only valid after
434    * activateOptions() method has been invoked.
435    */
436   protected TopicPublisher  getTopicPublisher() {
437     return topicPublisher;
438   }
439   
440   /** 
441    * The JMSAppender sends serialized events and consequently does not
442    * require a layout.
443    */
444   public boolean requiresLayout() {
445     return false;
446   }
447 }