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