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