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