root/trunk/src/java/org/jcoderz/commons/logging/JmsHandler.java

Revision 1011, 16.9 kB (checked in by amandel, 4 years ago)

Aligned svn keyword settings.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1/*
2 * $Id$
3 *
4 * Copyright 2006, The jCoderZ.org Project. All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are
8 * met:
9 *
10 *    * Redistributions of source code must retain the above copyright
11 *      notice, this list of conditions and the following disclaimer.
12 *    * Redistributions in binary form must reproduce the above
13 *      copyright notice, this list of conditions and the following
14 *      disclaimer in the documentation and/or other materials
15 *      provided with the distribution.
16 *    * Neither the name of the jCoderZ.org Project nor the names of
17 *      its contributors may be used to endorse or promote products
18 *      derived from this software without specific prior written
19 *      permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
24 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS
25 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
28 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
29 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
30 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
31 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 */
33package org.jcoderz.commons.logging;
34
35import java.io.CharArrayWriter;
36import java.io.PrintWriter;
37import java.lang.ref.WeakReference;
38import java.security.AccessController;
39import java.security.PrivilegedActionException;
40import java.security.PrivilegedExceptionAction;
41import java.util.ArrayList;
42import java.util.List;
43import java.util.logging.ErrorManager;
44import java.util.logging.Filter;
45import java.util.logging.Formatter;
46import java.util.logging.Handler;
47import java.util.logging.LogManager;
48import java.util.logging.LogRecord;
49
50import javax.jms.JMSException;
51import javax.jms.Queue;
52import javax.jms.QueueConnection;
53import javax.jms.QueueConnectionFactory;
54import javax.jms.QueueSender;
55import javax.jms.QueueSession;
56import javax.jms.Session;
57import javax.jms.TextMessage;
58import javax.naming.Context;
59import javax.naming.InitialContext;
60import javax.naming.NamingException;
61
62import org.jcoderz.commons.LoggableImpl;
63
64
65/**
66 * This log handler publishes log messages onto a jms queue. Information for the
67 * configuration has to be provided in the logging properties file.
68 * The following configuration can to be provided:<br>
69 *
70 * <code>org.jcoderz.commons.logging.JmsHandler.factory:</code><br>
71 *    Name of the jms connection factory, is mandatory.<br><br>
72 *
73 * <code>org.jcoderz.commons.logging.JmsHandler.queue:</code><br>
74 *    Name of the jms queue, is mandatory.<br><br>
75 *
76 * <code>org.jcoderz.commons.logging.JmsHandler.filter:</code><br>
77 *    Name of class implementing java.util.logging.Filter and which is to
78 *    set as filter for this. If this is not specified, the default filter is
79 *    used, which filters according to the symbol ids.<br><br>
80 *
81 * <code>org.jcoderz.commons.logging.JmsHandler.messageids:</code><br>
82 *    A comma or space separated list of message symbol ids for those
83 *    messages, which are loggable for the default filter. The ids have either
84 *    to be specified as integer values with base 10, or by prefixing with 0x as
85 *    hex values.
86 *
87 */
88public class JmsHandler
89      extends Handler
90{
91   private static final String CLASSNAME = JmsHandler.class.getName();
92   private static final String CLIENT_ID
93         = "JmsHandler@" + LoggableImpl.INSTANCE_ID;
94
95   private static final String JMS_FACTORY_NAME_PROPERTY
96         = CLASSNAME + "." + "factory";
97   private static final String JMS_QUEUE_NAME_PROPERTY
98         = CLASSNAME + "." + "queue";
99   private static final String JMS_FILTER_PROPERTY
100         = CLASSNAME + "." + "filter";
101   private static final String JMS_FORMATTER_PROPERTY
102         = CLASSNAME + "." + "formatter";
103
104   /**
105    * The created sessions to not behave transactional, i.e. do not put a tx
106    * boundary around sending several messages.
107    */
108   private static final boolean SESSION_TRANSACTION_MODE = false;
109
110   /**
111    * The acknowledge mode, is being ignored for senders.
112    */
113   private static final int SESSION_ACKNOWLEDGE_MODE = Session.AUTO_ACKNOWLEDGE;
114
115   /**
116    * Stores the jms queue session for the current thread. With respect to the
117    * JMS specification a session and the resources it provides must be used by
118    * only one thread.
119    */
120   private final ThreadLocal mJmsSessions = new ThreadLocal();
121
122   /**
123    * Stores the jms queue sender for the current thread.
124    */
125   private final ThreadLocal mJmsSenders = new ThreadLocal();
126
127   /**
128    * When closing this handler, all sessions have to be closed (closing a
129    * session is the only session method, which is allowed to be called from
130    * another than the session controlling thread). This gives access to all
131    * sessions and will store WeakReferences to allow a session being garbage
132    * collected if the corresponding thread dies.
133    */
134   private final List mAllSessions = new ArrayList();
135
136   private final LogManager mManager = LogManager.getLogManager();
137
138   private QueueConnection mJmsConnection = null;
139   private Queue mJmsQueue = null;
140
141   private Context mContext;
142
143   private String mFactoryName;
144   private String mQueueName;
145
146   /**
147    * This is the formatter to be used for formatting log records before they
148    * are put onto the jms queue.
149    * It formats the log record into a xml message using the
150    * {@link XmlPrinter}. The stack trace of messages and exceptions is
151    * neglected.
152    *
153    */
154   private static final class DefaultFormatter
155         extends Formatter
156   {
157      /** A PrintWriter is used by the XmlPrinter. */
158      private final ThreadLocal mPrintWriters = new ThreadLocal();
159      /** A CharWriter is used by the PrintWriter. */
160      private final ThreadLocal mCharWriters = new ThreadLocal();
161
162      private final XmlPrinter mXmlPrinter;
163      private final DisplayOptions mDisplayOptions;
164
165      private DefaultFormatter ()
166            throws InstantiationException
167      {
168         mXmlPrinter = new XmlPrinter();
169         mDisplayOptions = new DisplayOptions();
170         mDisplayOptions.displayMessageStackTrace(false);
171         mDisplayOptions.displayStackTrace(false);
172         mXmlPrinter.setDisplayOptions(mDisplayOptions);
173      }
174
175      /** {@inheritDoc} */
176      public String format (LogRecord record)
177      {
178         final CharArrayWriter writer = getCharWriter();
179         writer.reset();
180         final PrintWriter printer = getPrintWriter();
181         mXmlPrinter.print(printer, new LogElement(record));
182         return writer.toString();
183      }
184
185      private PrintWriter getPrintWriter ()
186      {
187         PrintWriter rc = (PrintWriter) mPrintWriters.get();
188         if (rc == null)
189         {
190            final CharArrayWriter cw = getCharWriter();
191            rc = new PrintWriter(cw);
192            mPrintWriters.set(rc);
193         }
194         return rc;
195      }
196
197      private CharArrayWriter getCharWriter ()
198      {
199         CharArrayWriter rc = (CharArrayWriter) mCharWriters.get();
200         if (rc == null)
201         {
202            rc = new CharArrayWriter();
203            mCharWriters.set(rc);
204         }
205         return rc;
206      }
207   }
208
209
210   /**
211    * Creates a new instance of this and initialises resources. It retrieves
212    * configuration parameters from the LogManagers and connects to the jms
213    * provider.
214    *
215    * @throws SecurityException If no permission to do the tasks.
216    * @throws NamingException If the jms connection factory lookup fails.
217    * @throws InstantiationException If not all required configuration
218    * parameters are specified.
219    * @throws IllegalAccessException If illegal access to a class.
220    * @throws ClassNotFoundException If a specified class name could not be
221    * found.
222    * @throws JMSException If an error connecting to the JmsProvider occurs.
223    */
224   public JmsHandler ()
225         throws SecurityException,
226         NamingException,
227         InstantiationException,
228         IllegalAccessException,
229         ClassNotFoundException,
230         JMSException
231   {
232      super();
233      mManager.checkAccess();
234      configure();
235      connect();
236   }
237
238   /** {@inheritDoc} */
239   public void close ()
240         throws SecurityException
241   {
242      mManager.checkAccess();
243      setFormatter(null);
244      setFilter(null);
245      synchronized (mAllSessions)
246      {
247         while (! mAllSessions.isEmpty())
248         {
249            final WeakReference ref = (WeakReference) mAllSessions.remove(0);
250            final QueueSession session = (QueueSession) ref.get();
251            if (session != null)
252            {
253               try
254               {
255                  session.close();
256               }
257               catch (Exception ex)
258               {
259                  reportError("Error closing jms session: " + session,
260                        ex, ErrorManager.CLOSE_FAILURE);
261               }
262            }
263         }
264      }
265      try
266      {
267         mJmsConnection.close();
268      }
269      catch (JMSException jex)
270      {
271         reportError("Error closing jms connection: " + mJmsConnection,
272               jex, ErrorManager.CLOSE_FAILURE);
273      }
274   }
275
276   /** {@inheritDoc} */
277   public void flush ()
278   {
279      // nop
280   }
281
282   /** {@inheritDoc} */
283   public void publish (final LogRecord record)
284   {
285      if (getFilter().isLoggable(record))
286      {
287         sendRecord(record);
288      }
289   }
290
291   private void configure ()
292         throws NamingException,
293         SecurityException,
294         InstantiationException,
295         IllegalAccessException,
296         ClassNotFoundException
297   {
298      configureJndiContext();
299      configureResources();
300      configureFilter();
301      configureFormatter();
302   }
303
304   private void configureJndiContext ()
305         throws NamingException
306   {
307      final Context context = new InitialContext();
308      mContext = context;
309   }
310
311   private void configureFilter ()
312         throws SecurityException,
313         InstantiationException,
314         IllegalAccessException,
315         ClassNotFoundException
316   {
317      final String filterClass = mManager.getProperty(JMS_FILTER_PROPERTY);
318      if (filterClass == null || filterClass.length() == 0)
319      {
320         setFilter(new MessageIdFilter());
321      }
322      else
323      {
324         try
325         {
326            AccessController.doPrivileged(
327                  new PrivilegedExceptionAction()
328                  {
329                     public Object run ()
330                           throws SecurityException,
331                           InstantiationException,
332                           IllegalAccessException,
333                           ClassNotFoundException
334                     {
335                        setFilter((Filter) Class.forName(filterClass)
336                              .newInstance());
337                        return null;
338                     }
339                  }
340            );
341        }
342        catch (PrivilegedActionException e)
343        {
344           final InstantiationException iex = new InstantiationException(
345                 "Could not install the Filter: " + filterClass);
346           iex.initCause(e);
347           throw iex;
348        }
349      }
350   }
351
352   private void configureFormatter ()
353         throws SecurityException,
354         InstantiationException,
355         IllegalAccessException,
356         ClassNotFoundException
357   {
358      final String formatter = mManager.getProperty(JMS_FORMATTER_PROPERTY);
359      if (formatter == null || formatter.length() == 0)
360      {
361         setFormatter(new DefaultFormatter());
362      }
363      else
364      {
365         try
366         {
367            AccessController.doPrivileged(
368                  new PrivilegedExceptionAction()
369                  {
370                     public Object run ()
371                           throws SecurityException,
372                           InstantiationException,
373                           IllegalAccessException,
374                           ClassNotFoundException
375                     {
376                        setFormatter((Formatter) Class.forName(formatter)
377                              .newInstance());
378                        return null;
379                     }
380                  }
381            );
382         }
383         catch (PrivilegedActionException e)
384         {
385            final InstantiationException iex
386                  = new InstantiationException(
387                        "Could not install the Formatter: " + formatter);
388            iex.initCause(e);
389            throw iex;
390         }
391      }
392   }
393
394   private void configureResources ()
395         throws InstantiationException
396   {
397      final String factory = mManager.getProperty(JMS_FACTORY_NAME_PROPERTY);
398      if ((factory == null) || (factory.length() == 0))
399      {
400         throw new InstantiationException("No jms connection factory configured"
401               + " in properties file with property: "
402               + JMS_FACTORY_NAME_PROPERTY);
403      }
404      final String queue = mManager.getProperty(JMS_QUEUE_NAME_PROPERTY);
405      if ((queue == null) || (queue.length() == 0))
406      {
407         throw new InstantiationException("No jms queue configured in "
408               + "properties file with property: " + JMS_QUEUE_NAME_PROPERTY);
409      }
410      mFactoryName = factory;
411      mQueueName = queue;
412   }
413
414   /**
415    * Creates a connection to the jms provider and performs a lookup for the
416    * queue, which will receive the log messages.
417    *
418    * @throws NamingException
419    * @throws JMSException
420    */
421   private void connect ()
422         throws NamingException, JMSException
423   {
424      final QueueConnectionFactory factory
425            = (QueueConnectionFactory) mContext.lookup(mFactoryName);
426      final Queue queue = (Queue) mContext.lookup(mQueueName);
427
428      mJmsConnection = factory.createQueueConnection();
429      mJmsConnection.setClientID(CLIENT_ID);
430      mJmsQueue = queue;
431   }
432
433   /**
434    * Gets the queue sender for the current thread. If there is no sender yet,
435    * this thread's session is used for creating a new sender.
436    *
437    * @return queue sender for the current thread.
438    *
439    * @throws JMSException if an error occurs.
440    */
441   private QueueSender getSender ()
442         throws JMSException
443   {
444      QueueSender rc = (QueueSender) mJmsSenders.get();
445      if (rc == null)
446      {
447         try
448         {
449            rc = (QueueSender) AccessController.doPrivileged(
450                  new PrivilegedExceptionAction()
451                  {
452                     public Object run ()
453                           throws JMSException
454                     {
455                        return installSender();
456                     }
457                  });
458         }
459         catch (PrivilegedActionException e)
460         {
461            final JMSException jex
462                  = new JMSException("Could not install a QueueSender:" + e);
463            jex.initCause(e);
464            throw jex;
465         }
466      }
467      return rc;
468   }
469
470   /**
471    * Gets the queue session for the current thread. If there is no session yet,
472    * a new session is created and initialized.
473    *
474    * @return queue session for the current thread.
475    *
476    * @throws JMSException if an error occurs.
477    */
478   private QueueSession getSession ()
479         throws JMSException
480   {
481      QueueSession rc = (QueueSession) mJmsSessions.get();
482      if (rc == null)
483      {
484         final QueueSession session = mJmsConnection.createQueueSession(
485               SESSION_TRANSACTION_MODE, SESSION_ACKNOWLEDGE_MODE);
486         mJmsSessions.set(session);
487         synchronized (mAllSessions)
488         {
489            mAllSessions.add(new WeakReference(session));
490         }
491         rc = session;
492      }
493      return rc;
494   }
495
496   private QueueSender installSender ()
497         throws JMSException
498   {
499      final QueueSession session = getSession();
500      QueueSender sender = null;
501      try
502      {
503         sender = session.createSender(mJmsQueue);
504         mJmsSenders.set(sender);
505      }
506      finally
507      {
508         if (sender == null)
509         {
510            // An error ocurred on this session. Close it, it might occur again.
511            mJmsSessions.set(null);
512            session.close();
513         }
514      }
515      return sender;
516   }
517
518   private void sendRecord (final LogRecord record)
519   {
520      String text = null;
521      try
522      {
523         text = getFormatter().format(record);
524      }
525      catch (Exception ex)
526      {
527         reportError("Error formatting the log record",
528               ex, ErrorManager.FORMAT_FAILURE);
529      }
530      if (text != null)
531      {
532         try
533         {
534            final TextMessage msg = getSession().createTextMessage();
535            msg.setText(text);
536            getSender().send(msg);
537         }
538         catch (JMSException ex)
539         {
540            reportError("Error publishing a log record", ex,
541                  ErrorManager.WRITE_FAILURE);
542         }
543      }
544   }
545}
Note: See TracBrowser for help on using the browser.