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.concurrent.TimeUnit;
22  
23  import org.apache.commons.dbcp2.ConnectionFactory;
24  import org.apache.commons.dbcp2.DriverManagerConnectionFactory;
25  import org.apache.commons.dbcp2.PoolableConnection;
26  import org.apache.commons.dbcp2.PoolableConnectionFactory;
27  import org.apache.commons.dbcp2.PoolingDriver;
28  import org.apache.commons.pool2.ObjectPool;
29  import org.apache.commons.pool2.impl.GenericObjectPool;
30  import org.apache.logging.log4j.core.Core;
31  import org.apache.logging.log4j.core.config.Property;
32  import org.apache.logging.log4j.core.config.plugins.Plugin;
33  import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
34  
35  /**
36   * A {@link ConnectionSource} that uses a JDBC connection string, a user name, and a password to call
37   * {@link DriverManager#getConnection(String, String, String)}. The connections are served from an
38   * <a href="http://commons.apache.org/proper/commons-dbcp/">Apache Commons DBCP</a> pooling driver.
39   */
40  @Plugin(name = "PoolingDriver", category = Core.CATEGORY_NAME, elementType = "connectionSource", printObject = true)
41  public final class PoolingDriverConnectionSource extends AbstractDriverManagerConnectionSource {
42  
43      /**
44       * Builds PoolingDriverConnectionSource instances.
45       *
46       * @param <B>
47       *            This builder type or a subclass.
48       */
49      public static class Builder<B extends Builder<B>> extends AbstractDriverManagerConnectionSource.Builder<B>
50      implements org.apache.logging.log4j.core.util.Builder<PoolingDriverConnectionSource> {
51  
52          public static final String DEFAULT_POOL_NAME = "example";
53          private String poolName = DEFAULT_POOL_NAME;
54  
55          @Override
56          public PoolingDriverConnectionSource build() {
57              try {
58                  return new PoolingDriverConnectionSource(getDriverClassName(), getConnectionString(), getUserName(),
59                          getPassword(), getProperties(), poolName);
60              } catch (final SQLException e) {
61                  getLogger().error("Exception constructing {} to '{}'", getClass(), getConnectionString(), e);
62                  return null;
63              }
64          }
65  
66          public B setPoolName(final String poolName) {
67              this.poolName = poolName;
68              return asBuilder();
69          }
70      }
71  
72      public static final String URL_PREFIX = "jdbc:apache:commons:dbcp:";
73  
74      // This method is not named newBuilder() to make the compiler happy.
75      @PluginBuilderFactory
76      public static <B extends Builder<B>> B newPoolingDriverConnectionSourceBuilder() {
77          return new Builder<B>().asBuilder();
78      }
79  
80      private final String poolingDriverClassName = "org.apache.commons.dbcp2.PoolingDriver";
81  
82      private final String poolName;
83  
84      public PoolingDriverConnectionSource(final String driverClassName, final String connectionString,
85              final char[] userName, final char[] password, final Property[] properties, final String poolName)
86              throws SQLException {
87          super(driverClassName, connectionString, URL_PREFIX + poolName, userName, password, properties);
88          this.poolName = poolName;
89          setupDriver(connectionString);
90      }
91  
92      @Override
93      public String getActualConnectionString() {
94          // TODO Auto-generated method stub
95          return super.getActualConnectionString();
96      }
97  
98      private PoolingDriver getPoolingDriver() throws SQLException {
99          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 }