View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core.appender.db.jdbc;
18  
19  import java.sql.DriverManager;
20  import java.sql.SQLException;
21  import java.util.Arrays;
22  import java.util.concurrent.TimeUnit;
23  
24  import org.apache.commons.dbcp2.ConnectionFactory;
25  import org.apache.commons.dbcp2.DriverManagerConnectionFactory;
26  import org.apache.commons.dbcp2.PoolableConnection;
27  import org.apache.commons.dbcp2.PoolableConnectionFactory;
28  import org.apache.commons.dbcp2.PoolingDriver;
29  import org.apache.commons.pool2.ObjectPool;
30  import org.apache.commons.pool2.impl.GenericObjectPool;
31  import org.apache.logging.log4j.core.Core;
32  import org.apache.logging.log4j.core.config.Property;
33  import org.apache.logging.log4j.core.config.plugins.Plugin;
34  import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
35  import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
36  import org.apache.logging.log4j.core.config.plugins.PluginElement;
37  
38  /**
39   * A {@link ConnectionSource} that uses a JDBC connection string, a user name, and a password to call
40   * {@link DriverManager#getConnection(String, String, String)}. The connections are served from an
41   * <a href="http://commons.apache.org/proper/commons-dbcp/">Apache Commons DBCP</a> pooling driver.
42   */
43  @Plugin(name = "PoolingDriver", category = Core.CATEGORY_NAME, elementType = "connectionSource", printObject = true)
44  public final class PoolingDriverConnectionSource extends AbstractDriverManagerConnectionSource {
45  
46      /**
47       * Builds PoolingDriverConnectionSource instances.
48       *
49       * @param <B>
50       *            This builder type or a subclass.
51       */
52      public static class Builder<B extends Builder<B>> extends AbstractDriverManagerConnectionSource.Builder<B>
53      implements org.apache.logging.log4j.core.util.Builder<PoolingDriverConnectionSource> {
54  
55          public static final String DEFAULT_POOL_NAME = "example";
56  
57          @PluginElement("PoolableConnectionFactoryConfig")
58          private PoolableConnectionFactoryConfig poolableConnectionFactoryConfig;
59  
60          @PluginBuilderAttribute
61          private String poolName = DEFAULT_POOL_NAME;
62  
63          @Override
64  		public PoolingDriverConnectionSource build() {
65  			try {
66  				return new PoolingDriverConnectionSource(getDriverClassName(), getConnectionString(), getUserName(),
67  						getPassword(), getProperties(), poolName, poolableConnectionFactoryConfig);
68  			} catch (final SQLException e) {
69  				getLogger().error("Exception constructing {} to '{}' with {}", PoolingDriverConnectionSource.class,
70  						getConnectionString(), this, e);
71  				return null;
72  			}
73  		}
74  
75          public B setPoolableConnectionFactoryConfig(final PoolableConnectionFactoryConfig poolableConnectionFactoryConfig) {
76              this.poolableConnectionFactoryConfig = poolableConnectionFactoryConfig;
77              return asBuilder();
78          }
79  
80  		public B setPoolName(final String poolName) {
81              this.poolName = poolName;
82              return asBuilder();
83          }
84  
85          @Override
86  		public String toString() {
87  			return "Builder [poolName=" + poolName + ", connectionString=" + connectionString + ", driverClassName="
88  					+ driverClassName + ", properties=" + Arrays.toString(properties) + ", userName="
89  					+ Arrays.toString(userName) + "]";
90  		}
91      }
92  
93      public static final String URL_PREFIX = "jdbc:apache:commons:dbcp:";
94  
95      // This method is not named newBuilder() to make the compiler happy.
96      @PluginBuilderFactory
97      public static <B extends Builder<B>> B newPoolingDriverConnectionSourceBuilder() {
98          return new Builder<B>().asBuilder();
99      }
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 }