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;
022import java.util.concurrent.TimeUnit;
023
024import org.apache.logging.log4j.core.LogEvent;
025import org.apache.logging.log4j.core.appender.AbstractManager;
026import org.apache.logging.log4j.core.appender.ManagerFactory;
027
028/**
029 * Manager that allows database appenders to have their configuration reloaded without losing events.
030 */
031public abstract class AbstractDatabaseManager extends AbstractManager implements Flushable {
032    private final ArrayList<LogEvent> buffer;
033    private final int bufferSize;
034
035    private boolean running = false;
036
037    /**
038     * Instantiates the base manager.
039     *
040     * @param name The manager name, which should include any configuration details that one might want to be able to
041     *             reconfigure at runtime, such as database name, username, (hashed) password, etc.
042     * @param bufferSize The size of the log event buffer.
043     */
044    protected AbstractDatabaseManager(final String name, final int bufferSize) {
045        super(null, name);
046        this.bufferSize = bufferSize;
047        this.buffer = new ArrayList<>(bufferSize + 1);
048    }
049
050    /**
051     * Implementations should implement this method to perform any proprietary startup operations. This method will
052     * never be called twice on the same instance. It is safe to throw any exceptions from this method. This method
053     * does not necessarily connect to the database, as it is generally unreliable to connect once and use the same
054     * connection for hours.
055     */
056    protected abstract void startupInternal() throws Exception;
057
058    /**
059     * This method is called within the appender when the appender is started. If it has not already been called, it
060     * calls {@link #startupInternal()} and catches any exceptions it might throw.
061     */
062    public final synchronized void startup() {
063        if (!this.isRunning()) {
064            try {
065                this.startupInternal();
066                this.running = true;
067            } catch (final Exception e) {
068                logError("Could not perform database startup operations", e);
069            }
070        }
071    }
072
073    /**
074     * Implementations should implement this method to perform any proprietary disconnection / shutdown operations. This
075     * method will never be called twice on the same instance, and it will only be called <em>after</em>
076     * {@link #startupInternal()}. It is safe to throw any exceptions from this method. This method does not
077     * necessarily disconnect from the database for the same reasons outlined in {@link #startupInternal()}.
078     * @return true if all resources were closed normally, false otherwise.
079     */
080    protected abstract boolean shutdownInternal() throws Exception;
081
082    /**
083     * This method is called from the {@link #close()} method when the appender is stopped or the appender's manager
084     * is replaced. If it has not already been called, it calls {@link #shutdownInternal()} and catches any exceptions
085     * it might throw.
086     * @return true if all resources were closed normally, false otherwise.
087     */
088    public final synchronized boolean shutdown() {
089        boolean closed = true;
090        this.flush();
091        if (this.isRunning()) {
092            try {
093                closed &= this.shutdownInternal();
094            } catch (final Exception e) {
095                logWarn("Caught exception while performing database shutdown operations", e);
096                closed = false;
097            } finally {
098                this.running = false;
099            }
100        }
101        return closed;
102    }
103
104    /**
105     * Indicates whether the manager is currently connected {@link #startup()} has been called and {@link #shutdown()}
106     * has not been called).
107     *
108     * @return {@code true} if the manager is connected.
109     */
110    public final boolean isRunning() {
111        return this.running;
112    }
113
114    /**
115     * Connects to the database and starts a transaction (if applicable). With buffering enabled, this is called when
116     * flushing the buffer begins, before the first call to {@link #writeInternal}. With buffering disabled, this is
117     * called immediately before every invocation of {@link #writeInternal}.
118     */
119    protected abstract void connectAndStart();
120
121    /**
122     * Performs the actual writing of the event in an implementation-specific way. This method is called immediately
123     * from {@link #write(LogEvent)} if buffering is off, or from {@link #flush()} if the buffer has reached its limit.
124     *
125     * @param event The event to write to the database.
126     */
127    protected abstract void writeInternal(LogEvent event);
128
129    /**
130     * Commits any active transaction (if applicable) and disconnects from the database (returns the connection to the
131     * connection pool). With buffering enabled, this is called when flushing the buffer completes, after the last call
132     * to {@link #writeInternal}. With buffering disabled, this is called immediately after every invocation of
133     * {@link #writeInternal}.
134     * @return true if all resources were closed normally, false otherwise.
135     */
136    protected abstract boolean commitAndClose();
137
138    /**
139     * This method is called automatically when the buffer size reaches its maximum or at the beginning of a call to
140     * {@link #shutdown()}. It can also be called manually to flush events to the database.
141     */
142    @Override
143    public final synchronized void flush() {
144        if (this.isRunning() && this.buffer.size() > 0) {
145            this.connectAndStart();
146            try {
147                for (final LogEvent event : this.buffer) {
148                    this.writeInternal(event);
149                }
150            } finally {
151                this.commitAndClose();
152                // not sure if this should be done when writing the events failed
153                this.buffer.clear();
154            }
155        }
156    }
157
158    /**
159     * This method manages buffering and writing of events.
160     *
161     * @param event The event to write to the database.
162     */
163    public final synchronized void write(final LogEvent event) {
164        if (this.bufferSize > 0) {
165            this.buffer.add(event.toImmutable());
166            if (this.buffer.size() >= this.bufferSize || event.isEndOfBatch()) {
167                this.flush();
168            }
169        } else {
170            this.connectAndStart();
171            try {
172                this.writeInternal(event);
173            } finally {
174                this.commitAndClose();
175            }
176        }
177    }
178
179    @Override
180    public final boolean releaseSub(final long timeout, final TimeUnit timeUnit) {
181        return this.shutdown();
182    }
183
184    @Override
185    public final String toString() {
186        return this.getName();
187    }
188
189    /**
190     * Implementations should define their own getManager method and call this method from that to create or get
191     * existing managers.
192     *
193     * @param name The manager name, which should include any configuration details that one might want to be able to
194     *             reconfigure at runtime, such as database name, username, (hashed) password, etc.
195     * @param data The concrete instance of {@link AbstractFactoryData} appropriate for the given manager.
196     * @param factory A factory instance for creating the appropriate manager.
197     * @param <M> The concrete manager type.
198     * @param <T> The concrete {@link AbstractFactoryData} type.
199     * @return a new or existing manager of the specified type and name.
200     */
201    protected static <M extends AbstractDatabaseManager, T extends AbstractFactoryData> M getManager(
202            final String name, final T data, final ManagerFactory<M, T> factory
203    ) {
204        return AbstractManager.getManager(name, factory, data);
205    }
206
207    /**
208     * Implementations should extend this class for passing data between the getManager method and the manager factory
209     * class.
210     */
211    protected abstract static class AbstractFactoryData {
212        private final int bufferSize;
213
214        /**
215         * Constructs the base factory data.
216         *
217         * @param bufferSize The size of the buffer.
218         */
219        protected AbstractFactoryData(final int bufferSize) {
220            this.bufferSize = bufferSize;
221        }
222
223        /**
224         * Gets the buffer size.
225         *
226         * @return the buffer size.
227         */
228        public int getBufferSize() {
229            return bufferSize;
230        }
231    }
232}