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.db;
18  
19  import java.util.ArrayList;
20  
21  import org.apache.logging.log4j.core.LogEvent;
22  import org.apache.logging.log4j.core.appender.AbstractManager;
23  import org.apache.logging.log4j.core.appender.ManagerFactory;
24  
25  /**
26   * Manager that allows database appenders to have their configuration reloaded without losing events.
27   */
28  public abstract class AbstractDatabaseManager extends AbstractManager {
29      private final ArrayList<LogEvent> buffer;
30      private final int bufferSize;
31  
32      private boolean connected = false;
33  
34      /**
35       * Instantiates the base manager.
36       * 
37       * @param name The manager name, which should include any configuration details that one might want to be able to
38       *             reconfigure at runtime, such as database name, username, (hashed) password, etc.
39       * @param bufferSize The size of the log event buffer.
40       */
41      protected AbstractDatabaseManager(final String name, final int bufferSize) {
42          super(name);
43          this.bufferSize = bufferSize;
44          this.buffer = new ArrayList<LogEvent>(bufferSize + 1);
45      }
46  
47      /**
48       * Implementations should implement this method to perform any proprietary connection operations. This method will
49       * never be called twice on the same instance. It is safe to throw any exceptions from this method.
50       */
51      protected abstract void connectInternal();
52  
53      /**
54       * This method is called within the appender when the appender is started. If it has not already been called, it
55       * calls {@link #connectInternal()} and catches any exceptions it might throw.
56       */
57      public final synchronized void connect() {
58          if (!this.isConnected()) {
59              try {
60                  this.connectInternal();
61                  this.connected = true;
62              } catch (final Exception e) {
63                  LOGGER.error("Could not connect database logging manager.", e);
64              }
65          }
66      }
67  
68      /**
69       * Implementations should implement this method to perform any proprietary disconnection / shutdown operations. This
70       * method will never be called twice on the same instance, and it will only be called <em>after</em>
71       * {@link #connectInternal()}. It is safe to throw any exceptions from this method.
72       */
73      protected abstract void disconnectInternal();
74  
75      /**
76       * This method is called from the {@link #release()} method when the appender is stopped or the appender's manager
77       * is replaced. If it has not already been called, it calls {@link #disconnectInternal()} and catches any exceptions
78       * it might throw.
79       */
80      public final synchronized void disconnect() {
81          this.flush();
82          if (this.isConnected()) {
83              try {
84                  this.disconnectInternal();
85              } catch (final Exception e) {
86                  LOGGER.warn("Error while disconnecting database logging manager.", e);
87              } finally {
88                  this.connected = false;
89              }
90          }
91      }
92  
93      /**
94       * Indicates whether the manager is currently connected {@link #connect()} has been called and {@link #disconnect()}
95       * has not been called).
96       *
97       * @return {@code true} if the manager is connected.
98       */
99      public final boolean isConnected() {
100         return this.connected;
101     }
102 
103     /**
104      * Performs the actual writing of the event in an implementation-specific way. This method is called immediately
105      * from {@link #write(LogEvent)} if buffering is off, or from {@link #flush()} if the buffer has reached its limit.
106      *
107      * @param event The event to write to the database.
108      */
109     protected abstract void writeInternal(LogEvent event);
110 
111     /**
112      * This method is called automatically when the buffer size reaches its maximum or at the beginning of a call to
113      * {@link #disconnect()}. It can also be called manually to flush events to the database.
114      */
115     public final synchronized void flush() {
116         if (this.isConnected() && this.buffer.size() > 0) {
117             for (final LogEvent event : this.buffer) {
118                 this.writeInternal(event);
119             }
120             this.buffer.clear();
121         }
122     }
123 
124     /**
125      * This method manages buffering and writing of events.
126      *
127      * @param event The event to write to the database.
128      */
129     public final synchronized void write(final LogEvent event) {
130         if (this.bufferSize > 0) {
131             this.buffer.add(event);
132             if (this.buffer.size() >= this.bufferSize || event.isEndOfBatch()) {
133                 this.flush();
134             }
135         } else {
136             this.writeInternal(event);
137         }
138     }
139 
140     @Override
141     public final void releaseSub() {
142         this.disconnect();
143     }
144 
145     @Override
146     public final String toString() {
147         return this.getName();
148     }
149 
150     /**
151      * Implementations should define their own getManager method and call this method from that to create or get
152      * existing managers.
153      *
154      * @param name The manager name, which should include any configuration details that one might want to be able to
155      *             reconfigure at runtime, such as database name, username, (hashed) password, etc.
156      * @param data The concrete instance of {@link AbstractFactoryData} appropriate for the given manager.
157      * @param factory A factory instance for creating the appropriate manager.
158      * @param <M> The concrete manager type.
159      * @param <T> The concrete {@link AbstractFactoryData} type.
160      * @return a new or existing manager of the specified type and name.
161      */
162     protected static <M extends AbstractDatabaseManager, T extends AbstractFactoryData> M getManager(
163             final String name, final T data, final ManagerFactory<M, T> factory
164     ) {
165         return AbstractManager.getManager(name, factory, data);
166     }
167 
168     /**
169      * Implementations should extend this class for passing data between the getManager method and the manager factory
170      * class.
171      */
172     protected abstract static class AbstractFactoryData {
173         private final int bufferSize;
174 
175         /**
176          * Constructs the base factory data.
177          *
178          * @param bufferSize The size of the buffer.
179          */
180         protected AbstractFactoryData(final int bufferSize) {
181             this.bufferSize = bufferSize;
182         }
183 
184         /**
185          * Gets the buffer size.
186          *
187          * @return the buffer size.
188          */
189         public int getBufferSize() {
190             return bufferSize;
191         }
192     }
193 }