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  package org.apache.logging.log4j.core.appender;
18  
19  import java.util.HashMap;
20  import java.util.Map;
21  import java.util.concurrent.TimeUnit;
22  import java.util.concurrent.locks.Lock;
23  import java.util.concurrent.locks.ReentrantLock;
24  
25  import org.apache.logging.log4j.Level;
26  import org.apache.logging.log4j.Logger;
27  import org.apache.logging.log4j.core.AbstractLifeCycle;
28  import org.apache.logging.log4j.core.LoggerContext;
29  import org.apache.logging.log4j.message.Message;
30  import org.apache.logging.log4j.status.StatusLogger;
31  
32  /**
33   * Abstract base class used to register managers.
34   * <p>
35   * This class implements {@link AutoCloseable} mostly to allow unit tests to be written safely and succinctly. While
36   * managers do need to allocate resources (usually on construction) and then free these resources, a manager is longer
37   * lived than other auto-closeable objects like streams. None the less, making a manager AutoCloseable forces readers to
38   * be aware of the the pattern: allocate resources on construction and call {@link #close()} at some point.
39   * </p>
40   */
41  public abstract class AbstractManager implements AutoCloseable {
42  
43      /**
44       * Allow subclasses access to the status logger without creating another instance.
45       */
46      protected static final Logger LOGGER = StatusLogger.getLogger();
47  
48      // Need to lock that map instead of using a ConcurrentMap due to stop removing the
49      // manager from the map and closing the stream, requiring the whole stop method to be locked.
50      private static final Map<String, AbstractManager> MAP = new HashMap<>();
51  
52      private static final Lock LOCK = new ReentrantLock();
53  
54      /**
55       * Number of Appenders using this manager.
56       */
57      protected int count;
58  
59      private final String name;
60      
61      private final LoggerContext loggerContext;
62  
63      protected AbstractManager(final LoggerContext loggerContext, final String name) {
64          this.loggerContext = loggerContext;
65          this.name = name;
66          LOGGER.debug("Starting {} {}", this.getClass().getSimpleName(), name);
67      }
68  
69      /**
70       * Called to signify that this Manager is no longer required by an Appender.
71       */
72      @Override
73      public void close() {
74          stop(AbstractLifeCycle.DEFAULT_STOP_TIMEOUT, AbstractLifeCycle.DEFAULT_STOP_TIMEUNIT);
75      }
76  
77      public boolean stop(final long timeout, final TimeUnit timeUnit) {
78          boolean stopped = true;
79          LOCK.lock();
80          try {
81              --count;
82              if (count <= 0) {
83                  MAP.remove(name);
84                  LOGGER.debug("Shutting down {} {}", this.getClass().getSimpleName(), getName());
85                  stopped = releaseSub(timeout, timeUnit);
86                  LOGGER.debug("Shut down {} {}, all resources released: {}", this.getClass().getSimpleName(), getName(), stopped);
87              }
88          } finally {
89              LOCK.unlock();
90          }
91          return stopped;
92      }
93  
94      /**
95       * Retrieves a Manager if it has been previously created or creates a new Manager.
96       * @param name The name of the Manager to retrieve.
97       * @param factory The Factory to use to create the Manager.
98       * @param data An Object that should be passed to the factory when creating the Manager.
99       * @param <M> The Type of the Manager to be created.
100      * @param <T> The type of the Factory data.
101      * @return A Manager with the specified name and type.
102      */
103     // @SuppressWarnings("resource"): this is a factory method, the resource is allocated and released elsewhere.
104     @SuppressWarnings("resource")
105     public static <M extends AbstractManager, T> M getManager(final String name, final ManagerFactory<M, T> factory,
106                                                               final T data) {
107         LOCK.lock();
108         try {
109             @SuppressWarnings("unchecked")
110             M manager = (M) MAP.get(name);
111             if (manager == null) {
112                 manager = factory.createManager(name, data);
113                 if (manager == null) {
114                     throw new IllegalStateException("ManagerFactory [" + factory + "] unable to create manager for ["
115                             + name + "] with data [" + data + "]");
116                 }
117                 MAP.put(name, manager);
118             } else {
119                 manager.updateData(data);
120             }
121             manager.count++;
122             return manager;
123         } finally {
124             LOCK.unlock();
125         }
126     }
127 
128     public void updateData(final Object data) {
129         // This default implementation does nothing.
130     }
131 
132     /**
133      * Determines if a Manager with the specified name exists.
134      * @param name The name of the Manager.
135      * @return True if the Manager exists, false otherwise.
136      */
137     public static boolean hasManager(final String name) {
138         LOCK.lock();
139         try {
140             return MAP.containsKey(name);
141         } finally {
142             LOCK.unlock();
143         }
144     }
145 
146     /**
147      * May be overridden by managers to perform processing while the manager is being released and the
148      * lock is held. A timeout is passed for implementors to use as they see fit.
149      * @param timeout timeout
150      * @param timeUnit timeout time unit
151      * @return true if all resources were closed normally, false otherwise.
152      */
153     protected boolean releaseSub(final long timeout, final TimeUnit timeUnit) {
154         // This default implementation does nothing.
155         return true;
156     }
157 
158     protected int getCount() {
159         return count;
160     }
161 
162     /**
163      * Gets the logger context used to create this instance or null. The logger context is usually set when an appender
164      * creates a manager and that appender is given a Configuration. Not all appenders are given a Configuration by
165      * their factory method or builder.
166      * 
167      * @return the logger context used to create this instance or null.
168      */
169     public LoggerContext getLoggerContext() {
170         return loggerContext;
171     }
172 
173     /**
174      * Called to signify that this Manager is no longer required by an Appender.
175      * @deprecated In 2.7, use {@link #close()}.
176      */
177     @Deprecated
178     public void release() {
179         close();
180     }
181 
182     /**
183      * Returns the name of the Manager.
184      * @return The name of the Manager.
185      */
186     public String getName() {
187         return name;
188     }
189 
190     /**
191      * Provide a description of the content format supported by this Manager.  Default implementation returns an empty
192      * (unspecified) Map.
193      *
194      * @return a Map of key/value pairs describing the Manager-specific content format, or an empty Map if no content
195      * format descriptors are specified.
196      */
197     public Map<String, String> getContentFormat() {
198         return new HashMap<>();
199     }
200 
201     protected void log(final Level level, final String message, final Throwable throwable) {
202         final Message m = LOGGER.getMessageFactory().newMessage("{} {} {}: {}",
203                 getClass().getSimpleName(), getName(), message, throwable);
204         LOGGER.log(level, m, throwable);
205     }
206 
207     protected void logDebug(final String message, final Throwable throwable) {
208         log(Level.DEBUG, message, throwable);
209     }
210 
211     protected void logError(final String message, final Throwable throwable) {
212         log(Level.ERROR, message, throwable);
213     }
214 
215     protected void logWarn(final String message, final Throwable throwable) {
216         log(Level.WARN, message, throwable);
217     }
218 
219 }