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 */
017package org.apache.logging.log4j.core.appender.db;
018
019import java.util.ArrayList;
020
021import org.apache.logging.log4j.core.LogEvent;
022import org.apache.logging.log4j.core.appender.AbstractManager;
023import org.apache.logging.log4j.core.appender.ManagerFactory;
024
025/**
026 * Manager that allows database appenders to have their configuration reloaded without losing events.
027 */
028public 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}