001/*
002// This software is subject to the terms of the Eclipse Public License v1.0
003// Agreement, available at the following URL:
004// http://www.eclipse.org/legal/epl-v10.html.
005// You must accept the terms of that agreement to use this software.
006//
007// Copyright (C) 2009-2010 Pentaho
008// All Rights Reserved.
009*/
010package mondrian.spi.impl;
011
012import mondrian.olap.Util;
013import mondrian.spi.Dialect;
014import mondrian.spi.DialectFactory;
015
016import java.lang.reflect.Constructor;
017import java.lang.reflect.InvocationTargetException;
018import java.sql.*;
019import javax.sql.DataSource;
020
021/**
022 * Implementation of {@link mondrian.spi.DialectFactory} for subclasses
023 * of {@link mondrian.spi.impl.JdbcDialectImpl}.
024 *
025 * <p>Assumes that the dialect has a public constructor that takes a
026 * {@link java.sql.Connection} as a parameter.
027 */
028public class JdbcDialectFactory implements DialectFactory {
029    private final Dialect.DatabaseProduct databaseProduct;
030    private final Constructor<? extends JdbcDialectImpl> constructor;
031
032    /**
033     * Creates a JdbcDialectFactory.
034     *
035     * @param dialectClass Dialect class
036     * @param databaseProduct Database type (e.g. Oracle) if this is a
037     * common RDBMS, or null if it is an uncommon one
038     */
039    public JdbcDialectFactory(
040        Class<? extends JdbcDialectImpl> dialectClass,
041        Dialect.DatabaseProduct databaseProduct)
042    {
043        this.databaseProduct = databaseProduct;
044        try {
045            constructor = dialectClass.getConstructor(Connection.class);
046        } catch (NoSuchMethodException e) {
047            throw Util.newError(
048                e,
049                "Class does not contain constructor "
050                + "'public <init>(Connection connection)' required "
051                + "for subclasses of JdbcDialectImpl");
052        }
053    }
054
055    /**
056     * Creates a temporary connection and calls
057     * {@link mondrian.spi.DialectFactory#createDialect(javax.sql.DataSource, java.sql.Connection)}.
058     *
059     * <p>Helper method, called when {@code createDialect} is called without a
060     * {@link java.sql.Connection} and the dialect factory
061     * cannot create a dialect with {@link javax.sql.DataSource} alone.
062     *
063     * <p>It is a user error if {@code dataSource} is null (since this implies
064     * that {@code createDialect} was called with {@code dataSource} and
065     * {@code connection} both null.</p>
066     *
067     * @param factory Dialect factory
068     * @param dataSource Data source, must not be null
069     * @return Dialect, or null if factory cannot create suitable dialect
070     */
071    public static Dialect createDialectHelper(
072        DialectFactory factory,
073        DataSource dataSource)
074    {
075        if (dataSource == null) {
076            throw new IllegalArgumentException(
077                "Must specify either dataSource or connection");
078        }
079        Connection connection = null;
080        try {
081            connection = dataSource.getConnection();
082            if (connection == null) {
083                // DataSource.getConnection does not return null. But
084                // a null value here would cause infinite recursion, so
085                // let's be cautious.
086                throw new IllegalArgumentException();
087            }
088            final Dialect dialect =
089                factory.createDialect(dataSource, connection);
090
091            // Close the connection in such a way that if there is a
092            // SQLException,
093            // (a) we propagate the exception,
094            // (b) we don't try to close the connection again.
095            Connection connection2 = connection;
096            connection = null;
097            connection2.close();
098            return dialect;
099        } catch (SQLException e) {
100            throw Util.newError(
101                e,
102                "Error while creating dialect");
103        } finally {
104            if (connection != null) {
105                try {
106                    connection.close();
107                } catch (SQLException e) {
108                    // ignore
109                }
110            }
111        }
112    }
113
114    public Dialect createDialect(DataSource dataSource, Connection connection) {
115        // If connection is null, create a temporary connection and
116        // recursively call this method.
117        if (connection == null) {
118            return createDialectHelper(this, dataSource);
119        }
120
121        assert connection != null;
122        if (acceptsConnection(connection)) {
123            try {
124                return constructor.newInstance(connection);
125            } catch (InstantiationException e) {
126                throw Util.newError(
127                    e, "Error while instantiating dialect");
128            } catch (IllegalAccessException e) {
129                throw Util.newError(
130                    e, "Error while instantiating dialect");
131            } catch (InvocationTargetException e) {
132                throw Util.newError(
133                    e, "Error while instantiating dialect");
134            }
135        }
136        return null;
137    }
138
139    /**
140     * Returns whether this dialect is suitable for the given connection.
141     *
142     * @param connection Connection
143     * @return Whether suitable
144     */
145    protected boolean acceptsConnection(Connection connection) {
146        try {
147            final DatabaseMetaData metaData = connection.getMetaData();
148            final String productName = metaData.getDatabaseProductName();
149            final String productVersion = metaData.getDatabaseProductVersion();
150            final Dialect.DatabaseProduct product =
151                JdbcDialectImpl.getProduct(productName, productVersion);
152            return product == this.databaseProduct;
153        } catch (SQLException e) {
154            throw Util.newError(
155                e, "Error while instantiating dialect");
156        }
157    }
158}
159
160// End JdbcDialectFactory.java