1. /* ===========================================================
  2. * JFreeChart : a free chart library for the Java(tm) platform
  3. * ===========================================================
  4. *
  5. * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
  6. *
  7. * Project Info: http://www.jfree.org/jfreechart/index.html
  8. *
  9. * This library is free software; you can redistribute it and/or modify it under the terms
  10. * of the GNU Lesser General Public License as published by the Free Software Foundation;
  11. * either version 2.1 of the License, or (at your option) any later version.
  12. *
  13. * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
  14. * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  15. * See the GNU Lesser General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU Lesser General Public License along with this
  18. * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
  19. * Boston, MA 02111-1307, USA.
  20. *
  21. * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
  22. * in the United States and other countries.]
  23. *
  24. * ------------------------
  25. * JDBCCategoryDataset.java
  26. * ------------------------
  27. * (C) Copyright 2002-2005, by Bryan Scott and Contributors.
  28. *
  29. * Original Author: Bryan Scott; Andy;
  30. * Contributor(s): David Gilbert (for Object Refinery Limited);
  31. * Thomas Morgner;
  32. *
  33. * Changes
  34. * -------
  35. * 26-Apr-2002 : Creation based on JdbcXYDataSet, using code contributed from Andy;
  36. * 13-Aug-2002 : Updated Javadocs, import statements and formatting (DG);
  37. * 03-Sep-2002 : Added fix for bug 591385 (DG);
  38. * 18-Sep-2002 : Updated to support BIGINT (BS);
  39. * 16-Oct-2002 : Added fix for bug 586667 (DG);
  40. * 03-Feb-2003 : Added Types.DECIMAL (see bug report 677814) (DG);
  41. * 13-Jun-2003 : Added Types.TIME as suggest by Bryan Scott in the forum (DG);
  42. * 30-Jun-2003 : CVS Write test (BS);
  43. * 30-Jul-2003 : Added empty contructor and executeQuery(connection,string) method (BS);
  44. * 29-Aug-2003 : Added a 'transpose' flag, so that data can be easily transposed if required (DG);
  45. * 10-Sep-2003 : Added support for additional JDBC types (DG);
  46. * 24-Sep-2003 : Added clearing results from previous queries to executeQuery
  47. * following being highlighted on online forum (BS);
  48. * 02-Dec-2003 : Throwing exceptions allows to handle errors, removed default constructor,
  49. * as without a connection, a query can never be executed (TM);
  50. * 04-Dec-2003 : Added missing Javadocs (DG);
  51. *
  52. */
  53. package org.jfree.data.jdbc;
  54. import java.sql.Connection;
  55. import java.sql.Date;
  56. import java.sql.DriverManager;
  57. import java.sql.ResultSet;
  58. import java.sql.ResultSetMetaData;
  59. import java.sql.SQLException;
  60. import java.sql.Statement;
  61. import java.sql.Types;
  62. import org.jfree.data.category.CategoryDataset;
  63. import org.jfree.data.category.DefaultCategoryDataset;
  64. /**
  65. * A {@link CategoryDataset} implementation over a database JDBC result set.
  66. * The dataset is populated via a call to executeQuery with the string sql
  67. * query.
  68. * The sql query must return at least two columns. The first column will be
  69. * the category name and remaining columns values.
  70. * executeQuery can be called a number of times.
  71. * <p>
  72. * The database connection is read-only and no write back facility exists.
  73. */
  74. public class JDBCCategoryDataset extends DefaultCategoryDataset {
  75. /** The database connection. */
  76. private transient Connection connection;
  77. /**
  78. * A flag the controls whether or not the table is transposed. The default is 'true'
  79. * because this provides the behaviour described in the documentation.
  80. */
  81. private boolean transpose = true;
  82. /**
  83. * Creates a new dataset with a database connection.
  84. *
  85. * @param url the URL of the database connection.
  86. * @param driverName the database driver class name.
  87. * @param user the database user.
  88. * @param passwd the database user's password.
  89. *
  90. * @throws ClassNotFoundException if the driver cannot be found.
  91. * @throws SQLException if there is an error obtaining a connection to the database.
  92. */
  93. public JDBCCategoryDataset(String url,
  94. String driverName,
  95. String user,
  96. String passwd)
  97. throws ClassNotFoundException, SQLException {
  98. Class.forName(driverName);
  99. this.connection = DriverManager.getConnection(url, user, passwd);
  100. }
  101. /**
  102. * Create a new dataset with the given database connection.
  103. *
  104. * @param connection the database connection.
  105. */
  106. public JDBCCategoryDataset(Connection connection) {
  107. if (connection == null) {
  108. throw new NullPointerException("A connection must be supplied.");
  109. }
  110. this.connection = connection;
  111. }
  112. /**
  113. * Creates a new dataset with the given database connection, and executes the supplied
  114. * query to populate the dataset.
  115. *
  116. * @param connection the connection.
  117. * @param query the query.
  118. *
  119. * @throws SQLException if there is a problem executing the query.
  120. */
  121. public JDBCCategoryDataset(Connection connection, String query)
  122. throws SQLException {
  123. this(connection);
  124. executeQuery(query);
  125. }
  126. /**
  127. * Returns a flag that controls whether or not the table values are transposed when added
  128. * to the dataset.
  129. *
  130. * @return A boolean.
  131. */
  132. public boolean getTranspose() {
  133. return this.transpose;
  134. }
  135. /**
  136. * Sets a flag that controls whether or not the table values are transposed when added to
  137. * the dataset.
  138. *
  139. * @param transpose the flag.
  140. */
  141. public void setTranspose(boolean transpose) {
  142. this.transpose = transpose;
  143. }
  144. /**
  145. * Populates the dataset by executing the supplied query against the existing database
  146. * connection. If no connection exists then no action is taken.
  147. * <p>
  148. * The results from the query are extracted and cached locally, thus applying an upper
  149. * limit on how many rows can be retrieved successfully.
  150. *
  151. * @param query the query.
  152. *
  153. * @throws SQLException if there is a problem executing the query.
  154. */
  155. public void executeQuery(String query) throws SQLException {
  156. executeQuery(this.connection, query);
  157. }
  158. /**
  159. * Populates the dataset by executing the supplied query against the existing database
  160. * connection. If no connection exists then no action is taken.
  161. * <p>
  162. * The results from the query are extracted and cached locally, thus applying an upper
  163. * limit on how many rows can be retrieved successfully.
  164. *
  165. * @param con the connection.
  166. * @param query the query.
  167. *
  168. * @throws SQLException if there is a problem executing the query.
  169. */
  170. public void executeQuery(Connection con, String query) throws SQLException {
  171. Statement statement = null;
  172. ResultSet resultSet = null;
  173. try {
  174. statement = con.createStatement();
  175. resultSet = statement.executeQuery(query);
  176. ResultSetMetaData metaData = resultSet.getMetaData();
  177. int columnCount = metaData.getColumnCount();
  178. if (columnCount < 2) {
  179. throw new SQLException(
  180. "JDBCCategoryDataset.executeQuery() : insufficient columns "
  181. + "returned from the database.");
  182. }
  183. // Remove any previous old data
  184. int i = getRowCount();
  185. for (; i > 0; --i) {
  186. removeRow(i);
  187. }
  188. while (resultSet.next()) {
  189. // first column contains the row key...
  190. Comparable rowKey = resultSet.getString(1);
  191. for (int column = 2; column <= columnCount; column++) {
  192. Comparable columnKey = metaData.getColumnName(column);
  193. int columnType = metaData.getColumnType(column);
  194. switch (columnType) {
  195. case Types.TINYINT:
  196. case Types.SMALLINT:
  197. case Types.INTEGER:
  198. case Types.BIGINT:
  199. case Types.FLOAT:
  200. case Types.DOUBLE:
  201. case Types.DECIMAL:
  202. case Types.NUMERIC:
  203. case Types.REAL: {
  204. Number value = (Number) resultSet.getObject(column);
  205. if (this.transpose) {
  206. setValue(value, columnKey, rowKey);
  207. }
  208. else {
  209. setValue(value, rowKey, columnKey);
  210. }
  211. break;
  212. }
  213. case Types.DATE:
  214. case Types.TIME:
  215. case Types.TIMESTAMP: {
  216. Date date = (Date) resultSet.getObject(column);
  217. Number value = new Long(date.getTime());
  218. if (this.transpose) {
  219. setValue(value, columnKey, rowKey);
  220. }
  221. else {
  222. setValue(value, rowKey, columnKey);
  223. }
  224. break;
  225. }
  226. case Types.CHAR:
  227. case Types.VARCHAR:
  228. case Types.LONGVARCHAR: {
  229. String string = (String) resultSet.getObject(column);
  230. try {
  231. Number value = Double.valueOf(string);
  232. if (this.transpose) {
  233. setValue(value, columnKey, rowKey);
  234. }
  235. else {
  236. setValue(value, rowKey, columnKey);
  237. }
  238. }
  239. catch (NumberFormatException e) {
  240. // suppress (value defaults to null)
  241. }
  242. break;
  243. }
  244. default:
  245. // not a value, can't use it (defaults to null)
  246. break;
  247. }
  248. }
  249. }
  250. fireDatasetChanged();
  251. }
  252. finally {
  253. if (resultSet != null) {
  254. try {
  255. resultSet.close();
  256. }
  257. catch (Exception e) {
  258. // report this?
  259. }
  260. }
  261. if (statement != null) {
  262. try {
  263. statement.close();
  264. }
  265. catch (Exception e) {
  266. // report this?
  267. }
  268. }
  269. }
  270. }
  271. }