001package org.javasimon.jdbc4;
002
003import java.sql.Connection;
004import java.sql.DriverManager;
005import java.sql.DriverPropertyInfo;
006import java.sql.SQLException;
007import java.sql.SQLFeatureNotSupportedException;
008import java.util.Properties;
009import java.util.logging.Logger;
010
011/**
012 * Simon JDBC 4.1 Proxy Driver (Java SE 7).
013 * <p>
014 * An application should not use this class directly. The application (if standalone)
015 * should use {@link java.sql.DriverManager} only. For example:
016 * </p>
017 * <pre>
018 * Connection conn = DriverManager.getConnection("jdbc:simon:oracle:thin:...", "scott", "tiger");</pre>
019 *
020 * Simon driver has following format of JDBC connection string:
021 * <pre>{@code
022 * jdbc:simon:<real driver conn string>;<param1>=<value1>;...}</pre>
023 * Simon driver recognizes two parameters:
024 * <ul>
025 * <li>
026 * {@code SIMON_REAL_DRV} - if you don't want or can't register real driver for any
027 * reason, you can use this parameter and Simon proxy driver will do the registration
028 * for you. You don't need to specify real driver parameter for some well known databases.
029 * Simon proxy driver recognize database by first key word after JDBC and register.
030 * </li>
031 * <li>
032 * {@code SIMON_PREFIX} - setting this parameter you can choose different prefix
033 * for all monitors for one instance of driver. For example, setting
034 * {@code SIMON_PREFIX=com.foo} will ensure that all proxy related Simons are located
035 * under the subtree specified by the prefix, e.g. {@code com.foo.conn}, <code>com.foo.stmt</code>,
036 * <code>com.foo.select</code>, etc. If no prefix is set, default {@code org.javasimon.jdbc} prefix
037 * is used.
038 * </li>
039 * </ul>                                `
040 *
041 * By default, there is no need to load any driver explicitly, because drivers are loaded automatically
042 * (since JDK 1.5) if they are in class path and jar have appropriate
043 * meta information (see {@link java.sql.DriverManager}).
044 *
045 * If this is not a case for any reason, you need to register Simon proxy driver at least.
046 * For real driver Simon proxy driver contains following procedure for find and register it:
047 * <ol>
048 * <li>Simon proxy driver tries if there is registered driver for driver key word.
049 * <li>If not, driver tries if there is real driver parameter in info properties and then registers it.
050 * <li>If not, driver tries to find driver by key word within internal list of well known drivers and
051 * then registers it. For now, list contains default drivers for Oracle, PostgreSQL, Enterprise DB, H2,
052 * MySQL.
053 * <li>If not, driver tries to find real driver param within connection string and then registers it.
054 * <li>If not, getting new connection fails.
055 * </ol>
056 * The safest way to get Simon proxy driver work is to load the drivers, the real one (i.e. oracle)
057 * and a Simon proxy driver explicitly. This can be done using Class.forName. To load the driver and open a
058 * database connection, use following code:
059 * <pre>
060 * Class.forName("oracle.jdbc.driver.OracleDriver");  // loads real driver
061 * Class.forName("org.javasimon.jdbc4.Driver");  // loads Simon proxy driver
062 * Connection conn = DriverManager.getConnection(
063 *      "jdbc:simon:oracle:thin:...", "scott", "tiger");</pre>
064 *
065 * @author Radovan Sninsky
066 * @author <a href="mailto:virgo47@gmail.com">Richard "Virgo" Richter</a>
067 * @see java.sql.DriverManager#getConnection(String)
068 * @since 2.4
069 */
070public final class Driver implements java.sql.Driver {
071        static {
072                try {
073                        DriverManager.registerDriver(new Driver());
074                } catch (Exception e) {
075                        // don't know what to do yet, maybe throw RuntimeException ???
076                        e.printStackTrace();
077                }
078        }
079
080    private java.sql.Driver driver;
081
082    /**
083         * Class constructor. It loads well known driver list from resource file drivers.properties.
084         */
085        public Driver() {
086        }
087
088        /**
089         * Opens new Simon proxy driver connection associated with real connection to the specified database.
090         *
091         * @param simonUrl JDBC connection string (i.e. jdbc:simon:h2:file:test)
092         * @param info properties for connection
093         * @return open connection to database or null if provided url is not accepted by this driver
094         * @throws java.sql.SQLException if there is no real driver registered/recognized or opening real connection fails
095         * @see Driver
096         */
097        @Override
098        public Connection connect(String simonUrl, Properties info) throws SQLException {
099                if (!acceptsURL(simonUrl)) {
100                        return null;
101                }
102
103                SimonConnectionConfiguration url = new SimonConnectionConfiguration(simonUrl);
104        driver = getRealDriver(url, info);
105
106        return new SimonConnection(driver.connect(url.getRealUrl(), info), url.getPrefix());
107        }
108
109        /**
110         * Tries to determine driver class, instantiate it and register if already not registered.
111         * For more detail look at {@link Driver} class javadoc.
112         *
113         * @param configuration instance of url object that represents url
114         * @param info parameters from {@link #connect(String, java.util.Properties)} method
115         * @return instance of real driver
116         * @throws java.sql.SQLException if real driver can't be determined or is not registerd
117         */
118        private java.sql.Driver getRealDriver(SimonConnectionConfiguration configuration, Properties info) throws SQLException {
119                java.sql.Driver drv = null;
120                try {
121                        drv = DriverManager.getDriver(configuration.getRealUrl());
122                } catch (SQLException e) {
123                        // nothing, not an error
124                }
125
126                if (drv == null && info != null && info.keySet().contains(SimonConnectionConfiguration.REAL_DRIVER)) {
127                        drv = registerDriver(info.getProperty(SimonConnectionConfiguration.REAL_DRIVER));
128                }
129
130                if (drv == null && configuration.getRealDriver() != null) {
131                        drv = registerDriver(configuration.getRealDriver());
132                }
133
134                if (drv == null) {
135                        if (configuration.getRealDriver() != null) {
136                                drv = registerDriver(configuration.getRealDriver());
137                        }
138                }
139
140                if (drv == null) {
141                        throw new SQLException("Real driver is not registered and can't determine real driver class name for registration.");
142                }
143                return drv;
144        }
145
146        /**
147         * Registers real driver through {@link java.sql.DriverManager}.
148         *
149         * @param name real driver class name
150         * @return instance of registered real driver
151         * @throws java.sql.SQLException if registration fails
152         */
153        private java.sql.Driver registerDriver(String name) throws SQLException {
154                try {
155                        java.sql.Driver d = (java.sql.Driver) Class.forName(name).newInstance();
156                        DriverManager.registerDriver(d);
157                        return d;
158                } catch (SQLException e) {
159                        throw e;
160                } catch (Exception e) {
161                        return null;
162                }
163        }
164
165        @Override
166        public boolean acceptsURL(String url) throws SQLException {
167                return SimonConnectionConfiguration.isSimonUrl(url);
168        }
169
170        @Override
171        public DriverPropertyInfo[] getPropertyInfo(String s, Properties properties) throws SQLException {
172                return new DriverPropertyInfo[0];
173        }
174
175        @Override
176        public int getMajorVersion() {
177                return 2;
178        }
179
180        @Override
181        public int getMinorVersion() {
182                return 4;
183        }
184
185        @Override
186        public boolean jdbcCompliant() {
187                return true;
188        }
189
190    @Override
191    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
192        return driver.getParentLogger();
193    }
194}