View Javadoc
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;
19  
20  import org.apache.log4j.helpers.LogLog;
21  import org.apache.log4j.or.ObjectRenderer;
22  import org.apache.log4j.or.RendererMap;
23  import org.apache.log4j.plugins.Plugin;
24  import org.apache.log4j.plugins.PluginRegistry;
25  import org.apache.log4j.scheduler.Scheduler;
26  import org.apache.log4j.spi.*;
27  import org.apache.log4j.xml.DOMConfigurator;
28  import org.apache.log4j.xml.UnrecognizedElementHandler;
29  import org.w3c.dom.Element;
30  
31  import java.util.*;
32  
33  
34  /**
35   * This class implements LoggerRepositoryEx by
36   * wrapping an existing LoggerRepository implementation
37   * and implementing the newly added capabilities.
38   */
39  public final class LoggerRepositoryExImpl
40      implements LoggerRepositoryEx,
41      RendererSupport,
42      UnrecognizedElementHandler {
43  
44      /**
45       * Wrapped logger repository.
46       */
47      private final LoggerRepository repo;
48  
49      /**
50       * Logger factory.  Does not affect class of logger
51       * created by underlying repository.
52       */
53      private LoggerFactory loggerFactory;
54  
55      /**
56       * Renderer support.
57       */
58      private final RendererSupport rendererSupport;
59  
60      /**
61       * List of repository event listeners.
62       */
63      private final ArrayList<LoggerRepositoryEventListener> repositoryEventListeners = new ArrayList<>();
64      /**
65       * Map of HierarchyEventListener keyed by LoggingEventListener.
66       */
67      private final Map<LoggerEventListener, HierarchyEventListenerProxy> loggerEventListeners = new HashMap<>();
68      /**
69       * Name of hierarchy.
70       */
71      private String name;
72      /**
73       * Plug in registry.
74       */
75      private PluginRegistry pluginRegistry;
76      /**
77       * Properties.
78       */
79      private final Map<String, String> properties = new Hashtable<>();
80      /**
81       * Scheduler.
82       */
83      private Scheduler scheduler;
84  
85      /**
86       * The repository can also be used as an object store
87       * for various objects used by log4j components.
88       */
89      private Map<String, Object> objectMap = new HashMap<>();
90  
91  
92      /**
93       * Error list.
94       */
95      private List<ErrorItem> errorList = new Vector<>();
96  
97      /**
98       * True if hierarchy has not been modified.
99       */
100     private boolean pristine = true;
101 
102     /**
103      * Constructs a new logger hierarchy.
104      *
105      * @param repository Base implementation of repository.
106      */
107     public LoggerRepositoryExImpl(final LoggerRepository repository) {
108         super();
109         if (repository == null) {
110             throw new NullPointerException("repository");
111         }
112         repo = repository;
113         if (repository instanceof RendererSupport) {
114             rendererSupport = (RendererSupport) repository;
115         } else {
116             rendererSupport = new RendererSupportImpl();
117         }
118     }
119 
120 
121     /**
122      * Add a {@link LoggerRepositoryEventListener} to the repository. The
123      * listener will be called when repository events occur.
124      *
125      * @param listener listener
126      */
127     public void addLoggerRepositoryEventListener(
128         final LoggerRepositoryEventListener listener) {
129         synchronized (repositoryEventListeners) {
130             if (repositoryEventListeners.contains(listener)) {
131                 LogLog.warn(
132                     "Ignoring attempt to add a previously "
133                         + "registered LoggerRepositoryEventListener.");
134             } else {
135                 repositoryEventListeners.add(listener);
136             }
137         }
138     }
139 
140 
141     /**
142      * Remove a {@link LoggerRepositoryEventListener} from the repository.
143      *
144      * @param listener listener
145      */
146     public void removeLoggerRepositoryEventListener(
147         final LoggerRepositoryEventListener listener) {
148         synchronized (repositoryEventListeners) {
149             if (!repositoryEventListeners.contains(listener)) {
150                 LogLog.warn(
151                     "Ignoring attempt to remove a "
152                         + "non-registered LoggerRepositoryEventListener.");
153             } else {
154                 repositoryEventListeners.remove(listener);
155             }
156         }
157     }
158 
159     /**
160      * Add a {@link LoggerEventListener} to the repository. The  listener
161      * will be called when repository events occur.
162      *
163      * @param listener listener
164      */
165     public void addLoggerEventListener(final LoggerEventListener listener) {
166         synchronized (loggerEventListeners) {
167             if (loggerEventListeners.get(listener) != null) {
168                 LogLog.warn(
169                     "Ignoring attempt to add a previously registerd LoggerEventListener.");
170             } else {
171                 HierarchyEventListenerProxy proxy =
172                     new HierarchyEventListenerProxy(listener);
173                 loggerEventListeners.put(listener, proxy);
174                 repo.addHierarchyEventListener(proxy);
175             }
176         }
177     }
178 
179     /**
180      * Add a {@link org.apache.log4j.spi.HierarchyEventListener}
181      * event to the repository.
182      *
183      * @param listener listener
184      * @deprecated Superceded by addLoggerEventListener
185      */
186     public void addHierarchyEventListener(final HierarchyEventListener listener) {
187         repo.addHierarchyEventListener(listener);
188     }
189 
190 
191     /**
192      * Remove a {@link LoggerEventListener} from the repository.
193      *
194      * @param listener listener to be removed
195      */
196     public void removeLoggerEventListener(final LoggerEventListener listener) {
197         synchronized (loggerEventListeners) {
198             HierarchyEventListenerProxy proxy =
199                 loggerEventListeners.get(listener);
200             if (proxy == null) {
201                 LogLog.warn(
202                     "Ignoring attempt to remove a non-registered LoggerEventListener.");
203             } else {
204                 loggerEventListeners.remove(listener);
205                 proxy.disable();
206             }
207         }
208     }
209 
210     /**
211      * Issue warning that there are no appenders in hierarchy.
212      *
213      * @param cat logger, not currently used.
214      */
215     public void emitNoAppenderWarning(final Category cat) {
216         repo.emitNoAppenderWarning(cat);
217     }
218 
219     /**
220      * Check if the named logger exists in the hierarchy. If so return
221      * its reference, otherwise returns <code>null</code>.
222      *
223      * @param loggerName The name of the logger to search for.
224      * @return true if logger exists.
225      */
226     public Logger exists(final String loggerName) {
227         return repo.exists(loggerName);
228     }
229 
230     /**
231      * Return the name of this hierarchy.
232      *
233      * @return name of hierarchy
234      */
235     public String getName() {
236         return name;
237     }
238 
239     /**
240      * Set the name of this repository.
241      * <p>
242      * Note that once named, a repository cannot be rerenamed.
243      *
244      * @param repoName name of hierarchy
245      */
246     public void setName(final String repoName) {
247         if (name == null) {
248             name = repoName;
249         } else if (!name.equals(repoName)) {
250             throw new IllegalStateException(
251                 "Repository [" + name + "] cannot be renamed as [" + repoName + "].");
252         }
253     }
254 
255     /**
256      * {@inheritDoc}
257      */
258     public Map<String, String> getProperties() {
259         return properties;
260     }
261 
262     /**
263      * {@inheritDoc}
264      */
265     public String getProperty(final String key) {
266         return properties.get(key);
267     }
268 
269     /**
270      * Set a property by key and value. The property will be shared by all
271      * events in this repository.
272      *
273      * @param key   property name
274      * @param value property value
275      */
276     public void setProperty(final String key,
277                             final String value) {
278         properties.put(key, value);
279     }
280 
281     /**
282      * The string form of {@link #setThreshold(Level)}.
283      *
284      * @param levelStr symbolic name for level
285      */
286     public void setThreshold(final String levelStr) {
287         repo.setThreshold(levelStr);
288     }
289 
290     /**
291      * Enable logging for logging requests with level <code>l</code> or
292      * higher. By default all levels are enabled.
293      *
294      * @param l The minimum level for which logging requests are sent to
295      *          their appenders.
296      */
297     public void setThreshold(final Level l) {
298         repo.setThreshold(l);
299     }
300 
301     /**
302      * {@inheritDoc}
303      */
304     public PluginRegistry getPluginRegistry() {
305         if (pluginRegistry == null) {
306             pluginRegistry = new PluginRegistry(this);
307         }
308         return pluginRegistry;
309     }
310 
311 
312     /**
313      * Requests that a appender added event be sent to any registered
314      * {@link LoggerEventListener}.
315      *
316      * @param logger   The logger to which the appender was added.
317      * @param appender The appender added to the logger.
318      */
319     public void fireAddAppenderEvent(final Category logger,
320                                      final Appender appender) {
321         repo.fireAddAppenderEvent(logger, appender);
322     }
323 
324 
325     /**
326      * Requests that a appender removed event be sent to any registered
327      * {@link LoggerEventListener}.
328      *
329      * @param logger   The logger from which the appender was removed.
330      * @param appender The appender removed from the logger.
331      */
332     public void fireRemoveAppenderEvent(final Category logger,
333                                         final Appender appender) {
334         if (repo instanceof Hierarchy) {
335             ((Hierarchy) repo).fireRemoveAppenderEvent(logger, appender);
336         }
337     }
338 
339 
340     /**
341      * Requests that a level changed event be sent to any registered
342      * {@link LoggerEventListener}.
343      *
344      * @param logger The logger which changed levels.
345      */
346     public void fireLevelChangedEvent(final Logger logger) {
347     }
348 
349     /**
350      * Requests that a configuration changed event be sent to any registered
351      * {@link LoggerRepositoryEventListener}.
352      */
353     public void fireConfigurationChangedEvent() {
354     }
355 
356 
357     /**
358      * Returns the current threshold.
359      *
360      * @return current threshold level
361      * @since 1.2
362      */
363     public Level getThreshold() {
364         return repo.getThreshold();
365     }
366 
367 
368     /**
369      * Return a new logger instance named as the first parameter using
370      * the default factory.
371      * <p>
372      * <p>If a logger of that name already exists, then it will be
373      * returned.  Otherwise, a new logger will be instantiated and
374      * then linked with its existing ancestors as well as children.
375      *
376      * @param loggerName The name of the logger to retrieve.
377      * @return logger
378      */
379     public Logger getLogger(final String loggerName) {
380         return repo.getLogger(loggerName);
381     }
382 
383     /**
384      * Return a new logger instance named as the first parameter using
385      * <code>factory</code>.
386      * <p>
387      * <p>If a logger of that name already exists, then it will be
388      * returned.  Otherwise, a new logger will be instantiated by the
389      * <code>factory</code> parameter and linked with its existing
390      * ancestors as well as children.
391      *
392      * @param loggerName The name of the logger to retrieve.
393      * @param factory    The factory that will make the new logger instance.
394      * @return logger
395      */
396     public Logger getLogger(final String loggerName,
397                             final LoggerFactory factory) {
398         return repo.getLogger(loggerName, factory);
399     }
400 
401     /**
402      * Returns all the currently defined categories in this hierarchy as
403      * an {@link java.util.Enumeration Enumeration}.
404      * <p>
405      * <p>The root logger is <em>not</em> included in the returned
406      * {@link Enumeration}.
407      *
408      * @return enumerator of current loggers
409      */
410     public Enumeration getCurrentLoggers() {
411         return repo.getCurrentLoggers();
412     }
413 
414     /**
415      * Return the the list of previously encoutered {@link ErrorItem error items}.
416      *
417      * @return list of errors
418      */
419     public List<ErrorItem> getErrorList() {
420         return errorList;
421     }
422 
423     /**
424      * Add an error item to the list of previously encountered errors.
425      *
426      * @param errorItem error to add to list of errors.
427      */
428     public void addErrorItem(final ErrorItem errorItem) {
429         getErrorList().add(errorItem);
430     }
431 
432     /**
433      * Get enumerator over current loggers.
434      *
435      * @return enumerator over current loggers
436      * @deprecated Please use {@link #getCurrentLoggers} instead.
437      */
438     public Enumeration getCurrentCategories() {
439         return repo.getCurrentCategories();
440     }
441 
442     /**
443      * Get the renderer map for this hierarchy.
444      *
445      * @return renderer map
446      */
447     public RendererMap getRendererMap() {
448         return rendererSupport.getRendererMap();
449     }
450 
451     /**
452      * Get the root of this hierarchy.
453      *
454      * @return root of hierarchy
455      * @since 0.9.0
456      */
457     public Logger getRootLogger() {
458         return repo.getRootLogger();
459     }
460 
461     /**
462      * This method will return <code>true</code> if this repository is
463      * disabled for <code>level</code> value passed as parameter and
464      * <code>false</code> otherwise. See also the {@link
465      * #setThreshold(Level) threshold} method.
466      *
467      * @param level numeric value for level.
468      * @return true if disabled for specified level
469      */
470     public boolean isDisabled(final int level) {
471         return repo.isDisabled(level);
472     }
473 
474     /**
475      * Reset all values contained in this hierarchy instance to their
476      * default.  This removes all appenders from all categories, sets
477      * the level of all non-root categories to <code>null</code>,
478      * sets their additivity flag to <code>true</code> and sets the level
479      * of the root logger to DEBUG.  Moreover,
480      * message disabling is set its default "off" value.
481      * <p>
482      * <p>Existing categories are not removed. They are just reset.
483      * <p>
484      * <p>This method should be used sparingly and with care as it will
485      * block all logging until it is completed.</p>
486      *
487      * @since 0.8.5
488      */
489     public void resetConfiguration() {
490         repo.resetConfiguration();
491     }
492 
493     /**
494      * Used by subclasses to add a renderer to the hierarchy passed as parameter.
495      *
496      * @param renderedClass class
497      * @param renderer      object used to render class.
498      */
499     public void setRenderer(final Class renderedClass,
500                             final ObjectRenderer renderer) {
501         rendererSupport.setRenderer(renderedClass, renderer);
502     }
503 
504     /**
505      * {@inheritDoc}
506      */
507     public boolean isPristine() {
508         return pristine;
509     }
510 
511     /**
512      * {@inheritDoc}
513      */
514     public void setPristine(final boolean state) {
515         pristine = state;
516     }
517 
518     /**
519      * Shutting down a hierarchy will <em>safely</em> close and remove
520      * all appenders in all categories including the root logger.
521      * <p>
522      * <p>Some appenders such as org.apache.log4j.net.SocketAppender
523      * and AsyncAppender need to be closed before the
524      * application exists. Otherwise, pending logging events might be
525      * lost.
526      * <p>
527      * <p>The <code>shutdown</code> method is careful to close nested
528      * appenders before closing regular appenders. This is allows
529      * configurations where a regular appender is attached to a logger
530      * and again to a nested appender.
531      *
532      * @since 1.0
533      */
534     public void shutdown() {
535         repo.shutdown();
536     }
537 
538 
539     /**
540      * Return this repository's own scheduler.
541      * The scheduler is lazily instantiated.
542      *
543      * @return this repository's own scheduler.
544      */
545     public Scheduler getScheduler() {
546         if (scheduler == null) {
547             scheduler = new Scheduler();
548             scheduler.setDaemon(true);
549             scheduler.start();
550         }
551         return scheduler;
552     }
553 
554     /**
555      * Puts object by key.
556      *
557      * @param key   key, may not be null.
558      * @param value object to associate with key.
559      */
560     public void putObject(final String key,
561                           final Object value) {
562         objectMap.put(key, value);
563     }
564 
565     /**
566      * Get object by key.
567      *
568      * @param key key, may not be null.
569      * @return object associated with key or null.
570      */
571     public Object getObject(final String key) {
572         return objectMap.get(key);
573     }
574 
575     /**
576      * Set logger factory.
577      *
578      * @param factory logger factory.
579      */
580     public void setLoggerFactory(final LoggerFactory factory) {
581         if (factory == null) {
582             throw new NullPointerException();
583         }
584         this.loggerFactory = factory;
585     }
586 
587     /**
588      * Get logger factory.
589      *
590      * @return logger factory.
591      */
592     public LoggerFactory getLoggerFactory() {
593         return loggerFactory;
594     }
595 
596     /**
597      * {@inheritDoc}
598      */
599     public boolean parseUnrecognizedElement(
600         final Element element,
601         final Properties props) throws Exception {
602         if ("plugin".equals(element.getNodeName())) {
603             Object instance =
604                 DOMConfigurator.parseElement(element, props, Plugin.class);
605             if (instance instanceof Plugin) {
606                 Plugin plugin = (Plugin) instance;
607                 String pluginName = DOMConfigurator.subst(element.getAttribute("name"), props);
608                 if (pluginName.length() > 0) {
609                     plugin.setName(pluginName);
610                 }
611                 getPluginRegistry().addPlugin(plugin);
612                 plugin.setLoggerRepository(this);
613 
614                 LogLog.debug("Pushing plugin on to the object stack.");
615                 plugin.activateOptions();
616                 return true;
617             }
618         }
619         return false;
620     }
621 
622 
623     /**
624      * Implementation of RendererSupportImpl if not
625      * provided by LoggerRepository.
626      */
627     private static final class RendererSupportImpl implements RendererSupport {
628         /**
629          * Renderer map.
630          */
631         private final RendererMap renderers = new RendererMap();
632 
633         /**
634          * Create new instance.
635          */
636         public RendererSupportImpl() {
637             super();
638         }
639 
640         /**
641          * {@inheritDoc}
642          */
643         public RendererMap getRendererMap() {
644             return renderers;
645         }
646 
647         /**
648          * {@inheritDoc}
649          */
650         public void setRenderer(final Class renderedClass,
651                                 final ObjectRenderer renderer) {
652             renderers.put(renderedClass, renderer);
653         }
654     }
655 
656     /**
657      * Proxy that implements HierarchyEventListener
658      * and delegates to LoggerEventListener.
659      */
660     private static final class HierarchyEventListenerProxy
661         implements HierarchyEventListener {
662         /**
663          * Wrapper listener.
664          */
665         private LoggerEventListener listener;
666 
667         /**
668          * Creates new instance.
669          *
670          * @param l listener
671          */
672         public HierarchyEventListenerProxy(final LoggerEventListener l) {
673             super();
674             if (l == null) {
675                 throw new NullPointerException("l");
676             }
677             listener = l;
678         }
679 
680         /**
681          * {@inheritDoc}
682          */
683         public void addAppenderEvent(final Category cat,
684                                      final Appender appender) {
685             if (isEnabled() && cat instanceof Logger) {
686                 listener.appenderAddedEvent((Logger) cat, appender);
687             }
688         }
689 
690         /**
691          * {@inheritDoc}
692          */
693         public void removeAppenderEvent(final Category cat,
694                                         final Appender appender) {
695             if (isEnabled() && cat instanceof Logger) {
696                 listener.appenderRemovedEvent((Logger) cat, appender);
697             }
698         }
699 
700         /**
701          * Disable forwarding of notifications to
702          * simulate removal of listener.
703          */
704         public synchronized void disable() {
705             listener = null;
706         }
707 
708         /**
709          * Gets whether proxy is enabled.
710          *
711          * @return true if proxy is enabled.
712          */
713         private synchronized boolean isEnabled() {
714             return listener != null;
715         }
716     }
717 
718 }