001    /*
002     * CDDL HEADER START
003     *
004     * The contents of this file are subject to the terms of the
005     * Common Development and Distribution License, Version 1.0 only
006     * (the "License").  You may not use this file except in compliance
007     * with the License.
008     *
009     * You can obtain a copy of the license at
010     * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011     * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012     * See the License for the specific language governing permissions
013     * and limitations under the License.
014     *
015     * When distributing Covered Code, include this CDDL HEADER in each
016     * file and include the License file at
017     * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
018     * add the following below this CDDL HEADER, with the fields enclosed
019     * by brackets "[]" replaced with your own identifying information:
020     *      Portions Copyright [yyyy] [name of copyright owner]
021     *
022     * CDDL HEADER END
023     *
024     *
025     *      Copyright 2007-2008 Sun Microsystems, Inc.
026     */
027    
028    package org.opends.server.loggers.debug;
029    import org.opends.messages.Message;
030    
031    import java.util.*;
032    import java.util.concurrent.CopyOnWriteArrayList;
033    import java.util.concurrent.ConcurrentHashMap;
034    import java.lang.reflect.Method;
035    import java.lang.reflect.InvocationTargetException;
036    
037    import org.opends.server.api.DebugLogPublisher;
038    import org.opends.server.loggers.*;
039    import org.opends.server.types.*;
040    import org.opends.server.admin.std.server.DebugLogPublisherCfg;
041    import org.opends.server.admin.std.meta.DebugLogPublisherCfgDefn;
042    import org.opends.server.admin.server.ConfigurationAddListener;
043    import org.opends.server.admin.server.ConfigurationChangeListener;
044    import org.opends.server.admin.server.ConfigurationDeleteListener;
045    import org.opends.server.admin.ClassPropertyDefinition;
046    import org.opends.server.config.ConfigException;
047    import org.opends.server.core.DirectoryServer;
048    
049    import static org.opends.messages.ConfigMessages.*;
050    
051    import static org.opends.server.util.StaticUtils.*;
052    
053    /**
054     * A logger for debug and trace logging. DebugLogger provides a debugging
055     * management access point. It is used to configure the Tracers, as well as
056     * to register a per-class tracer.
057     *
058     * Various stub debug methods are provided to log different types of debug
059     * messages. However, these methods do not contain any actual implementation.
060     * Tracer aspects are later weaved to catch alls to these stub methods and
061     * do the work of logging the message.
062     *
063     * DebugLogger is self-initializing.
064     */
065    public class DebugLogger implements
066        ConfigurationAddListener<DebugLogPublisherCfg>,
067        ConfigurationDeleteListener<DebugLogPublisherCfg>,
068        ConfigurationChangeListener<DebugLogPublisherCfg>
069    {
070      //The default level to log constructor exectuions.
071      static final LogLevel DEFAULT_CONSTRUCTOR_LEVEL =
072          DebugLogLevel.VERBOSE;
073      //The default level to log method entry and exit pointcuts.
074      static final LogLevel DEFAULT_ENTRY_EXIT_LEVEL =
075          DebugLogLevel.VERBOSE;
076      //The default level to log method entry and exit pointcuts.
077      static final LogLevel DEFAULT_THROWN_LEVEL =
078          DebugLogLevel.ERROR;
079    
080      // The set of all DebugTracer instances.
081      private static ConcurrentHashMap<String, DebugTracer> classTracers =
082          new ConcurrentHashMap<String, DebugTracer>();
083    
084      // The set of debug loggers that have been registered with the server.  It
085      // will initially be empty.
086      private static CopyOnWriteArrayList<DebugLogPublisher> debugPublishers =
087          new CopyOnWriteArrayList<DebugLogPublisher>();
088    
089      // Trace methods will use this static boolean to determine if debug is
090      // enabled so to not incur the cost of calling debugPublishers.isEmtpty().
091      static boolean enabled = false;
092    
093      // The singleton instance of this class for configuration purposes.
094      static final DebugLogger instance = new DebugLogger();
095    
096      /**
097       * Add an debug log publisher to the debug logger.
098       *
099       * @param publisher The error log publisher to add.
100       */
101      public synchronized static void addDebugLogPublisher(
102          DebugLogPublisher publisher)
103      {
104        debugPublishers.add(publisher);
105    
106        updateTracerSettings();
107    
108        enabled = true;
109      }
110    
111      /**
112       * Remove an debug log publisher from the debug logger.
113       *
114       * @param publisher The debug log publisher to remove.
115       * @return The publisher that was removed or null if it was not found.
116       */
117      public synchronized static boolean removeDebugLogPublisher(
118          DebugLogPublisher publisher)
119      {
120        boolean removed = debugPublishers.remove(publisher);
121    
122        if(removed)
123        {
124          publisher.close();
125        }
126    
127        updateTracerSettings();
128    
129        if(debugPublishers.isEmpty())
130        {
131          enabled = false;
132        }
133    
134        return removed;
135      }
136    
137      /**
138       * Removes all existing debug log publishers from the logger.
139       */
140      public synchronized static void removeAllDebugLogPublishers()
141      {
142        for(DebugLogPublisher publisher : debugPublishers)
143        {
144          publisher.close();
145        }
146    
147        debugPublishers.clear();
148    
149        updateTracerSettings();
150    
151        enabled = false;
152      }
153    
154      /**
155       * Initializes all the debug log publishers.
156       *
157       * @param  configs The debug log publisher configurations.
158       * @throws ConfigException
159       *           If an unrecoverable problem arises in the process of
160       *           performing the initialization as a result of the server
161       *           configuration.
162       * @throws InitializationException
163       *           If a problem occurs during initialization that is not
164       *           related to the server configuration.
165       */
166      public void initializeDebugLogger(List<DebugLogPublisherCfg> configs)
167          throws ConfigException, InitializationException
168      {
169        for(DebugLogPublisherCfg config : configs)
170        {
171          config.addDebugChangeListener(this);
172    
173          if(config.isEnabled())
174          {
175            DebugLogPublisher debugLogPublisher = getDebugPublisher(config);
176    
177            addDebugLogPublisher(debugLogPublisher);
178          }
179        }
180      }
181    
182      /**
183       * {@inheritDoc}
184       */
185      public boolean isConfigurationAddAcceptable(DebugLogPublisherCfg config,
186                                                  List<Message> unacceptableReasons)
187      {
188        return !config.isEnabled() ||
189            isJavaClassAcceptable(config, unacceptableReasons);
190      }
191    
192      /**
193       * {@inheritDoc}
194       */
195      public boolean isConfigurationChangeAcceptable(DebugLogPublisherCfg config,
196                                                  List<Message> unacceptableReasons)
197      {
198        return !config.isEnabled() ||
199            isJavaClassAcceptable(config, unacceptableReasons);
200      }
201    
202      /**
203       * {@inheritDoc}
204       */
205      public ConfigChangeResult applyConfigurationAdd(DebugLogPublisherCfg config)
206      {
207        // Default result code.
208        ResultCode resultCode = ResultCode.SUCCESS;
209        boolean adminActionRequired = false;
210        ArrayList<Message> messages = new ArrayList<Message>();
211    
212        config.addDebugChangeListener(this);
213    
214        if(config.isEnabled())
215        {
216          try
217          {
218            DebugLogPublisher debugLogPublisher =
219                getDebugPublisher(config);
220    
221            addDebugLogPublisher(debugLogPublisher);
222          }
223          catch(ConfigException e)
224          {
225            messages.add(e.getMessageObject());
226            resultCode = DirectoryServer.getServerErrorResultCode();
227          }
228          catch (Exception e)
229          {
230    
231            messages.add(ERR_CONFIG_LOGGER_CANNOT_CREATE_LOGGER.get(
232                    String.valueOf(config.dn().toString()),
233                    stackTraceToSingleLineString(e)));
234            resultCode = DirectoryServer.getServerErrorResultCode();
235          }
236        }
237        return new ConfigChangeResult(resultCode, adminActionRequired, messages);
238      }
239    
240      /**
241       * {@inheritDoc}
242       */
243      public ConfigChangeResult applyConfigurationChange(
244          DebugLogPublisherCfg config)
245      {
246        // Default result code.
247        ResultCode resultCode = ResultCode.SUCCESS;
248        boolean adminActionRequired = false;
249        ArrayList<Message> messages = new ArrayList<Message>();
250    
251        DN dn = config.dn();
252    
253        DebugLogPublisher debugLogPublisher = null;
254        for(DebugLogPublisher publisher : debugPublishers)
255        {
256          if(publisher.getDN().equals(dn))
257          {
258            debugLogPublisher = publisher;
259          }
260        }
261    
262        if(debugLogPublisher == null)
263        {
264          if(config.isEnabled())
265          {
266            // Needs to be added and enabled.
267            return applyConfigurationAdd(config);
268          }
269        }
270        else
271        {
272          if(config.isEnabled())
273          {
274            // The publisher is currently active, so we don't need to do anything.
275            // Changes to the class name cannot be
276            // applied dynamically, so if the class name did change then
277            // indicate that administrative action is required for that
278            // change to take effect.
279            String className = config.getJavaClass();
280            if(!className.equals(debugLogPublisher.getClass().getName()))
281            {
282              adminActionRequired = true;
283            }
284          }
285          else
286          {
287            // The publisher is being disabled so shut down and remove.
288            removeDebugLogPublisher(debugLogPublisher);
289          }
290        }
291    
292        return new ConfigChangeResult(resultCode, adminActionRequired, messages);
293      }
294    
295      /**
296       * {@inheritDoc}
297       */
298      public boolean isConfigurationDeleteAcceptable(DebugLogPublisherCfg config,
299                                                 List<Message> unacceptableReasons)
300      {
301        DN dn = config.dn();
302    
303        DebugLogPublisher debugLogPublisher = null;
304        for(DebugLogPublisher publisher : debugPublishers)
305        {
306          if(publisher.getDN().equals(dn))
307          {
308            debugLogPublisher = publisher;
309          }
310        }
311    
312        return debugLogPublisher != null;
313    
314      }
315    
316      /**
317       * {@inheritDoc}
318       */
319      public ConfigChangeResult
320             applyConfigurationDelete(DebugLogPublisherCfg config)
321      {
322        // Default result code.
323        ResultCode resultCode = ResultCode.SUCCESS;
324        boolean adminActionRequired = false;
325    
326        DebugLogPublisher debugLogPublisher = null;
327        for(DebugLogPublisher publisher : debugPublishers)
328        {
329          if(publisher.getDN().equals(config.dn()))
330          {
331            debugLogPublisher = publisher;
332          }
333        }
334    
335        if(debugLogPublisher != null)
336        {
337          removeDebugLogPublisher(debugLogPublisher);
338        }
339        else
340        {
341          resultCode = ResultCode.NO_SUCH_OBJECT;
342        }
343    
344        return new ConfigChangeResult(resultCode, adminActionRequired);
345      }
346    
347      private boolean isJavaClassAcceptable(DebugLogPublisherCfg config,
348                                            List<Message> unacceptableReasons)
349      {
350        String className = config.getJavaClass();
351        DebugLogPublisherCfgDefn d = DebugLogPublisherCfgDefn.getInstance();
352        ClassPropertyDefinition pd =
353            d.getJavaClassPropertyDefinition();
354        // Load the class and cast it to a DebugLogPublisher.
355        DebugLogPublisher publisher = null;
356        Class<? extends DebugLogPublisher> theClass;
357        try {
358          theClass = pd.loadClass(className, DebugLogPublisher.class);
359          publisher = theClass.newInstance();
360        } catch (Exception e) {
361          Message message = ERR_CONFIG_LOGGER_INVALID_DEBUG_LOGGER_CLASS.get(
362                  className,
363                  config.dn().toString(),
364                  String.valueOf(e));
365          unacceptableReasons.add(message);
366          return false;
367        }
368        // Check that the implementation class implements the correct interface.
369        try {
370          // Determine the initialization method to use: it must take a
371          // single parameter which is the exact type of the configuration
372          // object.
373          Method method = theClass.getMethod("isConfigurationAcceptable",
374                                             DebugLogPublisherCfg.class,
375                                             List.class);
376          Boolean acceptable = (Boolean) method.invoke(publisher, config,
377                                                       unacceptableReasons);
378    
379          if (! acceptable)
380          {
381            return false;
382          }
383        } catch (Exception e) {
384          Message message = ERR_CONFIG_LOGGER_INVALID_DEBUG_LOGGER_CLASS.get(
385                  className,
386                  config.dn().toString(),
387                  String.valueOf(e));
388          unacceptableReasons.add(message);
389          return false;
390        }
391        // The class is valid as far as we can tell.
392        return true;
393      }
394    
395      private DebugLogPublisher getDebugPublisher(DebugLogPublisherCfg config)
396          throws ConfigException {
397        String className = config.getJavaClass();
398        DebugLogPublisherCfgDefn d = DebugLogPublisherCfgDefn.getInstance();
399        ClassPropertyDefinition pd =
400            d.getJavaClassPropertyDefinition();
401        // Load the class and cast it to a DebugLogPublisher.
402        Class<? extends DebugLogPublisher> theClass;
403        DebugLogPublisher debugLogPublisher;
404        try {
405          theClass = pd.loadClass(className, DebugLogPublisher.class);
406          debugLogPublisher = theClass.newInstance();
407    
408          // Determine the initialization method to use: it must take a
409          // single parameter which is the exact type of the configuration
410          // object.
411          Method method = theClass.getMethod("initializeDebugLogPublisher", config
412              .configurationClass());
413          method.invoke(debugLogPublisher, config);
414        }
415        catch (InvocationTargetException ite)
416        {
417          // Rethrow the exceptions thrown be the invoked method.
418          Throwable e = ite.getTargetException();
419          Message message = ERR_CONFIG_LOGGER_INVALID_DEBUG_LOGGER_CLASS.get(
420              className, config.dn().toString(), stackTraceToSingleLineString(e));
421          throw new ConfigException(message, e);
422        }
423        catch (Exception e)
424        {
425          Message message = ERR_CONFIG_LOGGER_INVALID_DEBUG_LOGGER_CLASS.get(
426              className, config.dn().toString(), String.valueOf(e));
427          throw new ConfigException(message, e);
428        }
429    
430        // The debug publisher has been successfully initialized.
431        return debugLogPublisher;
432      }
433    
434      /**
435       * Update all debug tracers with the settings in the registered
436       * publishers.
437       */
438      static void updateTracerSettings()
439      {
440        DebugLogPublisher[] publishers =
441            debugPublishers.toArray(new DebugLogPublisher[0]);
442    
443        for(DebugTracer tracer : classTracers.values())
444        {
445          tracer.updateSettings(publishers);
446        }
447      }
448    
449      /**
450       * Indicates if debug logging is enabled.
451       *
452       * @return True if debug logging is enabled. False otherwise.
453       */
454      public static boolean debugEnabled()
455      {
456        return enabled;
457      }
458    
459      /**
460       * Retrieve the singleton instance of this class.
461       *
462       * @return The singleton instance of this logger.
463       */
464      public static DebugLogger getInstance()
465      {
466        return instance;
467      }
468    
469      /**
470       * Creates a new Debug Tracer for the caller class and registers it
471       * with the Debug Logger.
472       *
473       * @return The tracer created for the caller class.
474       */
475      public static DebugTracer getTracer()
476      {
477        DebugTracer tracer =
478            new DebugTracer(debugPublishers.toArray(new DebugLogPublisher[0]));
479        classTracers.put(tracer.getTracedClassName(), tracer);
480    
481        return tracer;
482      }
483    
484      /**
485       * Returns the registered Debug Tracer for a traced class.
486       *
487       * @param className The name of the class tracer to retrieve.
488       * @return The tracer for the provided class or null if there are
489       *         no tracers registered.
490       */
491      public static DebugTracer getTracer(String className)
492      {
493        return classTracers.get(className);
494      }
495    
496      /**
497       * Classes and methods annotated with @NoDebugTracing will not be weaved with
498       * debug logging statements by AspectJ.
499       */
500      public @interface NoDebugTracing {}
501    
502      /**
503       * Methods annotated with @NoEntryDebugTracing will not be weaved with
504       * entry debug logging statements by AspectJ.
505       */
506      public @interface NoEntryDebugTracing {}
507    
508      /**
509       * Methods annotated with @NoExitDebugTracing will not be weaved with
510       * exit debug logging statements by AspectJ.
511       */
512      public @interface NoExitDebugTracing {}
513    
514      /**
515       * Methods annotated with @TraceThrown will be weaved by AspectJ with
516       * debug logging statements when an exception is thrown from the method.
517       */
518      public @interface TraceThrown {}
519    
520    }