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.Core;
028import org.apache.logging.log4j.core.config.plugins.Plugin;
029import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
030import org.apache.logging.log4j.core.config.plugins.PluginFactory;
031import org.apache.logging.log4j.status.StatusLogger;
032import org.apache.logging.log4j.util.LoaderUtil;
033import org.apache.logging.log4j.util.Strings;
034
035/**
036 * A {@link JdbcAppender} connection source that uses a public static factory method to obtain a {@link Connection} or
037 * {@link DataSource}.
038 */
039@Plugin(name = "ConnectionFactory", category = Core.CATEGORY_NAME, elementType = "connectionSource", printObject = true)
040public final class FactoryMethodConnectionSource implements ConnectionSource {
041    private static final Logger LOGGER = StatusLogger.getLogger();
042
043    private final DataSource dataSource;
044    private final String description;
045
046    private FactoryMethodConnectionSource(final DataSource dataSource, final String className, final String methodName,
047                                          final String returnType) {
048        this.dataSource = dataSource;
049        this.description = "factory{ public static " + returnType + ' ' + className + '.' + methodName + "() }";
050    }
051
052    @Override
053    public Connection getConnection() throws SQLException {
054        return this.dataSource.getConnection();
055    }
056
057    @Override
058    public String toString() {
059        return this.description;
060    }
061
062    /**
063     * Factory method for creating a connection source within the plugin manager.
064     *
065     * @param className The name of a public class that contains a static method capable of returning either a
066     *                  {@link DataSource} or a {@link Connection}.
067     * @param methodName The name of the public static method on the aforementioned class that returns the data source
068     *                   or connection. If this method returns a {@link Connection}, it should return a new connection
069     *                   every call.
070     * @return the created connection source.
071     */
072    @PluginFactory
073    public static FactoryMethodConnectionSource createConnectionSource(
074            @PluginAttribute("class") final String className,
075            @PluginAttribute("method") final String methodName) {
076        if (Strings.isEmpty(className) || Strings.isEmpty(methodName)) {
077            LOGGER.error("No class name or method name specified for the connection factory method.");
078            return null;
079        }
080
081        final Method method;
082        try {
083            final Class<?> factoryClass = LoaderUtil.loadClass(className);
084            method = factoryClass.getMethod(methodName);
085        } catch (final Exception e) {
086            LOGGER.error(e.toString(), e);
087            return null;
088        }
089
090        final Class<?> returnType = method.getReturnType();
091        String returnTypeString = returnType.getName();
092        DataSource dataSource;
093        if (returnType == DataSource.class) {
094            try {
095                dataSource = (DataSource) method.invoke(null);
096                returnTypeString += "[" + dataSource + ']';
097            } catch (final Exception e) {
098                LOGGER.error(e.toString(), e);
099                return null;
100            }
101        } else if (returnType == Connection.class) {
102            dataSource = new DataSource() {
103                @Override
104                public Connection getConnection() throws SQLException {
105                    try {
106                        return (Connection) method.invoke(null);
107                    } catch (final Exception e) {
108                        throw new SQLException("Failed to obtain connection from factory method.", e);
109                    }
110                }
111
112                @Override
113                public Connection getConnection(final String username, final String password) throws SQLException {
114                    throw new UnsupportedOperationException();
115                }
116
117                @Override
118                public int getLoginTimeout() throws SQLException {
119                    throw new UnsupportedOperationException();
120                }
121
122                @Override
123                public PrintWriter getLogWriter() throws SQLException {
124                    throw new UnsupportedOperationException();
125                }
126
127                @Override
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}