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    package org.apache.logging.log4j.core.appender.db;
018    
019    import java.util.ArrayList;
020    
021    import org.apache.logging.log4j.core.LogEvent;
022    import org.apache.logging.log4j.core.appender.AbstractManager;
023    import org.apache.logging.log4j.core.appender.ManagerFactory;
024    
025    /**
026     * Manager that allows database appenders to have their configuration reloaded without losing events.
027     */
028    public abstract class AbstractDatabaseManager extends AbstractManager {
029        private final ArrayList<LogEvent> buffer;
030        private final int bufferSize;
031    
032        private boolean connected = false;
033    
034        /**
035         * Instantiates the base manager.
036         * 
037         * @param name The manager name, which should include any configuration details that one might want to be able to
038         *             reconfigure at runtime, such as database name, username, (hashed) password, etc.
039         * @param bufferSize The size of the log event buffer.
040         */
041        protected AbstractDatabaseManager(final String name, final int bufferSize) {
042            super(name);
043            this.bufferSize = bufferSize;
044            this.buffer = new ArrayList<LogEvent>(bufferSize + 1);
045        }
046    
047        /**
048         * Implementations should implement this method to perform any proprietary connection operations. This method will
049         * never be called twice on the same instance. It is safe to throw any exceptions from this method.
050         */
051        protected abstract void connectInternal();
052    
053        /**
054         * This method is called within the appender when the appender is started. If it has not already been called, it
055         * calls {@link #connectInternal()} and catches any exceptions it might throw.
056         */
057        public final synchronized void connect() {
058            if (!this.isConnected()) {
059                try {
060                    this.connectInternal();
061                    this.connected = true;
062                } catch (final Exception e) {
063                    LOGGER.error("Could not connect database logging manager.", e);
064                }
065            }
066        }
067    
068        /**
069         * Implementations should implement this method to perform any proprietary disconnection / shutdown operations. This
070         * method will never be called twice on the same instance, and it will only be called <em>after</em>
071         * {@link #connectInternal()}. It is safe to throw any exceptions from this method.
072         */
073        protected abstract void disconnectInternal();
074    
075        /**
076         * This method is called from the {@link #release()} method when the appender is stopped or the appender's manager
077         * is replaced. If it has not already been called, it calls {@link #disconnectInternal()} and catches any exceptions
078         * it might throw.
079         */
080        public final synchronized void disconnect() {
081            this.flush();
082            if (this.isConnected()) {
083                try {
084                    this.disconnectInternal();
085                } catch (final Exception e) {
086                    LOGGER.warn("Error while disconnecting database logging manager.", e);
087                } finally {
088                    this.connected = false;
089                }
090            }
091        }
092    
093        /**
094         * Indicates whether the manager is currently connected {@link #connect()} has been called and {@link #disconnect()}
095         * has not been called).
096         *
097         * @return {@code true} if the manager is connected.
098         */
099        public final boolean isConnected() {
100            return this.connected;
101        }
102    
103        /**
104         * Performs the actual writing of the event in an implementation-specific way. This method is called immediately
105         * from {@link #write(LogEvent)} if buffering is off, or from {@link #flush()} if the buffer has reached its limit.
106         *
107         * @param event The event to write to the database.
108         */
109        protected abstract void writeInternal(LogEvent event);
110    
111        /**
112         * This method is called automatically when the buffer size reaches its maximum or at the beginning of a call to
113         * {@link #disconnect()}. It can also be called manually to flush events to the database.
114         */
115        public final synchronized void flush() {
116            if (this.isConnected() && this.buffer.size() > 0) {
117                for (final LogEvent event : this.buffer) {
118                    this.writeInternal(event);
119                }
120                this.buffer.clear();
121            }
122        }
123    
124        /**
125         * This method manages buffering and writing of events.
126         *
127         * @param event The event to write to the database.
128         */
129        public final synchronized void write(final LogEvent event) {
130            if (this.bufferSize > 0) {
131                this.buffer.add(event);
132                if (this.buffer.size() >= this.bufferSize || event.isEndOfBatch()) {
133                    this.flush();
134                }
135            } else {
136                this.writeInternal(event);
137            }
138        }
139    
140        @Override
141        public final void releaseSub() {
142            this.disconnect();
143        }
144    
145        @Override
146        public final String toString() {
147            return this.getName();
148        }
149    
150        /**
151         * Implementations should define their own getManager method and call this method from that to create or get
152         * existing managers.
153         *
154         * @param name The manager name, which should include any configuration details that one might want to be able to
155         *             reconfigure at runtime, such as database name, username, (hashed) password, etc.
156         * @param data The concrete instance of {@link AbstractFactoryData} appropriate for the given manager.
157         * @param factory A factory instance for creating the appropriate manager.
158         * @param <M> The concrete manager type.
159         * @param <T> The concrete {@link AbstractFactoryData} type.
160         * @return a new or existing manager of the specified type and name.
161         */
162        protected static <M extends AbstractDatabaseManager, T extends AbstractFactoryData> M getManager(
163                final String name, final T data, final ManagerFactory<M, T> factory
164        ) {
165            return AbstractManager.getManager(name, factory, data);
166        }
167    
168        /**
169         * Implementations should extend this class for passing data between the getManager method and the manager factory
170         * class.
171         */
172        protected abstract static class AbstractFactoryData {
173            private final int bufferSize;
174    
175            /**
176             * Constructs the base factory data.
177             *
178             * @param bufferSize The size of the buffer.
179             */
180            protected AbstractFactoryData(final int bufferSize) {
181                this.bufferSize = bufferSize;
182            }
183    
184            /**
185             * Gets the buffer size.
186             *
187             * @return the buffer size.
188             */
189            public int getBufferSize() {
190                return bufferSize;
191            }
192        }
193    }