blob: 4118fd7f541c62ee58c222f7e635cde3c7a13dc5 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.calcite.avatica;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.DriverPropertyInfo;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Implementation of JDBC driver that does not register itself.
*
* <p>You can easily create a "vanity driver" that recognizes its own
* URL prefix as a sub-class of this class. Per the JDBC specification it
* must register itself when the class is loaded.</p>
*
* <p>Derived classes must implement {@link #createDriverVersion()} and
* {@link #getConnectStringPrefix()}, and may override
* {@link #createFactory()}.</p>
*
* <p>The provider must implement:</p>
* <ul>
* <li>{@link Meta#prepare(Meta.ConnectionHandle, String, long)}
* <li>{@link Meta#createIterable(org.apache.calcite.avatica.Meta.StatementHandle, org.apache.calcite.avatica.QueryState, org.apache.calcite.avatica.Meta.Signature, java.util.List, Meta.Frame)}
* </ul>
*/
public abstract class UnregisteredDriver implements java.sql.Driver {
final DriverVersion version;
protected final AvaticaFactory factory;
public final Handler handler;
protected UnregisteredDriver() {
this.factory = createFactory();
this.version = createDriverVersion();
this.handler = createHandler();
}
/**
* Creates a factory for JDBC objects (connection, statement).
* Called from the driver constructor.
*
* <p>The default implementation calls {@link JdbcVersion#current},
* then {@link #getFactoryClassName} with that version,
* then passes that class name to {@link #instantiateFactory(String)}.
* This approach is recommended it does not include in the code references
* to classes that may not be instantiable in all JDK versions.
* But drivers are free to do it their own way.</p>
*
* @return JDBC object factory
*/
protected AvaticaFactory createFactory() {
return instantiateFactory(getFactoryClassName(JdbcVersion.current()));
}
/** Creates a Handler. */
protected Handler createHandler() {
return new HandlerImpl();
}
/**
* Returns the name of a class to be factory for JDBC objects
* (connection, statement) appropriate for the current JDBC version.
*/
protected String getFactoryClassName(JdbcVersion jdbcVersion) {
switch (jdbcVersion) {
case JDBC_30:
case JDBC_40:
throw new IllegalArgumentException("JDBC version not supported: "
+ jdbcVersion);
case JDBC_41:
default:
return "org.apache.calcite.avatica.AvaticaJdbc41Factory";
}
}
/**
* Creates an object describing the name and version of this driver.
* Called from the driver constructor.
*/
protected abstract DriverVersion createDriverVersion();
/**
* Returns the connection properties supported by this driver.
*/
protected Collection<ConnectionProperty> getConnectionProperties() {
return Arrays.<ConnectionProperty>asList(
BuiltInConnectionProperty.values());
}
/** Helper method for creating factories. */
protected static AvaticaFactory instantiateFactory(String factoryClassName) {
try {
final Class<?> clazz = Class.forName(factoryClassName);
return (AvaticaFactory) clazz.getConstructor().newInstance();
} catch (Throwable e) {
// It is not usually good to catch Throwable. But class loading can fail
// with serious errors such as java.lang.NoClassDefFoundError
throw handle("Error loading factory " + factoryClassName, e);
}
}
private static RuntimeException handle(String msg, Throwable e) {
Logger.getLogger("").log(Level.SEVERE, msg, e);
throw new RuntimeException(msg, e);
}
public Connection connect(String url, Properties info) throws SQLException {
if (!acceptsURL(url)) {
return null;
}
final String prefix = getConnectStringPrefix();
assert url.startsWith(prefix);
final String urlSuffix = url.substring(prefix.length());
final Properties info2 = ConnectStringParser.parse(urlSuffix, info);
final AvaticaConnection connection =
factory.newConnection(this, factory, url, info2);
handler.onConnectionInit(connection);
return connection;
}
public boolean acceptsURL(String url) throws SQLException {
return url.startsWith(getConnectStringPrefix());
}
/** Returns the prefix of the connect string that this driver will recognize
* as its own. For example, "jdbc:calcite:". */
protected abstract String getConnectStringPrefix();
public DriverPropertyInfo[] getPropertyInfo(
String url, Properties info) throws SQLException {
List<DriverPropertyInfo> list = new ArrayList<DriverPropertyInfo>();
// First, add the contents of info
for (Map.Entry<Object, Object> entry : info.entrySet()) {
list.add(
new DriverPropertyInfo(
(String) entry.getKey(),
(String) entry.getValue()));
}
// Next, add property definitions not mentioned in info
for (ConnectionProperty p : getConnectionProperties()) {
if (info.containsKey(p.name())) {
continue;
}
list.add(new DriverPropertyInfo(p.name(), null));
}
return list.toArray(new DriverPropertyInfo[list.size()]);
}
// JDBC 4.1 support (JDK 1.7 and higher)
public Logger getParentLogger() {
return Logger.getLogger("");
}
/**
* Returns the driver version object. Not in the JDBC API.
*
* @return Driver version
*/
public DriverVersion getDriverVersion() {
return version;
}
public final int getMajorVersion() {
return version.majorVersion;
}
public final int getMinorVersion() {
return version.minorVersion;
}
public boolean jdbcCompliant() {
return version.jdbcCompliant;
}
/**
* Registers this driver with the driver manager.
*/
protected void register() {
try {
DriverManager.registerDriver(this);
} catch (SQLException e) {
System.out.println(
"Error occurred while registering JDBC driver "
+ this + ": " + e.toString());
}
}
/** Creates a service handler that will give connections from this Driver
* their behavior. */
public abstract Meta createMeta(AvaticaConnection connection);
/** JDBC version. */
protected enum JdbcVersion {
/** Unknown JDBC version. */
JDBC_UNKNOWN,
/** JDBC version 3.0. Generally associated with JDK 1.5. */
JDBC_30,
/** JDBC version 4.0. Generally associated with JDK 1.6. */
JDBC_40,
/** JDBC version 4.1. Generally associated with JDK 1.7. */
JDBC_41;
/** Deduces the current JDBC version. */
public static JdbcVersion current() {
try {
// If java.sql.PseudoColumnUsage is present, we are running JDBC
// 4.1 or later.
Class.forName("java.sql.PseudoColumnUsage");
return JDBC_41;
} catch (ClassNotFoundException e) {
// java.sql.PseudoColumnUsage is not present. This means we are
// running JDBC 4.0 or earlier.
try {
Class.forName("java.sql.Wrapper");
return JDBC_40;
} catch (ClassNotFoundException e2) {
// java.sql.Wrapper is not present. This means we are
// running JDBC 3.0 or earlier (probably JDK 1.5).
return JDBC_30;
}
}
}
}
}
// End UnregisteredDriver.java