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.io.PrintWriter;
020import java.lang.reflect.Method;
021import java.sql.Connection;
022import java.sql.SQLException;
023
024import javax.sql.DataSource;
025
026import org.apache.logging.log4j.Logger;
027import org.apache.logging.log4j.core.config.plugins.Plugin;
028import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
029import org.apache.logging.log4j.core.config.plugins.PluginFactory;
030import org.apache.logging.log4j.core.util.Loader;
031import org.apache.logging.log4j.status.StatusLogger;
032import org.apache.logging.log4j.util.Strings;
033
034/**
035 * A {@link JdbcAppender} connection source that uses a public static factory method to obtain a {@link Connection} or
036 * {@link DataSource}.
037 */
038@Plugin(name = "ConnectionFactory", category = "Core", elementType = "connectionSource", printObject = true)
039public final class FactoryMethodConnectionSource implements ConnectionSource {
040    private static final Logger LOGGER = StatusLogger.getLogger();
041
042    private final DataSource dataSource;
043    private final String description;
044
045    private FactoryMethodConnectionSource(final DataSource dataSource, final String className, final String methodName,
046                                          final String returnType) {
047        this.dataSource = dataSource;
048        this.description = "factory{ public static " + returnType + ' ' + className + '.' + methodName + "() }";
049    }
050
051    @Override
052    public Connection getConnection() throws SQLException {
053        return this.dataSource.getConnection();
054    }
055
056    @Override
057    public String toString() {
058        return this.description;
059    }
060
061    /**
062     * Factory method for creating a connection source within the plugin manager.
063     *
064     * @param className The name of a public class that contains a static method capable of returning either a
065     *                  {@link DataSource} or a {@link Connection}.
066     * @param methodName The name of the public static method on the aforementioned class that returns the data source
067     *                   or connection. If this method returns a {@link Connection}, it should return a new connection
068     *                   every call.
069     * @return the created connection source.
070     */
071    @PluginFactory
072    public static FactoryMethodConnectionSource createConnectionSource(
073            @PluginAttribute("class") final String className,
074            @PluginAttribute("method") final String methodName) {
075        if (Strings.isEmpty(className) || Strings.isEmpty(methodName)) {
076            LOGGER.error("No class name or method name specified for the connection factory method.");
077            return null;
078        }
079
080        final Method method;
081        try {
082            final Class<?> factoryClass = Loader.loadClass(className);
083            method = factoryClass.getMethod(methodName);
084        } catch (final Exception e) {
085            LOGGER.error(e.toString(), e);
086            return null;
087        }
088
089        final Class<?> returnType = method.getReturnType();
090        String returnTypeString = returnType.getName();
091        DataSource dataSource;
092        if (returnType == DataSource.class) {
093            try {
094                dataSource = (DataSource) method.invoke(null);
095                returnTypeString += "[" + dataSource + ']';
096            } catch (final Exception e) {
097                LOGGER.error(e.toString(), e);
098                return null;
099            }
100        } else if (returnType == Connection.class) {
101            dataSource = new DataSource() {
102                @Override
103                public Connection getConnection() throws SQLException {
104                    try {
105                        return (Connection) method.invoke(null);
106                    } catch (final Exception e) {
107                        throw new SQLException("Failed to obtain connection from factory method.", e);
108                    }
109                }
110
111                @Override
112                public Connection getConnection(final String username, final String password) throws SQLException {
113                    throw new UnsupportedOperationException();
114                }
115
116                @Override
117                public int getLoginTimeout() throws SQLException {
118                    throw new UnsupportedOperationException();
119                }
120
121                @Override
122                public PrintWriter getLogWriter() throws SQLException {
123                    throw new UnsupportedOperationException();
124                }
125
126                // method must be present to compile on Java 7!
127                // @Override must be absent to compile on Java 6!
128                @SuppressWarnings("unused")
129                public java.util.logging.Logger getParentLogger() {
130                    throw new UnsupportedOperationException();
131                }
132
133                @Override
134                public boolean isWrapperFor(final Class<?> iface) throws SQLException {
135                    return false;
136                }
137
138                @Override
139                public void setLoginTimeout(final int seconds) throws SQLException {
140                    throw new UnsupportedOperationException();
141                }
142
143                @Override
144                public void setLogWriter(final PrintWriter out) throws SQLException {
145                    throw new UnsupportedOperationException();
146                }
147
148                @Override
149                public <T> T unwrap(final Class<T> iface) throws SQLException {
150                    return null;
151                }
152            };
153        } else {
154            LOGGER.error("Method [{}.{}()] returns unsupported type [{}].", className, methodName,
155                    returnType.getName());
156            return null;
157        }
158
159        return new FactoryMethodConnectionSource(dataSource, className, methodName, returnTypeString);
160    }
161}