001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements. See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache license, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License. You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the license for the specific language governing permissions and
015     * limitations under the license.
016     */
017    package org.apache.logging.log4j.core.appender.db;
018    
019    import java.util.ArrayList;
020    
021    import org.apache.logging.log4j.core.LogEvent;
022    import org.apache.logging.log4j.core.appender.AbstractManager;
023    import org.apache.logging.log4j.core.appender.ManagerFactory;
024    
025    /**
026     * Manager that allows database appenders to have their configuration reloaded without losing events.
027     */
028    public abstract class AbstractDatabaseManager extends AbstractManager {
029        private final ArrayList<LogEvent> buffer;
030        private final int bufferSize;
031    
032        private boolean running = false;
033    
034        /**
035         * Instantiates the base manager.
036         *
037         * @param name The manager name, which should include any configuration details that one might want to be able to
038         *             reconfigure at runtime, such as database name, username, (hashed) password, etc.
039         * @param bufferSize The size of the log event buffer.
040         */
041        protected AbstractDatabaseManager(final String name, final int bufferSize) {
042            super(name);
043            this.bufferSize = bufferSize;
044            this.buffer = new ArrayList<LogEvent>(bufferSize + 1);
045        }
046    
047        /**
048         * Implementations should implement this method to perform any proprietary startup operations. This method will
049         * never be called twice on the same instance. It is safe to throw any exceptions from this method. This method
050         * does not necessarily connect to the database, as it is generally unreliable to connect once and use the same
051         * connection for hours.
052         */
053        protected abstract void startupInternal() throws Exception;
054    
055        /**
056         * This method is called within the appender when the appender is started. If it has not already been called, it
057         * calls {@link #startupInternal()} and catches any exceptions it might throw.
058         */
059        public final synchronized void startup() {
060            if (!this.isRunning()) {
061                try {
062                    this.startupInternal();
063                    this.running = true;
064                } catch (final Exception e) {
065                    LOGGER.error("Could not perform database startup operations using logging manager [{}].",
066                            this.getName(), e);
067                }
068            }
069        }
070    
071        /**
072         * Implementations should implement this method to perform any proprietary disconnection / shutdown operations. This
073         * method will never be called twice on the same instance, and it will only be called <em>after</em>
074         * {@link #startupInternal()}. It is safe to throw any exceptions from this method. This method does not
075         * necessarily disconnect from the database for the same reasons outlined in {@link #startupInternal()}.
076         */
077        protected abstract void shutdownInternal() throws Exception;
078    
079        /**
080         * This method is called from the {@link #release()} method when the appender is stopped or the appender's manager
081         * is replaced. If it has not already been called, it calls {@link #shutdownInternal()} and catches any exceptions
082         * it might throw.
083         */
084        public final synchronized void shutdown() {
085            this.flush();
086            if (this.isRunning()) {
087                try {
088                    this.shutdownInternal();
089                } catch (final Exception e) {
090                    LOGGER.warn("Error while performing database shutdown operations using logging manager [{}].",
091                            this.getName(), e);
092                } finally {
093                    this.running = false;
094                }
095            }
096        }
097    
098        /**
099         * Indicates whether the manager is currently connected {@link #startup()} has been called and {@link #shutdown()}
100         * has not been called).
101         *
102         * @return {@code true} if the manager is connected.
103         */
104        public final boolean isRunning() {
105            return this.running;
106        }
107    
108        /**
109         * Connects to the database and starts a transaction (if applicable). With buffering enabled, this is called when
110         * flushing the buffer begins, before the first call to {@link #writeInternal}. With buffering disabled, this is
111         * called immediately before every invocation of {@link #writeInternal}.
112         */
113        protected abstract void connectAndStart();
114    
115        /**
116         * Performs the actual writing of the event in an implementation-specific way. This method is called immediately
117         * from {@link #write(LogEvent)} if buffering is off, or from {@link #flush()} if the buffer has reached its limit.
118         *
119         * @param event The event to write to the database.
120         */
121        protected abstract void writeInternal(LogEvent event);
122    
123        /**
124         * Commits any active transaction (if applicable) and disconnects from the database (returns the connection to the
125         * connection pool). With buffering enabled, this is called when flushing the buffer completes, after the last call
126         * to {@link #writeInternal}. With buffering disabled, this is called immediately after every invocation of
127         * {@link #writeInternal}.
128         */
129        protected abstract void commitAndClose();
130    
131        /**
132         * This method is called automatically when the buffer size reaches its maximum or at the beginning of a call to
133         * {@link #shutdown()}. It can also be called manually to flush events to the database.
134         */
135        public final synchronized void flush() {
136            if (this.isRunning() && this.buffer.size() > 0) {
137                this.connectAndStart();
138                for (final LogEvent event : this.buffer) {
139                    this.writeInternal(event);
140                }
141                this.commitAndClose();
142                this.buffer.clear();
143            }
144        }
145    
146        /**
147         * This method manages buffering and writing of events.
148         *
149         * @param event The event to write to the database.
150         */
151        public final synchronized void write(final LogEvent event) {
152            if (this.bufferSize > 0) {
153                this.buffer.add(event);
154                if (this.buffer.size() >= this.bufferSize || event.isEndOfBatch()) {
155                    this.flush();
156                }
157            } else {
158                this.connectAndStart();
159                this.writeInternal(event);
160                this.commitAndClose();
161            }
162        }
163    
164        @Override
165        public final void releaseSub() {
166            this.shutdown();
167        }
168    
169        @Override
170        public final String toString() {
171            return this.getName();
172        }
173    
174        /**
175         * Implementations should define their own getManager method and call this method from that to create or get
176         * existing managers.
177         *
178         * @param name The manager name, which should include any configuration details that one might want to be able to
179         *             reconfigure at runtime, such as database name, username, (hashed) password, etc.
180         * @param data The concrete instance of {@link AbstractFactoryData} appropriate for the given manager.
181         * @param factory A factory instance for creating the appropriate manager.
182         * @param <M> The concrete manager type.
183         * @param <T> The concrete {@link AbstractFactoryData} type.
184         * @return a new or existing manager of the specified type and name.
185         */
186        protected static <M extends AbstractDatabaseManager, T extends AbstractFactoryData> M getManager(
187                final String name, final T data, final ManagerFactory<M, T> factory
188        ) {
189            return AbstractManager.getManager(name, factory, data);
190        }
191    
192        /**
193         * Implementations should extend this class for passing data between the getManager method and the manager factory
194         * class.
195         */
196        protected abstract static class AbstractFactoryData {
197            private final int bufferSize;
198    
199            /**
200             * Constructs the base factory data.
201             *
202             * @param bufferSize The size of the buffer.
203             */
204            protected AbstractFactoryData(final int bufferSize) {
205                this.bufferSize = bufferSize;
206            }
207    
208            /**
209             * Gets the buffer size.
210             *
211             * @return the buffer size.
212             */
213            public int getBufferSize() {
214                return bufferSize;
215            }
216        }
217    }