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    }