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                try {
139                    for (final LogEvent event : this.buffer) {
140                        this.writeInternal(event);
141                    }
142                } finally {
143                    this.commitAndClose();
144                    // not sure if this should be done when writing the events failed
145                    this.buffer.clear();
146                }
147            }
148        }
149    
150        /**
151         * This method manages buffering and writing of events.
152         *
153         * @param event The event to write to the database.
154         */
155        public final synchronized void write(final LogEvent event) {
156            if (this.bufferSize > 0) {
157                this.buffer.add(event);
158                if (this.buffer.size() >= this.bufferSize || event.isEndOfBatch()) {
159                    this.flush();
160                }
161            } else {
162                this.connectAndStart();
163                try {
164                    this.writeInternal(event);
165                } finally {
166                    this.commitAndClose();
167                }
168            }
169        }
170    
171        @Override
172        public final void releaseSub() {
173            this.shutdown();
174        }
175    
176        @Override
177        public final String toString() {
178            return this.getName();
179        }
180    
181        /**
182         * Implementations should define their own getManager method and call this method from that to create or get
183         * existing managers.
184         *
185         * @param name The manager name, which should include any configuration details that one might want to be able to
186         *             reconfigure at runtime, such as database name, username, (hashed) password, etc.
187         * @param data The concrete instance of {@link AbstractFactoryData} appropriate for the given manager.
188         * @param factory A factory instance for creating the appropriate manager.
189         * @param <M> The concrete manager type.
190         * @param <T> The concrete {@link AbstractFactoryData} type.
191         * @return a new or existing manager of the specified type and name.
192         */
193        protected static <M extends AbstractDatabaseManager, T extends AbstractFactoryData> M getManager(
194                final String name, final T data, final ManagerFactory<M, T> factory
195        ) {
196            return AbstractManager.getManager(name, factory, data);
197        }
198    
199        /**
200         * Implementations should extend this class for passing data between the getManager method and the manager factory
201         * class.
202         */
203        protected abstract static class AbstractFactoryData {
204            private final int bufferSize;
205    
206            /**
207             * Constructs the base factory data.
208             *
209             * @param bufferSize The size of the buffer.
210             */
211            protected AbstractFactoryData(final int bufferSize) {
212                this.bufferSize = bufferSize;
213            }
214    
215            /**
216             * Gets the buffer size.
217             *
218             * @return the buffer size.
219             */
220            public int getBufferSize() {
221                return bufferSize;
222            }
223        }
224    }