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
018package org.apache.logging.log4j.core.appender.db;
019
020import java.io.Flushable;
021import java.util.ArrayList;
022
023import org.apache.logging.log4j.core.LogEvent;
024import org.apache.logging.log4j.core.appender.AbstractManager;
025import org.apache.logging.log4j.core.appender.ManagerFactory;
026
027/**
028 * Manager that allows database appenders to have their configuration reloaded without losing events.
029 */
030public abstract class AbstractDatabaseManager extends AbstractManager implements Flushable {
031    private final ArrayList<LogEvent> buffer;
032    private final int bufferSize;
033
034    private boolean running = false;
035
036    /**
037     * Instantiates the base manager.
038     *
039     * @param name The manager name, which should include any configuration details that one might want to be able to
040     *             reconfigure at runtime, such as database name, username, (hashed) password, etc.
041     * @param bufferSize The size of the log event buffer.
042     */
043    protected AbstractDatabaseManager(final String name, final int bufferSize) {
044        super(name);
045        this.bufferSize = bufferSize;
046        this.buffer = new ArrayList<>(bufferSize + 1);
047    }
048
049    /**
050     * Implementations should implement this method to perform any proprietary startup operations. This method will
051     * never be called twice on the same instance. It is safe to throw any exceptions from this method. This method
052     * does not necessarily connect to the database, as it is generally unreliable to connect once and use the same
053     * connection for hours.
054     */
055    protected abstract void startupInternal() throws Exception;
056
057    /**
058     * This method is called within the appender when the appender is started. If it has not already been called, it
059     * calls {@link #startupInternal()} and catches any exceptions it might throw.
060     */
061    public final synchronized void startup() {
062        if (!this.isRunning()) {
063            try {
064                this.startupInternal();
065                this.running = true;
066            } catch (final Exception e) {
067                logError("could not perform database startup operations", e);
068            }
069        }
070    }
071
072    /**
073     * Implementations should implement this method to perform any proprietary disconnection / shutdown operations. This
074     * method will never be called twice on the same instance, and it will only be called <em>after</em>
075     * {@link #startupInternal()}. It is safe to throw any exceptions from this method. This method does not
076     * necessarily disconnect from the database for the same reasons outlined in {@link #startupInternal()}.
077     */
078    protected abstract void shutdownInternal() throws Exception;
079
080    /**
081     * This method is called from the {@link #release()} method when the appender is stopped or the appender's manager
082     * is replaced. If it has not already been called, it calls {@link #shutdownInternal()} and catches any exceptions
083     * it might throw.
084     */
085    public final synchronized void shutdown() {
086        this.flush();
087        if (this.isRunning()) {
088            try {
089                this.shutdownInternal();
090            } catch (final Exception e) {
091                logWarn("caught exception while performing database shutdown operations", 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    @Override
136    public final synchronized void flush() {
137        if (this.isRunning() && this.buffer.size() > 0) {
138            this.connectAndStart();
139            try {
140                for (final LogEvent event : this.buffer) {
141                    this.writeInternal(event);
142                }
143            } finally {
144                this.commitAndClose();
145                // not sure if this should be done when writing the events failed
146                this.buffer.clear();
147            }
148        }
149    }
150
151    /**
152     * This method manages buffering and writing of events.
153     *
154     * @param event The event to write to the database.
155     */
156    public final synchronized void write(final LogEvent event) {
157        if (this.bufferSize > 0) {
158            this.buffer.add(event);
159            if (this.buffer.size() >= this.bufferSize || event.isEndOfBatch()) {
160                this.flush();
161            }
162        } else {
163            this.connectAndStart();
164            try {
165                this.writeInternal(event);
166            } finally {
167                this.commitAndClose();
168            }
169        }
170    }
171
172    @Override
173    public final void releaseSub() {
174        this.shutdown();
175    }
176
177    @Override
178    public final String toString() {
179        return this.getName();
180    }
181
182    /**
183     * Implementations should define their own getManager method and call this method from that to create or get
184     * existing managers.
185     *
186     * @param name The manager name, which should include any configuration details that one might want to be able to
187     *             reconfigure at runtime, such as database name, username, (hashed) password, etc.
188     * @param data The concrete instance of {@link AbstractFactoryData} appropriate for the given manager.
189     * @param factory A factory instance for creating the appropriate manager.
190     * @param <M> The concrete manager type.
191     * @param <T> The concrete {@link AbstractFactoryData} type.
192     * @return a new or existing manager of the specified type and name.
193     */
194    protected static <M extends AbstractDatabaseManager, T extends AbstractFactoryData> M getManager(
195            final String name, final T data, final ManagerFactory<M, T> factory
196    ) {
197        return AbstractManager.getManager(name, factory, data);
198    }
199
200    /**
201     * Implementations should extend this class for passing data between the getManager method and the manager factory
202     * class.
203     */
204    protected abstract static class AbstractFactoryData {
205        private final int bufferSize;
206
207        /**
208         * Constructs the base factory data.
209         *
210         * @param bufferSize The size of the buffer.
211         */
212        protected AbstractFactoryData(final int bufferSize) {
213            this.bufferSize = bufferSize;
214        }
215
216        /**
217         * Gets the buffer size.
218         *
219         * @return the buffer size.
220         */
221        public int getBufferSize() {
222            return bufferSize;
223        }
224    }
225}