    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2008, by Object Refinery Limited and Contributors.
    006     *
    007     * Project Info:  http://www.jfree.org/jfreechart/index.html
    008     *
    009     * This library is free software; you can redistribute it and/or modify it
    010     * under the terms of the GNU Lesser General Public License as published by
    011     * the Free Software Foundation; either version 2.1 of the License, or
    012     * (at your option) any later version.
    013     *
    014     * This library is distributed in the hope that it will be useful, but
    015     * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
    016     * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
    017     * License for more details.
    018     *
    019     * You should have received a copy of the GNU Lesser General Public
    020     * License along with this library; if not, write to the Free Software
    021     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
    022     * USA.
    023     *
    024     * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
    025     * in the United States and other countries.]
    026     *
    027     * ------------------------
    028     * JDBCCategoryDataset.java
    029     * ------------------------
    030     * (C) Copyright 2002-2008, by Bryan Scott and Contributors.
    031     *
    032     * Original Author:  Bryan Scott; Andy;
    033     * Contributor(s):   David Gilbert (for Object Refinery Limited);
    034     *                   Thomas Morgner;
    035     *
    036     * Changes
    037     * -------
    038     * 26-Apr-2002 : Creation based on JdbcXYDataSet, using code contributed from
    039     *               Andy;
    040     * 13-Aug-2002 : Updated Javadocs, import statements and formatting (DG);
    041     * 03-Sep-2002 : Added fix for bug 591385 (DG);
    042     * 18-Sep-2002 : Updated to support BIGINT (BS);
    043     * 16-Oct-2002 : Added fix for bug 586667 (DG);
    044     * 03-Feb-2003 : Added Types.DECIMAL (see bug report 677814) (DG);
    045     * 13-Jun-2003 : Added Types.TIME as suggest by Bryan Scott in the forum (DG);
    046     * 30-Jun-2003 : CVS Write test (BS);
    047     * 30-Jul-2003 : Added empty contructor and executeQuery(connection,string)
    048     *               method (BS);
    049     * 29-Aug-2003 : Added a 'transpose' flag, so that data can be easily
    050     *               transposed if required (DG);
    051     * 10-Sep-2003 : Added support for additional JDBC types (DG);
    052     * 24-Sep-2003 : Added clearing results from previous queries to executeQuery
    053     *               following being highlighted on online forum (BS);
    054     * 02-Dec-2003 : Throwing exceptions allows to handle errors, removed default
    055     *               constructor, as without a connection, a query can never be
    056     *               executed (TM);
    057     * 04-Dec-2003 : Added missing Javadocs (DG);
    058     * ------------- JFREECHART 1.0.0 ---------------------------------------------
    059     * 08-Mar-2006 : Fixed bug 1445748 where an exception is thrown if
    060     *               executeQuery() is called more than once (DG);
    061     *
    062     */
    063
    064    package org.jfree.data.jdbc;
    065
    066    import java.sql.Connection;
    067    import java.sql.Date;
    068    import java.sql.DriverManager;
    069    import java.sql.ResultSet;
    070    import java.sql.ResultSetMetaData;
    071    import java.sql.SQLException;
    072    import java.sql.Statement;
    073    import java.sql.Types;
    074
    075    import org.jfree.data.category.CategoryDataset;
    076    import org.jfree.data.category.DefaultCategoryDataset;
    077
    078    /**
    079     * A {@link CategoryDataset} implementation over a database JDBC result set.
    080     * The dataset is populated via a call to {@link #executeQuery(String)} with
    081     * the string SQL query.  The SQL query must return at least two columns.  The
    082     * first column will be the category name and remaining columns values (each
    083     * column represents a series).  Subsequent calls to
    084     * {@link #executeQuery(String)} will refresh the dataset.
    085     * <p>
    086     * The database connection is read-only and no write back facility exists.
    087     * <p>
    088     * NOTE: Many people have found this class too restrictive in general use.
    089     * For the greatest flexibility, please consider writing your own code to read
    090     * data from a <code>ResultSet</code> and populate a
    091     * {@link DefaultCategoryDataset} directly.
    092     */
    093    public class JDBCCategoryDataset extends DefaultCategoryDataset {
    094
    095        /** For serialization. */
    096        static final long serialVersionUID = -3080395327918844965L;
    097
    098        /** The database connection. */
    099        private transient Connection connection;
    100
    101        /**
    102         * A flag the controls whether or not the table is transposed.  The default
    103         * is 'true' because this provides the behaviour described in the
    104         * documentation.
    105         */
    106        private boolean transpose = true;
    107
    108
    109        /**
    110         * Creates a new dataset with a database connection.
    111         *
    112         * @param  url  the URL of the database connection.
    113         * @param  driverName  the database driver class name.
    114         * @param  user  the database user.
    115         * @param  passwd  the database user's password.
    116         *
    117         * @throws ClassNotFoundException if the driver cannot be found.
    118         * @throws SQLException if there is an error obtaining a connection to the
    119         *                      database.
    120         */
    121        public JDBCCategoryDataset(String url,
    122                                   String driverName,
    123                                   String user,
    124                                   String passwd)
    125            throws ClassNotFoundException, SQLException {
    126
    127            Class.forName(driverName);
    128            this.connection = DriverManager.getConnection(url, user, passwd);
    129        }
    130
    131        /**
    132         * Create a new dataset with the given database connection.
    133         *
    134         * @param connection  the database connection.
    135         */
    136        public JDBCCategoryDataset(Connection connection) {
    137            if (connection == null) {
    138                throw new NullPointerException("A connection must be supplied.");
    139            }
    140            this.connection = connection;
    141        }
    142
    143        /**
    144         * Creates a new dataset with the given database connection, and executes
    145         * the supplied query to populate the dataset.
    146         *
    147         * @param connection  the connection.
    148         * @param query  the query.
    149         *
    150         * @throws SQLException if there is a problem executing the query.
    151         */
    152        public JDBCCategoryDataset(Connection connection, String query)
    153            throws SQLException {
    154            this(connection);
    155            executeQuery(query);
    156        }
    157
    158        /**
    159         * Returns a flag that controls whether or not the table values are
    160         * transposed when added to the dataset.
    161         *
    162         * @return A boolean.
    163         */
    164        public boolean getTranspose() {
    165            return this.transpose;
    166        }
    167
    168        /**
    169         * Sets a flag that controls whether or not the table values are transposed
    170         * when added to the dataset.
    171         *
    172         * @param transpose  the flag.
    173         */
    174        public void setTranspose(boolean transpose) {
    175            this.transpose = transpose;
    176        }
    177
    178        /**
    179         * Populates the dataset by executing the supplied query against the
    180         * existing database connection.  If no connection exists then no action
    181         * is taken.
    182         * <p>
    183         * The results from the query are extracted and cached locally, thus
    184         * applying an upper limit on how many rows can be retrieved successfully.
    185         *
    186         * @param query  the query.
    187         *
    188         * @throws SQLException if there is a problem executing the query.
    189         */
    190        public void executeQuery(String query) throws SQLException {
    191            executeQuery(this.connection, query);
    192        }
    193
    194        /**
    195         * Populates the dataset by executing the supplied query against the
    196         * existing database connection.  If no connection exists then no action
    197         * is taken.
    198         * <p>
    199         * The results from the query are extracted and cached locally, thus
    200         * applying an upper limit on how many rows can be retrieved successfully.
    201         *
    202         * @param con  the connection.
    203         * @param query  the query.
    204         *
    205         * @throws SQLException if there is a problem executing the query.
    206         */
    207        public void executeQuery(Connection con, String query) throws SQLException {
    208
    209            Statement statement = null;
    210            ResultSet resultSet = null;
    211            try {
    212                statement = con.createStatement();
    213                resultSet = statement.executeQuery(query);
    214                ResultSetMetaData metaData = resultSet.getMetaData();
    215
    216                int columnCount = metaData.getColumnCount();
    217
    218                if (columnCount < 2) {
    219                    throw new SQLException(
    220                        "JDBCCategoryDataset.executeQuery() : insufficient columns "
    221                        + "returned from the database.");
    222                }
    223
    224                // Remove any previous old data
    225                int i = getRowCount();
    226                while (--i >= 0) {
    227                    removeRow(i);
    228                }
    229
    230                while (resultSet.next()) {
    231                    // first column contains the row key...
    232                    Comparable rowKey = resultSet.getString(1);
    233                    for (int column = 2; column <= columnCount; column++) {
    234
    235                        Comparable columnKey = metaData.getColumnName(column);
    236                        int columnType = metaData.getColumnType(column);
    237
    238                        switch (columnType) {
    239                            case Types.TINYINT:
    240                            case Types.SMALLINT:
    241                            case Types.INTEGER:
    242                            case Types.BIGINT:
    243                            case Types.FLOAT:
    244                            case Types.DOUBLE:
    245                            case Types.DECIMAL:
    246                            case Types.NUMERIC:
    247                            case Types.REAL: {
    248                                Number value = (Number) resultSet.getObject(column);
    249                                if (this.transpose) {
    250                                    setValue(value, columnKey, rowKey);
    251                                }
    252                                else {
    253                                    setValue(value, rowKey, columnKey);
    254                                }
    255                                break;
    256                            }
    257                            case Types.DATE:
    258                            case Types.TIME:
    259                            case Types.TIMESTAMP: {
    260                                Date date = (Date) resultSet.getObject(column);
    261                                Number value = new Long(date.getTime());
    262                                if (this.transpose) {
    263                                    setValue(value, columnKey, rowKey);
    264                                }
    265                                else {
    266                                    setValue(value, rowKey, columnKey);
    267                                }
    268                                break;
    269                            }
    270                            case Types.CHAR:
    271                            case Types.VARCHAR:
    272                            case Types.LONGVARCHAR: {
    273                                String string
    274                                    = (String) resultSet.getObject(column);
    275                                try {
    276                                    Number value = Double.valueOf(string);
    277                                    if (this.transpose) {
    278                                        setValue(value, columnKey, rowKey);
    279                                    }
    280                                    else {
    281                                        setValue(value, rowKey, columnKey);
    282                                    }
    283                                }
    284                                catch (NumberFormatException e) {
    285                                    // suppress (value defaults to null)
    286                                }
    287                                break;
    288                            }
    289                            default:
    290                                // not a value, can't use it (defaults to null)
    291                                break;
    292                        }
    293                    }
    294                }
    295
    296                fireDatasetChanged();
    297            }
    298            finally {
    299                if (resultSet != null) {
    300                    try {
    301                        resultSet.close();
    302                    }
    303                    catch (Exception e) {
    304                        // report this?
    305                    }
    306                }
    307                if (statement != null) {
    308                    try {
    309                        statement.close();
    310                    }
    311                    catch (Exception e) {
    312                        // report this?
    313                    }
    314                }
    315            }
    316        }
    317
    318    }