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.Connection;
020import java.sql.DriverManager;
021import java.sql.SQLException;
022import java.util.Properties;
023
024import org.apache.logging.log4j.Logger;
025import org.apache.logging.log4j.core.config.Property;
026import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
027import org.apache.logging.log4j.core.config.plugins.PluginElement;
028import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
029import org.apache.logging.log4j.status.StatusLogger;
030
031/**
032 * A {@link ConnectionSource} that uses a JDBC connection string, a user name, and a password to call
033 * {@link DriverManager#getConnection(String, String, String)}.
034 * <p>
035 * This plugin does not provide any connection pooling unless it is available through the connection string and driver
036 * itself. This handy to get you off the ground without having to deal with JNDI.
037 * </p>
038 */
039public class AbstractDriverManagerConnectionSource extends AbstractConnectionSource {
040
041    /**
042     * Builds DriverManagerConnectionSource instances.
043     *
044     * @param <B>
045     *            This builder type or a subclass.
046     */
047    public static class Builder<B extends Builder<B>> {
048
049        @PluginBuilderAttribute
050        @Required
051        protected String connectionString;
052
053        @PluginBuilderAttribute
054        protected String driverClassName;
055
056        @PluginBuilderAttribute
057        protected char[] password;
058
059        @PluginElement("Properties")
060        protected Property[] properties;
061
062        @PluginBuilderAttribute
063        protected char[] userName;
064
065        @SuppressWarnings("unchecked")
066        protected B asBuilder() {
067            return (B) this;
068        }
069
070        public String getConnectionString() {
071            return connectionString;
072        }
073
074        public String getDriverClassName() {
075            return driverClassName;
076        }
077
078        public char[] getPassword() {
079            return password;
080        }
081
082        public Property[] getProperties() {
083            return properties;
084        }
085
086        public char[] getUserName() {
087            return userName;
088        }
089
090        public B setConnectionString(final String connectionString) {
091            this.connectionString = connectionString;
092            return asBuilder();
093        }
094
095        public B setDriverClassName(final String driverClassName) {
096            this.driverClassName = driverClassName;
097            return asBuilder();
098        }
099
100        public B setPassword(final char[] password) {
101            this.password = password;
102            return asBuilder();
103        }
104
105        public B setProperties(final Property[] properties) {
106            this.properties = properties;
107            return asBuilder();
108        }
109
110        public B setUserName(final char[] userName) {
111            this.userName = userName;
112            return asBuilder();
113        }
114    }
115
116    private static final Logger LOGGER = StatusLogger.getLogger();
117
118    public static Logger getLogger() {
119        return LOGGER;
120    }
121
122    private final String actualConnectionString;
123    private final String connectionString;
124    private final String driverClassName;
125    private final char[] password;
126    private final Property[] properties;
127    private final char[] userName;
128
129    public AbstractDriverManagerConnectionSource(final String driverClassName, final String connectionString,
130            final String actualConnectionString, final char[] userName, final char[] password, final Property[] properties) {
131        this.driverClassName = driverClassName;
132        this.connectionString = connectionString;
133        this.actualConnectionString = actualConnectionString;
134        this.userName = userName;
135        this.password = password;
136        this.properties = properties;
137    }
138
139    public String getActualConnectionString() {
140        return actualConnectionString;
141    }
142
143    @SuppressWarnings("resource") // The JDBC Connection is freed when the connection source is stopped.
144    @Override
145    public Connection getConnection() throws SQLException {
146        loadDriver();
147        final String actualConnectionString = getActualConnectionString();
148        LOGGER.debug("{} getting connection for '{}'", getClass().getSimpleName(), actualConnectionString);
149        Connection connection;
150        if (properties != null && properties.length > 0) {
151            if (userName != null || password != null) {
152                throw new SQLException("Either set the userName and password, or set the Properties, but not both.");
153            }
154            connection = DriverManager.getConnection(actualConnectionString, toProperties(properties));
155        } else {
156            connection = DriverManager.getConnection(actualConnectionString, toString(userName), toString(password));
157        }
158        LOGGER.debug("{} acquired connection for '{}': {} ({}@{})", getClass().getSimpleName(), actualConnectionString,
159                connection, connection.getClass().getName(), Integer.toHexString(connection.hashCode()));
160        return connection;
161    }
162
163    public String getConnectionString() {
164        return connectionString;
165    }
166
167    public String getDriverClassName() {
168        return driverClassName;
169    }
170
171    public char[] getPassword() {
172        return password;
173    }
174
175    public Property[] getProperties() {
176        return properties;
177    }
178
179    public char[] getUserName() {
180        return userName;
181    }
182
183    protected void loadDriver() throws SQLException {
184        loadDriver(driverClassName);
185    }
186
187    /**
188     * Loads a JDBC driver for the given class name
189     *
190     * @param className
191     *            the fully-qualified class name for a JDBC Driver.
192     * @throws SQLException
193     *             thrown when loading the driver throws an exception.
194     */
195    protected void loadDriver(final String className) throws SQLException {
196        if (className != null) {
197            // Hack for old JDBC drivers.
198            LOGGER.debug("Loading driver class {}", className);
199            try {
200                Class.forName(className);
201            } catch (final Exception e) {
202                throw new SQLException(String.format("The %s could not load the JDBC driver %s: %s",
203                        getClass().getSimpleName(), className, e.toString()), e);
204            }
205        }
206    }
207
208    protected Properties toProperties(final Property[] properties) {
209        final Properties props = new Properties();
210        for (final Property property : properties) {
211            props.setProperty(property.getName(), property.getValue());
212        }
213        return props;
214    }
215
216    @Override
217    public String toString() {
218        return this.connectionString;
219    }
220
221    protected String toString(final char[] value) {
222        return value == null ? null : String.valueOf(value);
223    }
224}