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 18KB

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