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