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        super();
132        this.driverClassName = driverClassName;
133        this.connectionString = connectionString;
134        this.actualConnectionString = actualConnectionString;
135        this.userName = userName;
136        this.password = password;
137        this.properties = properties;
138    }
139
140    public String getActualConnectionString() {
141        return actualConnectionString;
142    }
143
144    @SuppressWarnings("resource") // The JDBC Connection is freed when the connection source is stopped.
145    @Override
146    public Connection getConnection() throws SQLException {
147        loadDriver();
148        final String actualConnectionString = getActualConnectionString();
149        LOGGER.debug("{} getting connection for '{}'", getClass().getSimpleName(), actualConnectionString);
150        Connection connection;
151        if (properties != null && properties.length > 0) {
152            if (userName != null || password != null) {
153                throw new SQLException("Either set the userName and password, or set the Properties, but not both.");
154            }
155            connection = DriverManager.getConnection(actualConnectionString, toProperties(properties));
156        } else {
157            connection = DriverManager.getConnection(actualConnectionString, toString(userName), toString(password));
158        }
159        LOGGER.debug("{} acquired connection for '{}': {} ({}@{})", getClass().getSimpleName(), actualConnectionString,
160                connection, connection.getClass().getName(), Integer.toHexString(connection.hashCode()));
161        return connection;
162    }
163
164    public String getConnectionString() {
165        return connectionString;
166    }
167
168    public String getDriverClassName() {
169        return driverClassName;
170    }
171
172    public char[] getPassword() {
173        return password;
174    }
175
176    public Property[] getProperties() {
177        return properties;
178    }
179
180    public char[] getUserName() {
181        return userName;
182    }
183
184    protected void loadDriver() throws SQLException {
185        loadDriver(driverClassName);
186    }
187
188    /**
189     * Loads a JDBC driver for the given class name
190     *
191     * @param className
192     *            the fully-qualified class name for a JDBC Driver.
193     * @throws SQLException
194     *             thrown when loading the driver throws an exception.
195     */
196    protected void loadDriver(final String className) throws SQLException {
197        if (className != null) {
198            // Hack for old JDBC drivers.
199            LOGGER.debug("Loading driver class {}", className);
200            try {
201                Class.forName(className);
202            } catch (final Exception e) {
203                throw new SQLException(String.format("The %s could not load the JDBC driver %s: %s",
204                        getClass().getSimpleName(), className, e.toString()), e);
205            }
206        }
207    }
208
209    protected Properties toProperties(final Property[] properties) {
210        final Properties props = new Properties();
211        for (final Property property : properties) {
212            props.setProperty(property.getName(), property.getValue());
213        }
214        return props;
215    }
216
217    @Override
218    public String toString() {
219        return this.connectionString;
220    }
221
222    protected String toString(final char[] value) {
223        return value == null ? null : String.valueOf(value);
224    }
225}