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<LogEvent>(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                LOGGER.error("Could not perform database startup operations using logging manager [{}].",
068                        this.getName(), 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     */
079    protected abstract void shutdownInternal() throws Exception;
080
081    /**
082     * This method is called from the {@link #release()} method when the appender is stopped or the appender's manager
083     * is replaced. If it has not already been called, it calls {@link #shutdownInternal()} and catches any exceptions
084     * it might throw.
085     */
086    public final synchronized void shutdown() {
087        this.flush();
088        if (this.isRunning()) {
089            try {
090                this.shutdownInternal();
091            } catch (final Exception e) {
092                LOGGER.warn("Error while performing database shutdown operations using logging manager [{}].",
093                        this.getName(), e);
094            } finally {
095                this.running = false;
096            }
097        }
098    }
099
100    /**
101     * Indicates whether the manager is currently connected {@link #startup()} has been called and {@link #shutdown()}
102     * has not been called).
103     *
104     * @return {@code true} if the manager is connected.
105     */
106    public final boolean isRunning() {
107        return this.running;
108    }
109
110    /**
111     * Connects to the database and starts a transaction (if applicable). With buffering enabled, this is called when
112     * flushing the buffer begins, before the first call to {@link #writeInternal}. With buffering disabled, this is
113     * called immediately before every invocation of {@link #writeInternal}.
114     */
115    protected abstract void connectAndStart();
116
117    /**
118     * Performs the actual writing of the event in an implementation-specific way. This method is called immediately
119     * from {@link #write(LogEvent)} if buffering is off, or from {@link #flush()} if the buffer has reached its limit.
120     *
121     * @param event The event to write to the database.
122     */
123    protected abstract void writeInternal(LogEvent event);
124
125    /**
126     * Commits any active transaction (if applicable) and disconnects from the database (returns the connection to the
127     * connection pool). With buffering enabled, this is called when flushing the buffer completes, after the last call
128     * to {@link #writeInternal}. With buffering disabled, this is called immediately after every invocation of
129     * {@link #writeInternal}.
130     */
131    protected abstract void commitAndClose();
132
133    /**
134     * This method is called automatically when the buffer size reaches its maximum or at the beginning of a call to
135     * {@link #shutdown()}. It can also be called manually to flush events to the database.
136     */
137    @Override
138    public final synchronized void flush() {
139        if (this.isRunning() && this.buffer.size() > 0) {
140            this.connectAndStart();
141            try {
142                for (final LogEvent event : this.buffer) {
143                    this.writeInternal(event);
144                }
145            } finally {
146                this.commitAndClose();
147                // not sure if this should be done when writing the events failed
148                this.buffer.clear();
149            }
150        }
151    }
152
153    /**
154     * This method manages buffering and writing of events.
155     *
156     * @param event The event to write to the database.
157     */
158    public final synchronized void write(final LogEvent event) {
159        if (this.bufferSize > 0) {
160            this.buffer.add(event);
161            if (this.buffer.size() >= this.bufferSize || event.isEndOfBatch()) {
162                this.flush();
163            }
164        } else {
165            this.connectAndStart();
166            try {
167                this.writeInternal(event);
168            } finally {
169                this.commitAndClose();
170            }
171        }
172    }
173
174    @Override
175    public final void releaseSub() {
176        this.shutdown();
177    }
178
179    @Override
180    public final String toString() {
181        return this.getName();
182    }
183
184    /**
185     * Implementations should define their own getManager method and call this method from that to create or get
186     * existing managers.
187     *
188     * @param name The manager name, which should include any configuration details that one might want to be able to
189     *             reconfigure at runtime, such as database name, username, (hashed) password, etc.
190     * @param data The concrete instance of {@link AbstractFactoryData} appropriate for the given manager.
191     * @param factory A factory instance for creating the appropriate manager.
192     * @param <M> The concrete manager type.
193     * @param <T> The concrete {@link AbstractFactoryData} type.
194     * @return a new or existing manager of the specified type and name.
195     */
196    protected static <M extends AbstractDatabaseManager, T extends AbstractFactoryData> M getManager(
197            final String name, final T data, final ManagerFactory<M, T> factory
198    ) {
199        return AbstractManager.getManager(name, factory, data);
200    }
201
202    /**
203     * Implementations should extend this class for passing data between the getManager method and the manager factory
204     * class.
205     */
206    protected abstract static class AbstractFactoryData {
207        private final int bufferSize;
208
209        /**
210         * Constructs the base factory data.
211         *
212         * @param bufferSize The size of the buffer.
213         */
214        protected AbstractFactoryData(final int bufferSize) {
215            this.bufferSize = bufferSize;
216        }
217
218        /**
219         * Gets the buffer size.
220         *
221         * @return the buffer size.
222         */
223        public int getBufferSize() {
224            return bufferSize;
225        }
226    }
227}