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