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}