All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.firebirdsql.jdbc.FBDriver Maven / Gradle / Ivy

There is a newer version: 6.0.0-beta-1
Show newest version
/*
 * Firebird Open Source JDBC Driver
 *
 * Distributable under LGPL license.
 * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * LGPL License for more details.
 *
 * This file was created by members of the firebird development team.
 * All individual contributions remain the Copyright (C) of those
 * individuals.  Contributors to this file are either listed here or
 * can be obtained from a source control history command.
 *
 * All rights reserved.
 */
package org.firebirdsql.jdbc;

import org.firebirdsql.gds.GDSException;
import org.firebirdsql.gds.JaybirdErrorCodes;
import org.firebirdsql.gds.impl.GDSFactory;
import org.firebirdsql.gds.impl.GDSType;
import org.firebirdsql.gds.ng.FbExceptionBuilder;
import org.firebirdsql.jaybird.Version;
import org.firebirdsql.jaybird.props.InvalidPropertyValueException;
import org.firebirdsql.jaybird.props.PropertyNames;
import org.firebirdsql.jaybird.xca.FBManagedConnectionFactory;
import org.firebirdsql.logging.Logger;
import org.firebirdsql.logging.LoggerFactory;

import java.io.UnsupportedEncodingException;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.net.URLDecoder;
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentHashMap;

/**
 * The Jaybird JDBC Driver implementation for the Firebird database.
 *
 * @author David Jencks
 * @author Mark Rotteveel
 */
public class FBDriver implements FirebirdDriver {

    private static final Logger log;

    @Deprecated
    public static final String CHARSET = PropertyNames.charSet;
    @Deprecated
    public static final String USER = PropertyNames.user;
    /**
     * @deprecated Use {@link PropertyNames#user}
     */
    @Deprecated
    public static final String USER_NAME = "user_name";
    @Deprecated
    public static final String PASSWORD = PropertyNames.password;
    @Deprecated
    public static final String DATABASE = FBConnectionProperties.DATABASE_PROPERTY;
    /**
     * @deprecated Use {@link PropertyNames#blobBufferSize}
     */
    @Deprecated
    public static final String BLOB_BUFFER_LENGTH = "blob_buffer_length";
    /**
     * @deprecated Use {@link PropertyNames#tpbMapping}
     */
    @Deprecated
    public static final String TPB_MAPPING = "tpb_mapping";

    private static final String URL_CHARSET = "UTF-8";

    /*
     * @todo implement the default subject for the
     * standard connection.
     */

    private final Map> mcfToDataSourceMap =
            new ConcurrentHashMap<>();
    private final ReferenceQueue dataSourceReferenceQueue = new ReferenceQueue<>();
    private final Object createDataSourceLock = new Object();

    static {
        log = LoggerFactory.getLogger(FBDriver.class);
        try {
            DriverManager.registerDriver(new FBDriver());
        } catch (Exception ex) {
            log.error("Could not register with driver manager", ex);
        }
    }

    @Override
    public Connection connect(String url, final Properties info) throws SQLException {
        if (url == null) {
            throw new SQLException("url is null");
        }

        final GDSType type = GDSFactory.getTypeForProtocol(url);
        if (type == null) {
            return null;
        }

        final Map mergedProperties = mergeProperties(url, info);
        try {
            int qMarkIndex = url.indexOf('?');
            if (qMarkIndex != -1) {
                url = url.substring(0, qMarkIndex);
            }

            FBManagedConnectionFactory mcf = new FBManagedConnectionFactory(type);
            String databaseURL = GDSFactory.getDatabasePath(type, url);

            // NOTE: occurrence of an explicit connection property may override this
            mcf.setDatabaseName(databaseURL);
            for (Map.Entry entry : mergedProperties.entrySet()) {
                try {
                    mcf.setProperty(entry.getKey(), entry.getValue());
                } catch (InvalidPropertyValueException e) {
                    throw e.asSQLException();
                }
            }

            FBTpbMapper.processMapping(mcf, mergedProperties);

            mcf = mcf.canonicalize();

            FBDataSource dataSource = createDataSource(mcf);

            return dataSource.getConnection(mcf.getUser(), mcf.getPassword());

        } catch (GDSException e) {
            throw new FBSQLException(e);
        }
    }

    private FBDataSource createDataSource(final FBManagedConnectionFactory mcf) {
        final FBConnectionProperties cacheKey = mcf.getCacheKey();
        FBDataSource dataSource = dataSourceFromCache(cacheKey);
        if (dataSource != null) return dataSource;
        synchronized (createDataSourceLock) {
            // Obtain again
            dataSource = dataSourceFromCache(cacheKey);
            if (dataSource == null) {
                dataSource = (FBDataSource) mcf.createConnectionFactory();
                mcfToDataSourceMap.put(cacheKey, new SoftReference<>(dataSource, dataSourceReferenceQueue));
            }
        }
        cleanDataSourceCache();
        return dataSource;
    }

    /**
     * Removes cleared references from the {@link #mcfToDataSourceMap} cache.
     */
    private void cleanDataSourceCache() {
        Reference reference;
        while ((reference = dataSourceReferenceQueue.poll()) != null) {
            mcfToDataSourceMap.values().remove(reference);
        }
    }

    private FBDataSource dataSourceFromCache(final FBConnectionProperties cacheKey) {
        final Reference dataSourceReference = mcfToDataSourceMap.get(cacheKey);
        return dataSourceReference != null ? dataSourceReference.get() : null;
    }

    @Override
    public FirebirdConnection connect(FirebirdConnectionProperties properties) throws SQLException {
        GDSType type = GDSType.getType(properties.getType());
        if (type == null) {
            type = GDSFactory.getDefaultGDSType();
        }

        FBManagedConnectionFactory mcf = new FBManagedConnectionFactory(type, (FBConnectionProperties) properties)
                .canonicalize();
        FBDataSource dataSource = createDataSource(mcf);
        return (FirebirdConnection) dataSource.getConnection(mcf.getUser(), mcf.getPassword());
    }

    @Override
    public FirebirdConnectionProperties newConnectionProperties() {
        return new FBConnectionProperties();
    }

    @Override
    public boolean acceptsURL(String url) throws SQLException {
        if (url == null) {
            throw new SQLException("url is null");
        }

        for (String protocol : GDSFactory.getSupportedProtocols()) {
            if (url.startsWith(protocol))
                return true;
        }

        return false;
    }

    // TODO check the correctness of implementation
    // TODO convert parameters into constants
    @Override
    public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException {
        return FBDriverPropertyManager.getDriverPropertyInfo(info);
    }

    @Override
    public int getMajorVersion() {
        return Version.JAYBIRD_MAJOR_VERSION;
    }

    @Override
    public int getMinorVersion() {
        return Version.JAYBIRD_MINOR_VERSION;
    }

    @Override
    public boolean jdbcCompliant() {
        return true;
    }

    @Override
    public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException {
        throw new FBDriverNotCapableException("Method getParentLogger() not supported");
    }

    /**
     * Merges the properties from the JDBC URL and properties object, normalizes them to a standard name.
     * 

* If a property with the exact same name is present in both, the property specified in the JDBC url takes * precedence. Occurrence of multiple (distinct) aliases for the same property result in a {@link SQLException}. *

*

* The property name that is the result of normalization, is implementation specific behaviour, and might change in * a future version of Jaybird. When present, the (normalized) property `"database"` will be excluded, this also * might change in the future. *

*

* The behaviour of this method does not necessarily match the behaviour of Jaybird when processing properties * during {@link #connect(String, Properties)}, as this method performs additional processing that is skipped during * connect. *

* * @param jdbcUrl * JDBC Url * @param connectionProperties * Properties object * @return New map object with the merged and normalized connection properties * @throws SQLException * For failures to extract connection properties from {@code jdbcUrl} (URL decoding errors), or presence * of the same property under multiple aliases. * @since 4.0.1 */ public static Map normalizeProperties(String jdbcUrl, Properties connectionProperties) throws SQLException { Map mergedProperties = mergeProperties(jdbcUrl, connectionProperties); return FBDriverPropertyManager.normalize(mergedProperties); } /** * Merges the properties from the JDBC URL and properties object. *

* If a property with the exact same name is present in both, the property specified in the JDBC url takes * precedence. *

* * @param jdbcUrl * JDBC Url * @param connectionProperties * Properties object * @return Map with connection properties * @throws SQLException * For failures to extract connection properties from {@code jdbcUrl} (URL decoding errors) */ private static Map mergeProperties(String jdbcUrl, Properties connectionProperties) throws SQLException { Map mergedProperties = new HashMap<>(); if (connectionProperties != null) { for (String propertyName : connectionProperties.stringPropertyNames()) { mergedProperties.put(propertyName, connectionProperties.getProperty(propertyName)); } } convertUrlParams(jdbcUrl, mergedProperties); return mergedProperties; } /** * Extract properties specified as URL parameter into the specified properties object. * * @param url * specified URL. * @param info * map into which values should be extracted. * @throws SQLException * For failures to extract connection properties from {@code url} (URL decoding errors) */ private static void convertUrlParams(String url, Map info) throws SQLException { if (url == null) { return; } int iQuestionMark = url.indexOf("?"); if (iQuestionMark == -1) { return; } String propString = url.substring(iQuestionMark + 1); StringTokenizer st = new StringTokenizer(propString, "&;"); while (st.hasMoreTokens()) { String propertyString = st.nextToken(); int iIs = propertyString.indexOf("="); if (iIs > -1) { String property = urlDecode(propertyString.substring(0, iIs), url); String value = urlDecode(propertyString.substring(iIs + 1), url); info.put(property, value); } else { info.put(urlDecode(propertyString, url), ""); } } } /** * Decodes URL encoded value, transforming exceptions to SQLExceptions * * @param encodedValue * The value to decode * @return The decoded value * @throws SQLException * If decoding fails (failures of {@link URLDecoder#decode(String, String)} */ private static String urlDecode(String encodedValue, String url) throws SQLException { try { return URLDecoder.decode(encodedValue, URL_CHARSET); } catch (RuntimeException | UnsupportedEncodingException e) { // NOTE: The UnsupportedEncodingException shouldn't occur because UTF-8 support is required in Java throw new FbExceptionBuilder() .nonTransientConnectionException(JaybirdErrorCodes.jb_invalidConnectionString) .messageParameter(url) .messageParameter(e.toString()) .cause(e) .toSQLException(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy