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