You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

FreeformQuery.java 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507
  1. /*
  2. @VaadinApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.data.util.sqlcontainer.query;
  5. import java.io.IOException;
  6. import java.sql.Connection;
  7. import java.sql.PreparedStatement;
  8. import java.sql.ResultSet;
  9. import java.sql.SQLException;
  10. import java.sql.Statement;
  11. import java.util.ArrayList;
  12. import java.util.Arrays;
  13. import java.util.Collections;
  14. import java.util.List;
  15. import com.vaadin.data.Container.Filter;
  16. import com.vaadin.data.util.sqlcontainer.RowItem;
  17. import com.vaadin.data.util.sqlcontainer.SQLContainer;
  18. import com.vaadin.data.util.sqlcontainer.connection.JDBCConnectionPool;
  19. import com.vaadin.data.util.sqlcontainer.query.generator.StatementHelper;
  20. import com.vaadin.data.util.sqlcontainer.query.generator.filter.QueryBuilder;
  21. @SuppressWarnings("serial")
  22. public class FreeformQuery implements QueryDelegate {
  23. FreeformQueryDelegate delegate = null;
  24. private String queryString;
  25. private List<String> primaryKeyColumns;
  26. private JDBCConnectionPool connectionPool;
  27. private transient Connection activeConnection = null;
  28. /**
  29. * Prevent no-parameters instantiation of FreeformQuery
  30. */
  31. @SuppressWarnings("unused")
  32. private FreeformQuery() {
  33. }
  34. /**
  35. * Creates a new freeform query delegate to be used with the
  36. * {@link SQLContainer}.
  37. *
  38. * @param queryString
  39. * The actual query to perform.
  40. * @param primaryKeyColumns
  41. * The primary key columns. Read-only mode is forced if this
  42. * parameter is null or empty.
  43. * @param connectionPool
  44. * the JDBCConnectionPool to use to open connections to the SQL
  45. * database.
  46. * @deprecated @see
  47. * {@link FreeformQuery#FreeformQuery(String, JDBCConnectionPool, String...)}
  48. */
  49. @Deprecated
  50. public FreeformQuery(String queryString, List<String> primaryKeyColumns,
  51. JDBCConnectionPool connectionPool) {
  52. if (primaryKeyColumns == null) {
  53. primaryKeyColumns = new ArrayList<String>();
  54. }
  55. if (primaryKeyColumns.contains("")) {
  56. throw new IllegalArgumentException(
  57. "The primary key columns contain an empty string!");
  58. } else if (queryString == null || "".equals(queryString)) {
  59. throw new IllegalArgumentException(
  60. "The query string may not be empty or null!");
  61. } else if (connectionPool == null) {
  62. throw new IllegalArgumentException(
  63. "The connectionPool may not be null!");
  64. }
  65. this.queryString = queryString;
  66. this.primaryKeyColumns = Collections
  67. .unmodifiableList(primaryKeyColumns);
  68. this.connectionPool = connectionPool;
  69. }
  70. /**
  71. * Creates a new freeform query delegate to be used with the
  72. * {@link SQLContainer}.
  73. *
  74. * @param queryString
  75. * The actual query to perform.
  76. * @param connectionPool
  77. * the JDBCConnectionPool to use to open connections to the SQL
  78. * database.
  79. * @param primaryKeyColumns
  80. * The primary key columns. Read-only mode is forced if none are
  81. * provided. (optional)
  82. */
  83. public FreeformQuery(String queryString, JDBCConnectionPool connectionPool,
  84. String... primaryKeyColumns) {
  85. this(queryString, Arrays.asList(primaryKeyColumns), connectionPool);
  86. }
  87. /**
  88. * This implementation of getCount() actually fetches all records from the
  89. * database, which might be a performance issue. Override this method with a
  90. * SELECT COUNT(*) ... query if this is too slow for your needs.
  91. *
  92. * {@inheritDoc}
  93. */
  94. @Override
  95. public int getCount() throws SQLException {
  96. // First try the delegate
  97. int count = countByDelegate();
  98. if (count < 0) {
  99. // Couldn't use the delegate, use the bad way.
  100. Connection conn = getConnection();
  101. Statement statement = conn.createStatement(
  102. ResultSet.TYPE_SCROLL_INSENSITIVE,
  103. ResultSet.CONCUR_READ_ONLY);
  104. ResultSet rs = statement.executeQuery(queryString);
  105. if (rs.last()) {
  106. count = rs.getRow();
  107. } else {
  108. count = 0;
  109. }
  110. rs.close();
  111. statement.close();
  112. releaseConnection(conn);
  113. }
  114. return count;
  115. }
  116. @SuppressWarnings("deprecation")
  117. private int countByDelegate() throws SQLException {
  118. int count = -1;
  119. if (delegate == null) {
  120. return count;
  121. }
  122. /* First try using prepared statement */
  123. if (delegate instanceof FreeformStatementDelegate) {
  124. try {
  125. StatementHelper sh = ((FreeformStatementDelegate) delegate)
  126. .getCountStatement();
  127. Connection c = getConnection();
  128. PreparedStatement pstmt = c.prepareStatement(sh
  129. .getQueryString());
  130. sh.setParameterValuesToStatement(pstmt);
  131. ResultSet rs = pstmt.executeQuery();
  132. rs.next();
  133. count = rs.getInt(1);
  134. rs.close();
  135. pstmt.clearParameters();
  136. pstmt.close();
  137. releaseConnection(c);
  138. return count;
  139. } catch (UnsupportedOperationException e) {
  140. // Count statement generation not supported
  141. }
  142. }
  143. /* Try using regular statement */
  144. try {
  145. String countQuery = delegate.getCountQuery();
  146. if (countQuery != null) {
  147. Connection conn = getConnection();
  148. Statement statement = conn.createStatement();
  149. ResultSet rs = statement.executeQuery(countQuery);
  150. rs.next();
  151. count = rs.getInt(1);
  152. rs.close();
  153. statement.close();
  154. releaseConnection(conn);
  155. return count;
  156. }
  157. } catch (UnsupportedOperationException e) {
  158. // Count query generation not supported
  159. }
  160. return count;
  161. }
  162. private Connection getConnection() throws SQLException {
  163. if (activeConnection != null) {
  164. return activeConnection;
  165. }
  166. return connectionPool.reserveConnection();
  167. }
  168. /**
  169. * Fetches the results for the query. This implementation always fetches the
  170. * entire record set, ignoring the offset and page length parameters. In
  171. * order to support lazy loading of records, you must supply a
  172. * FreeformQueryDelegate that implements the
  173. * FreeformQueryDelegate.getQueryString(int,int) method.
  174. *
  175. * @throws SQLException
  176. *
  177. * @see FreeformQueryDelegate#getQueryString(int, int)
  178. */
  179. @Override
  180. @SuppressWarnings("deprecation")
  181. public ResultSet getResults(int offset, int pagelength) throws SQLException {
  182. if (activeConnection == null) {
  183. throw new SQLException("No active transaction!");
  184. }
  185. String query = queryString;
  186. if (delegate != null) {
  187. /* First try using prepared statement */
  188. if (delegate instanceof FreeformStatementDelegate) {
  189. try {
  190. StatementHelper sh = ((FreeformStatementDelegate) delegate)
  191. .getQueryStatement(offset, pagelength);
  192. PreparedStatement pstmt = activeConnection
  193. .prepareStatement(sh.getQueryString());
  194. sh.setParameterValuesToStatement(pstmt);
  195. return pstmt.executeQuery();
  196. } catch (UnsupportedOperationException e) {
  197. // Statement generation not supported, continue...
  198. }
  199. }
  200. try {
  201. query = delegate.getQueryString(offset, pagelength);
  202. } catch (UnsupportedOperationException e) {
  203. // This is fine, we'll just use the default queryString.
  204. }
  205. }
  206. Statement statement = activeConnection.createStatement();
  207. ResultSet rs = statement.executeQuery(query);
  208. return rs;
  209. }
  210. @Override
  211. @SuppressWarnings("deprecation")
  212. public boolean implementationRespectsPagingLimits() {
  213. if (delegate == null) {
  214. return false;
  215. }
  216. /* First try using prepared statement */
  217. if (delegate instanceof FreeformStatementDelegate) {
  218. try {
  219. StatementHelper sh = ((FreeformStatementDelegate) delegate)
  220. .getCountStatement();
  221. if (sh != null && sh.getQueryString() != null
  222. && sh.getQueryString().length() > 0) {
  223. return true;
  224. }
  225. } catch (UnsupportedOperationException e) {
  226. // Statement generation not supported, continue...
  227. }
  228. }
  229. try {
  230. String queryString = delegate.getQueryString(0, 50);
  231. return queryString != null && queryString.length() > 0;
  232. } catch (UnsupportedOperationException e) {
  233. return false;
  234. }
  235. }
  236. /*
  237. * (non-Javadoc)
  238. *
  239. * @see
  240. * com.vaadin.data.util.sqlcontainer.query.QueryDelegate#setFilters(java
  241. * .util.List)
  242. */
  243. @Override
  244. public void setFilters(List<Filter> filters)
  245. throws UnsupportedOperationException {
  246. if (delegate != null) {
  247. delegate.setFilters(filters);
  248. } else if (filters != null) {
  249. throw new UnsupportedOperationException(
  250. "FreeFormQueryDelegate not set!");
  251. }
  252. }
  253. /*
  254. * (non-Javadoc)
  255. *
  256. * @see
  257. * com.vaadin.data.util.sqlcontainer.query.QueryDelegate#setOrderBy(java
  258. * .util.List)
  259. */
  260. @Override
  261. public void setOrderBy(List<OrderBy> orderBys)
  262. throws UnsupportedOperationException {
  263. if (delegate != null) {
  264. delegate.setOrderBy(orderBys);
  265. } else if (orderBys != null) {
  266. throw new UnsupportedOperationException(
  267. "FreeFormQueryDelegate not set!");
  268. }
  269. }
  270. /*
  271. * (non-Javadoc)
  272. *
  273. * @see
  274. * com.vaadin.data.util.sqlcontainer.query.QueryDelegate#storeRow(com.vaadin
  275. * .data.util.sqlcontainer.RowItem)
  276. */
  277. @Override
  278. public int storeRow(RowItem row) throws SQLException {
  279. if (activeConnection == null) {
  280. throw new IllegalStateException("No transaction is active!");
  281. } else if (primaryKeyColumns.isEmpty()) {
  282. throw new UnsupportedOperationException(
  283. "Cannot store items fetched with a read-only freeform query!");
  284. }
  285. if (delegate != null) {
  286. return delegate.storeRow(activeConnection, row);
  287. } else {
  288. throw new UnsupportedOperationException(
  289. "FreeFormQueryDelegate not set!");
  290. }
  291. }
  292. /*
  293. * (non-Javadoc)
  294. *
  295. * @see
  296. * com.vaadin.data.util.sqlcontainer.query.QueryDelegate#removeRow(com.vaadin
  297. * .data.util.sqlcontainer.RowItem)
  298. */
  299. @Override
  300. public boolean removeRow(RowItem row) throws SQLException {
  301. if (activeConnection == null) {
  302. throw new IllegalStateException("No transaction is active!");
  303. } else if (primaryKeyColumns.isEmpty()) {
  304. throw new UnsupportedOperationException(
  305. "Cannot remove items fetched with a read-only freeform query!");
  306. }
  307. if (delegate != null) {
  308. return delegate.removeRow(activeConnection, row);
  309. } else {
  310. throw new UnsupportedOperationException(
  311. "FreeFormQueryDelegate not set!");
  312. }
  313. }
  314. /*
  315. * (non-Javadoc)
  316. *
  317. * @see
  318. * com.vaadin.data.util.sqlcontainer.query.QueryDelegate#beginTransaction()
  319. */
  320. @Override
  321. public synchronized void beginTransaction()
  322. throws UnsupportedOperationException, SQLException {
  323. if (activeConnection != null) {
  324. throw new IllegalStateException("A transaction is already active!");
  325. }
  326. activeConnection = connectionPool.reserveConnection();
  327. activeConnection.setAutoCommit(false);
  328. }
  329. /*
  330. * (non-Javadoc)
  331. *
  332. * @see com.vaadin.data.util.sqlcontainer.query.QueryDelegate#commit()
  333. */
  334. @Override
  335. public synchronized void commit() throws UnsupportedOperationException,
  336. SQLException {
  337. if (activeConnection == null) {
  338. throw new SQLException("No active transaction");
  339. }
  340. if (!activeConnection.getAutoCommit()) {
  341. activeConnection.commit();
  342. }
  343. connectionPool.releaseConnection(activeConnection);
  344. activeConnection = null;
  345. }
  346. /*
  347. * (non-Javadoc)
  348. *
  349. * @see com.vaadin.data.util.sqlcontainer.query.QueryDelegate#rollback()
  350. */
  351. @Override
  352. public synchronized void rollback() throws UnsupportedOperationException,
  353. SQLException {
  354. if (activeConnection == null) {
  355. throw new SQLException("No active transaction");
  356. }
  357. activeConnection.rollback();
  358. connectionPool.releaseConnection(activeConnection);
  359. activeConnection = null;
  360. }
  361. /*
  362. * (non-Javadoc)
  363. *
  364. * @see
  365. * com.vaadin.data.util.sqlcontainer.query.QueryDelegate#getPrimaryKeyColumns
  366. * ()
  367. */
  368. @Override
  369. public List<String> getPrimaryKeyColumns() {
  370. return primaryKeyColumns;
  371. }
  372. public String getQueryString() {
  373. return queryString;
  374. }
  375. public FreeformQueryDelegate getDelegate() {
  376. return delegate;
  377. }
  378. public void setDelegate(FreeformQueryDelegate delegate) {
  379. this.delegate = delegate;
  380. }
  381. /**
  382. * This implementation of the containsRowWithKey method rewrites existing
  383. * WHERE clauses in the query string. The logic is, however, not very
  384. * complex and some times can do the Wrong Thing<sup>TM</sup>. For the
  385. * situations where this logic is not enough, you can implement the
  386. * getContainsRowQueryString method in FreeformQueryDelegate and this will
  387. * be used instead of the logic.
  388. *
  389. * @see FreeformQueryDelegate#getContainsRowQueryString(Object...)
  390. *
  391. */
  392. @Override
  393. @SuppressWarnings("deprecation")
  394. public boolean containsRowWithKey(Object... keys) throws SQLException {
  395. String query = null;
  396. boolean contains = false;
  397. if (delegate != null) {
  398. if (delegate instanceof FreeformStatementDelegate) {
  399. try {
  400. StatementHelper sh = ((FreeformStatementDelegate) delegate)
  401. .getContainsRowQueryStatement(keys);
  402. Connection c = getConnection();
  403. PreparedStatement pstmt = c.prepareStatement(sh
  404. .getQueryString());
  405. sh.setParameterValuesToStatement(pstmt);
  406. ResultSet rs = pstmt.executeQuery();
  407. contains = rs.next();
  408. rs.close();
  409. pstmt.clearParameters();
  410. pstmt.close();
  411. releaseConnection(c);
  412. return contains;
  413. } catch (UnsupportedOperationException e) {
  414. // Statement generation not supported, continue...
  415. }
  416. }
  417. try {
  418. query = delegate.getContainsRowQueryString(keys);
  419. } catch (UnsupportedOperationException e) {
  420. query = modifyWhereClause(keys);
  421. }
  422. } else {
  423. query = modifyWhereClause(keys);
  424. }
  425. Connection conn = getConnection();
  426. try {
  427. Statement statement = conn.createStatement();
  428. ResultSet rs = statement.executeQuery(query);
  429. contains = rs.next();
  430. rs.close();
  431. statement.close();
  432. } finally {
  433. releaseConnection(conn);
  434. }
  435. return contains;
  436. }
  437. /**
  438. * Releases the connection if it is not part of an active transaction.
  439. *
  440. * @param conn
  441. * the connection to release
  442. */
  443. private void releaseConnection(Connection conn) {
  444. if (conn != activeConnection) {
  445. connectionPool.releaseConnection(conn);
  446. }
  447. }
  448. private String modifyWhereClause(Object... keys) {
  449. // Build the where rules for the provided keys
  450. StringBuffer where = new StringBuffer();
  451. for (int ix = 0; ix < primaryKeyColumns.size(); ix++) {
  452. where.append(QueryBuilder.quote(primaryKeyColumns.get(ix)));
  453. if (keys[ix] == null) {
  454. where.append(" IS NULL");
  455. } else {
  456. where.append(" = '").append(keys[ix]).append("'");
  457. }
  458. if (ix < primaryKeyColumns.size() - 1) {
  459. where.append(" AND ");
  460. }
  461. }
  462. // Is there already a WHERE clause in the query string?
  463. int index = queryString.toLowerCase().indexOf("where ");
  464. if (index > -1) {
  465. // Rewrite the where clause
  466. return queryString.substring(0, index) + "WHERE " + where + " AND "
  467. + queryString.substring(index + 6);
  468. }
  469. // Append a where clause
  470. return queryString + " WHERE " + where;
  471. }
  472. private void writeObject(java.io.ObjectOutputStream out) throws IOException {
  473. try {
  474. rollback();
  475. } catch (SQLException ignored) {
  476. }
  477. out.defaultWriteObject();
  478. }
  479. }