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 */
017package org.apache.logging.log4j.core.appender.db.jdbc;
018
019import java.sql.DriverManager;
020import java.sql.SQLException;
021import java.util.concurrent.TimeUnit;
022
023import org.apache.commons.dbcp2.ConnectionFactory;
024import org.apache.commons.dbcp2.DriverManagerConnectionFactory;
025import org.apache.commons.dbcp2.PoolableConnection;
026import org.apache.commons.dbcp2.PoolableConnectionFactory;
027import org.apache.commons.dbcp2.PoolingDriver;
028import org.apache.commons.pool2.ObjectPool;
029import org.apache.commons.pool2.impl.GenericObjectPool;
030import org.apache.logging.log4j.core.Core;
031import org.apache.logging.log4j.core.config.Property;
032import org.apache.logging.log4j.core.config.plugins.Plugin;
033import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
034
035/**
036 * A {@link ConnectionSource} that uses a JDBC connection string, a user name, and a password to call
037 * {@link DriverManager#getConnection(String, String, String)}. The connections are served from an
038 * <a href="http://commons.apache.org/proper/commons-dbcp/">Apache Commons DBCP</a> pooling driver.
039 */
040@Plugin(name = "PoolingDriver", category = Core.CATEGORY_NAME, elementType = "connectionSource", printObject = true)
041public final class PoolingDriverConnectionSource extends AbstractDriverManagerConnectionSource {
042
043    /**
044     * Builds PoolingDriverConnectionSource instances.
045     *
046     * @param <B>
047     *            This builder type or a subclass.
048     */
049    public static class Builder<B extends Builder<B>> extends AbstractDriverManagerConnectionSource.Builder<B>
050    implements org.apache.logging.log4j.core.util.Builder<PoolingDriverConnectionSource> {
051
052        public static final String DEFAULT_POOL_NAME = "example";
053        private String poolName = DEFAULT_POOL_NAME;
054
055        @Override
056        public PoolingDriverConnectionSource build() {
057            try {
058                return new PoolingDriverConnectionSource(getDriverClassName(), getConnectionString(), getUserName(),
059                        getPassword(), getProperties(), poolName);
060            } catch (final SQLException e) {
061                getLogger().error("Exception constructing {} to '{}'", getClass(), getConnectionString(), e);
062                return null;
063            }
064        }
065
066        public B setPoolName(final String poolName) {
067            this.poolName = poolName;
068            return asBuilder();
069        }
070    }
071
072    public static final String URL_PREFIX = "jdbc:apache:commons:dbcp:";
073
074    // This method is not named newBuilder() to make the compiler happy.
075    @PluginBuilderFactory
076    public static <B extends Builder<B>> B newPoolingDriverConnectionSourceBuilder() {
077        return new Builder<B>().asBuilder();
078    }
079
080    private final String poolingDriverClassName = "org.apache.commons.dbcp2.PoolingDriver";
081
082    private final String poolName;
083
084    public PoolingDriverConnectionSource(final String driverClassName, final String connectionString,
085            final char[] userName, final char[] password, final Property[] properties, final String poolName)
086            throws SQLException {
087        super(driverClassName, connectionString, URL_PREFIX + poolName, userName, password, properties);
088        this.poolName = poolName;
089        setupDriver(connectionString);
090    }
091
092    @Override
093    public String getActualConnectionString() {
094        // TODO Auto-generated method stub
095        return super.getActualConnectionString();
096    }
097
098    private PoolingDriver getPoolingDriver() throws SQLException {
099        final PoolingDriver driver = (PoolingDriver) DriverManager.getDriver(URL_PREFIX);
100        if (driver == null) {
101            getLogger().error("No JDBC driver for '{}'", URL_PREFIX);
102        }
103        return driver;
104    }
105
106    private void setupDriver(final String connectionString) throws SQLException {
107        //
108        // First, we'll create a ConnectionFactory that the
109        // pool will use to create Connections.
110        // We'll use the DriverManagerConnectionFactory,
111        // using the connect string passed in the command line
112        // arguments.
113        //
114        final ConnectionFactory connectionFactory = new DriverManagerConnectionFactory(connectionString, null);
115
116        //
117        // Next, we'll create the PoolableConnectionFactory, which wraps
118        // the "real" Connections created by the ConnectionFactory with
119        // the classes that implement the pooling functionality.
120        //
121        final PoolableConnectionFactory poolableConnectionFactory = new PoolableConnectionFactory(connectionFactory,
122                null);
123
124        //
125        // Now we'll need a ObjectPool that serves as the
126        // actual pool of connections.
127        //
128        // We'll use a GenericObjectPool instance, although
129        // any ObjectPool implementation will suffice.
130        //
131        @SuppressWarnings("resource")
132        // This GenericObjectPool will be closed on shutown
133        final ObjectPool<PoolableConnection> connectionPool = new GenericObjectPool<>(poolableConnectionFactory);
134
135        // Set the factory's pool property to the owning pool
136        poolableConnectionFactory.setPool(connectionPool);
137
138        loadDriver(poolingDriverClassName);
139        final PoolingDriver driver = getPoolingDriver();
140        if (driver != null) {
141            getLogger().debug("Registering DBCP pool '{}'", poolName);
142            driver.registerPool(poolName, connectionPool);
143        }
144        //
145        // Now we can just use the connect string "jdbc:apache:commons:dbcp:example"
146        // to access our pool of Connections.
147        //
148    }
149
150    @Override
151    public boolean stop(long timeout, TimeUnit timeUnit) {
152        try {
153            final PoolingDriver driver = getPoolingDriver();
154            if (driver != null) {
155                getLogger().debug("Closing DBCP pool '{}'", poolName);
156                driver.closePool(poolName);
157            }
158            return true;
159        } catch (Exception e) {
160            getLogger().error("Exception stopping connection source for '{}' → '{}'", getConnectionString(),
161                    getActualConnectionString(), e);
162            return false;
163        }
164    }
165}