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     * JDBCPieDataset.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, but extending
039     *               DefaultPieDataset (BS);
040     * 24-Jun-2002 : Removed unnecessary import and local variable (DG);
041     * 13-Aug-2002 : Updated Javadoc comments and imports, removed default
042     *               constructor (DG);
043     * 18-Sep-2002 : Updated to support BIGINT (BS);
044     * 21-Jan-2003 : Renamed JdbcPieDataset --> JDBCPieDataset (DG);
045     * 03-Feb-2003 : Added Types.DECIMAL (see bug report 677814) (DG);
046     * 05-Jun-2003 : Updated to support TIME, optimised executeQuery method (BS);
047     * 30-Jul-2003 : Added empty contructor and executeQuery(connection,string)
048     *               method (BS);
049     * 02-Dec-2003 : Throwing exceptions allows to handle errors, removed default
050     *               constructor, as without a connection, a query can never be
051     *               executed (TM);
052     * 04-Dec-2003 : Added missing Javadocs (DG);
053     * ------------- JFREECHART 1.0.x ---------------------------------------------
054     * 02-Feb-2007 : Removed author tags all over JFreeChart sources (DG);
055     *
056     */
057    
058    package org.jfree.data.jdbc;
059    
060    import java.sql.Connection;
061    import java.sql.DriverManager;
062    import java.sql.ResultSet;
063    import java.sql.ResultSetMetaData;
064    import java.sql.SQLException;
065    import java.sql.Statement;
066    import java.sql.Timestamp;
067    import java.sql.Types;
068    
069    import org.jfree.data.general.DefaultPieDataset;
070    import org.jfree.data.general.PieDataset;
071    
072    /**
073     * A {@link PieDataset} that reads data from a database via JDBC.
074     * <P>
075     * A query should be supplied that returns data in two columns, the first
076     * containing VARCHAR data, and the second containing numerical data.  The
077     * data is cached in-memory and can be refreshed at any time.
078     */
079    public class JDBCPieDataset extends DefaultPieDataset {
080    
081        /** For serialization. */
082        static final long serialVersionUID = -8753216855496746108L;
083    
084        /** The database connection. */
085        private transient Connection connection;
086    
087        /**
088         * Creates a new JDBCPieDataset and establishes a new database connection.
089         *
090         * @param url  the URL of the database connection.
091         * @param driverName  the database driver class name.
092         * @param user  the database user.
093         * @param password  the database users password.
094         *
095         * @throws ClassNotFoundException if the driver cannot be found.
096         * @throws SQLException if there is a problem obtaining a database
097         *                      connection.
098         */
099        public JDBCPieDataset(String url,
100                              String driverName,
101                              String user,
102                              String password)
103            throws SQLException, ClassNotFoundException {
104    
105            Class.forName(driverName);
106            this.connection = DriverManager.getConnection(url, user, password);
107        }
108    
109        /**
110         * Creates a new JDBCPieDataset using a pre-existing database connection.
111         * <P>
112         * The dataset is initially empty, since no query has been supplied yet.
113         *
114         * @param con  the database connection.
115         */
116        public JDBCPieDataset(Connection con) {
117            if (con == null) {
118                throw new NullPointerException("A connection must be supplied.");
119            }
120            this.connection = con;
121        }
122    
123    
124        /**
125         * Creates a new JDBCPieDataset using a pre-existing database connection.
126         * <P>
127         * The dataset is initialised with the supplied query.
128         *
129         * @param con  the database connection.
130         * @param query  the database connection.
131         *
132         * @throws SQLException if there is a problem executing the query.
133         */
134        public JDBCPieDataset(Connection con, String query) throws SQLException {
135            this(con);
136            executeQuery(query);
137        }
138    
139        /**
140         *  ExecuteQuery will attempt execute the query passed to it against the
141         *  existing database connection.  If no connection exists then no action
142         *  is taken.
143         *  The results from the query are extracted and cached locally, thus
144         *  applying an upper limit on how many rows can be retrieved successfully.
145         *
146         * @param  query  the query to be executed.
147         *
148         * @throws SQLException if there is a problem executing the query.
149         */
150        public void executeQuery(String query) throws SQLException {
151          executeQuery(this.connection, query);
152        }
153    
154        /**
155         *  ExecuteQuery will attempt execute the query passed to it against the
156         *  existing database connection.  If no connection exists then no action
157         *  is taken.
158         *  The results from the query are extracted and cached locally, thus
159         *  applying an upper limit on how many rows can be retrieved successfully.
160         *
161         * @param  query  the query to be executed
162         * @param  con  the connection the query is to be executed against
163         *
164         * @throws SQLException if there is a problem executing the query.
165         */
166        public void executeQuery(Connection con, String query) throws SQLException {
167    
168            Statement statement = null;
169            ResultSet resultSet = null;
170    
171            try {
172                statement = con.createStatement();
173                resultSet = statement.executeQuery(query);
174                ResultSetMetaData metaData = resultSet.getMetaData();
175    
176                int columnCount = metaData.getColumnCount();
177                if (columnCount != 2) {
178                    throw new SQLException(
179                        "Invalid sql generated.  PieDataSet requires 2 columns only"
180                    );
181                }
182    
183                int columnType = metaData.getColumnType(2);
184                double value = Double.NaN;
185                while (resultSet.next()) {
186                    Comparable key = resultSet.getString(1);
187                    switch (columnType) {
188                        case Types.NUMERIC:
189                        case Types.REAL:
190                        case Types.INTEGER:
191                        case Types.DOUBLE:
192                        case Types.FLOAT:
193                        case Types.DECIMAL:
194                        case Types.BIGINT:
195                            value = resultSet.getDouble(2);
196                            setValue(key, value);
197                            break;
198    
199                        case Types.DATE:
200                        case Types.TIME:
201                        case Types.TIMESTAMP:
202                            Timestamp date = resultSet.getTimestamp(2);
203                            value = date.getTime();
204                            setValue(key, value);
205                            break;
206    
207                        default:
208                            System.err.println(
209                                "JDBCPieDataset - unknown data type"
210                            );
211                            break;
212                    }
213                }
214    
215                fireDatasetChanged();
216    
217            }
218            finally {
219                if (resultSet != null) {
220                    try {
221                        resultSet.close();
222                    }
223                    catch (Exception e) {
224                        System.err.println("JDBCPieDataset: swallowing exception.");
225                    }
226                }
227                if (statement != null) {
228                    try {
229                        statement.close();
230                    }
231                    catch (Exception e) {
232                        System.err.println("JDBCPieDataset: swallowing exception.");
233                    }
234                }
235            }
236        }
237    
238    
239        /**
240         * Close the database connection
241         */
242        public void close() {
243            try {
244                this.connection.close();
245            }
246            catch (Exception e) {
247                System.err.println("JdbcXYDataset: swallowing exception.");
248            }
249        }
250    }