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;
023import javax.sql.DataSource;
024
025import org.apache.logging.log4j.Logger;
026import org.apache.logging.log4j.core.Core;
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.CATEGORY_NAME, elementType = "connectionSource", printObject = true)
039public final class FactoryMethodConnectionSource extends AbstractConnectionSource {
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                @Override
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}