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.

SQLContainer.java 59KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802
  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;
  17. import java.io.IOException;
  18. import java.sql.ResultSet;
  19. import java.sql.ResultSetMetaData;
  20. import java.sql.SQLException;
  21. import java.util.ArrayList;
  22. import java.util.Collection;
  23. import java.util.Collections;
  24. import java.util.ConcurrentModificationException;
  25. import java.util.Date;
  26. import java.util.EventObject;
  27. import java.util.HashMap;
  28. import java.util.LinkedList;
  29. import java.util.List;
  30. import java.util.Map;
  31. import java.util.logging.Level;
  32. import java.util.logging.Logger;
  33. import com.vaadin.data.Container;
  34. import com.vaadin.data.ContainerHelpers;
  35. import com.vaadin.data.Item;
  36. import com.vaadin.data.Property;
  37. import com.vaadin.data.util.filter.Compare.Equal;
  38. import com.vaadin.data.util.filter.Like;
  39. import com.vaadin.data.util.filter.UnsupportedFilterException;
  40. import com.vaadin.data.util.sqlcontainer.query.OrderBy;
  41. import com.vaadin.data.util.sqlcontainer.query.QueryDelegate;
  42. import com.vaadin.data.util.sqlcontainer.query.QueryDelegate.RowIdChangeListener;
  43. import com.vaadin.data.util.sqlcontainer.query.TableQuery;
  44. import com.vaadin.data.util.sqlcontainer.query.generator.MSSQLGenerator;
  45. import com.vaadin.data.util.sqlcontainer.query.generator.OracleGenerator;
  46. public class SQLContainer implements Container, Container.Filterable,
  47. Container.Indexed, Container.Sortable, Container.ItemSetChangeNotifier {
  48. /** Query delegate */
  49. private QueryDelegate delegate;
  50. /** Auto commit mode, default = false */
  51. private boolean autoCommit = false;
  52. /** Page length = number of items contained in one page */
  53. private int pageLength = DEFAULT_PAGE_LENGTH;
  54. public static final int DEFAULT_PAGE_LENGTH = 100;
  55. /** Number of items to cache = CACHE_RATIO x pageLength */
  56. public static final int CACHE_RATIO = 2;
  57. /** Item and index caches */
  58. private final Map<Integer, RowId> itemIndexes = new HashMap<Integer, RowId>();
  59. private final CacheMap<RowId, RowItem> cachedItems = new CacheMap<RowId, RowItem>();
  60. /** Container properties = column names, data types and statuses */
  61. private final List<String> propertyIds = new ArrayList<String>();
  62. private final Map<String, Class<?>> propertyTypes = new HashMap<String, Class<?>>();
  63. private final Map<String, Boolean> propertyReadOnly = new HashMap<String, Boolean>();
  64. private final Map<String, Boolean> propertyPersistable = new HashMap<String, Boolean>();
  65. private final Map<String, Boolean> propertyNullable = new HashMap<String, Boolean>();
  66. private final Map<String, Boolean> propertyPrimaryKey = new HashMap<String, Boolean>();
  67. /** Filters (WHERE) and sorters (ORDER BY) */
  68. private final List<Filter> filters = new ArrayList<Filter>();
  69. private final List<OrderBy> sorters = new ArrayList<OrderBy>();
  70. /**
  71. * Total number of items available in the data source using the current
  72. * query, filters and sorters.
  73. */
  74. private int size;
  75. /**
  76. * Size updating logic. Do not update size from data source if it has been
  77. * updated in the last sizeValidMilliSeconds milliseconds.
  78. */
  79. private final int sizeValidMilliSeconds = 10000;
  80. private boolean sizeDirty = true;
  81. private Date sizeUpdated = new Date();
  82. /** Starting row number of the currently fetched page */
  83. private int currentOffset;
  84. /** ItemSetChangeListeners */
  85. private LinkedList<Container.ItemSetChangeListener> itemSetChangeListeners;
  86. /** Temporary storage for modified items and items to be removed and added */
  87. private final Map<RowId, RowItem> removedItems = new HashMap<RowId, RowItem>();
  88. private final List<RowItem> addedItems = new ArrayList<RowItem>();
  89. private final List<RowItem> modifiedItems = new ArrayList<RowItem>();
  90. /** List of references to other SQLContainers */
  91. private final Map<SQLContainer, Reference> references = new HashMap<SQLContainer, Reference>();
  92. /** Cache flush notification system enabled. Disabled by default. */
  93. private boolean notificationsEnabled;
  94. /**
  95. * Prevent instantiation without a QueryDelegate.
  96. */
  97. @SuppressWarnings("unused")
  98. private SQLContainer() {
  99. }
  100. /**
  101. * Creates and initializes SQLContainer using the given QueryDelegate
  102. *
  103. * @param delegate
  104. * QueryDelegate implementation
  105. * @throws SQLException
  106. */
  107. public SQLContainer(QueryDelegate delegate) throws SQLException {
  108. if (delegate == null) {
  109. throw new IllegalArgumentException(
  110. "QueryDelegate must not be null.");
  111. }
  112. this.delegate = delegate;
  113. getPropertyIds();
  114. cachedItems.setCacheLimit(CACHE_RATIO * getPageLength());
  115. }
  116. /**************************************/
  117. /** Methods from interface Container **/
  118. /**************************************/
  119. /**
  120. * Note! If auto commit mode is enabled, this method will still return the
  121. * temporary row ID assigned for the item. Implement
  122. * QueryDelegate.RowIdChangeListener to receive the actual Row ID value
  123. * after the addition has been committed.
  124. *
  125. * {@inheritDoc}
  126. */
  127. @Override
  128. public Object addItem() throws UnsupportedOperationException {
  129. Object emptyKey[] = new Object[delegate.getPrimaryKeyColumns().size()];
  130. RowId itemId = new TemporaryRowId(emptyKey);
  131. // Create new empty column properties for the row item.
  132. List<ColumnProperty> itemProperties = new ArrayList<ColumnProperty>();
  133. for (String propertyId : propertyIds) {
  134. /* Default settings for new item properties. */
  135. ColumnProperty cp = new ColumnProperty(propertyId,
  136. propertyReadOnly.get(propertyId),
  137. propertyPersistable.get(propertyId),
  138. propertyNullable.get(propertyId),
  139. propertyPrimaryKey.get(propertyId), null,
  140. getType(propertyId));
  141. itemProperties.add(cp);
  142. }
  143. RowItem newRowItem = new RowItem(this, itemId, itemProperties);
  144. if (autoCommit) {
  145. /* Add and commit instantly */
  146. try {
  147. if (delegate instanceof TableQuery) {
  148. itemId = ((TableQuery) delegate)
  149. .storeRowImmediately(newRowItem);
  150. } else {
  151. delegate.beginTransaction();
  152. delegate.storeRow(newRowItem);
  153. delegate.commit();
  154. }
  155. refresh();
  156. if (notificationsEnabled) {
  157. CacheFlushNotifier.notifyOfCacheFlush(this);
  158. }
  159. getLogger().log(Level.FINER, "Row added to DB...");
  160. return itemId;
  161. } catch (SQLException e) {
  162. getLogger().log(Level.WARNING,
  163. "Failed to add row to DB. Rolling back.", e);
  164. try {
  165. delegate.rollback();
  166. } catch (SQLException ee) {
  167. getLogger().log(Level.SEVERE,
  168. "Failed to roll back row addition", e);
  169. }
  170. return null;
  171. }
  172. } else {
  173. addedItems.add(newRowItem);
  174. fireContentsChange();
  175. return itemId;
  176. }
  177. }
  178. /*
  179. * (non-Javadoc)
  180. *
  181. * @see com.vaadin.data.Container#containsId(java.lang.Object)
  182. */
  183. @Override
  184. public boolean containsId(Object itemId) {
  185. if (itemId == null) {
  186. return false;
  187. }
  188. if (cachedItems.containsKey(itemId)) {
  189. return true;
  190. } else {
  191. for (RowItem item : addedItems) {
  192. if (item.getId().equals(itemId)) {
  193. return itemPassesFilters(item);
  194. }
  195. }
  196. }
  197. if (removedItems.containsKey(itemId)) {
  198. return false;
  199. }
  200. if (itemId instanceof ReadOnlyRowId) {
  201. int rowNum = ((ReadOnlyRowId) itemId).getRowNum();
  202. return rowNum >= 0 && rowNum < size;
  203. }
  204. if (itemId instanceof RowId && !(itemId instanceof TemporaryRowId)) {
  205. try {
  206. return delegate.containsRowWithKey(((RowId) itemId).getId());
  207. } catch (Exception e) {
  208. /* Query failed, just return false. */
  209. getLogger().log(Level.WARNING, "containsId query failed", e);
  210. }
  211. }
  212. return false;
  213. }
  214. /*
  215. * (non-Javadoc)
  216. *
  217. * @see com.vaadin.data.Container#getContainerProperty(java.lang.Object,
  218. * java.lang.Object)
  219. */
  220. @Override
  221. public Property getContainerProperty(Object itemId, Object propertyId) {
  222. Item item = getItem(itemId);
  223. if (item == null) {
  224. return null;
  225. }
  226. return item.getItemProperty(propertyId);
  227. }
  228. /*
  229. * (non-Javadoc)
  230. *
  231. * @see com.vaadin.data.Container#getContainerPropertyIds()
  232. */
  233. @Override
  234. public Collection<?> getContainerPropertyIds() {
  235. return Collections.unmodifiableCollection(propertyIds);
  236. }
  237. /*
  238. * (non-Javadoc)
  239. *
  240. * @see com.vaadin.data.Container#getItem(java.lang.Object)
  241. */
  242. @Override
  243. public Item getItem(Object itemId) {
  244. if (!cachedItems.containsKey(itemId)) {
  245. int index = indexOfId(itemId);
  246. if (index >= size) {
  247. // The index is in the added items
  248. int offset = index - size;
  249. RowItem item = addedItems.get(offset);
  250. if (itemPassesFilters(item)) {
  251. return item;
  252. } else {
  253. return null;
  254. }
  255. } else {
  256. // load the item into cache
  257. updateOffsetAndCache(index);
  258. }
  259. }
  260. return cachedItems.get(itemId);
  261. }
  262. /**
  263. * Bypasses in-memory filtering to return items that are cached in memory.
  264. * <em>NOTE</em>: This does not bypass database-level filtering.
  265. *
  266. * @param itemId
  267. * the id of the item to retrieve.
  268. * @return the item represented by itemId.
  269. */
  270. public Item getItemUnfiltered(Object itemId) {
  271. if (!cachedItems.containsKey(itemId)) {
  272. for (RowItem item : addedItems) {
  273. if (item.getId().equals(itemId)) {
  274. return item;
  275. }
  276. }
  277. }
  278. return cachedItems.get(itemId);
  279. }
  280. /**
  281. * NOTE! Do not use this method if in any way avoidable. This method doesn't
  282. * (and cannot) use lazy loading, which means that all rows in the database
  283. * will be loaded into memory.
  284. *
  285. * {@inheritDoc}
  286. */
  287. @Override
  288. public Collection<?> getItemIds() {
  289. updateCount();
  290. ArrayList<RowId> ids = new ArrayList<RowId>();
  291. ResultSet rs = null;
  292. try {
  293. // Load ALL rows :(
  294. delegate.beginTransaction();
  295. rs = delegate.getResults(0, 0);
  296. List<String> pKeys = delegate.getPrimaryKeyColumns();
  297. while (rs.next()) {
  298. RowId id = null;
  299. if (pKeys.isEmpty()) {
  300. /* Create a read only itemId */
  301. id = new ReadOnlyRowId(rs.getRow());
  302. } else {
  303. /* Generate itemId for the row based on primary key(s) */
  304. Object[] itemId = new Object[pKeys.size()];
  305. for (int i = 0; i < pKeys.size(); i++) {
  306. itemId[i] = rs.getObject(pKeys.get(i));
  307. }
  308. id = new RowId(itemId);
  309. }
  310. if (id != null && !removedItems.containsKey(id)) {
  311. ids.add(id);
  312. }
  313. }
  314. rs.getStatement().close();
  315. rs.close();
  316. delegate.commit();
  317. } catch (SQLException e) {
  318. getLogger().log(Level.WARNING,
  319. "getItemIds() failed, rolling back.", e);
  320. try {
  321. delegate.rollback();
  322. } catch (SQLException e1) {
  323. getLogger().log(Level.SEVERE, "Failed to roll back state", e1);
  324. }
  325. try {
  326. rs.getStatement().close();
  327. rs.close();
  328. } catch (SQLException e1) {
  329. getLogger().log(Level.WARNING, "Closing session failed", e1);
  330. }
  331. throw new RuntimeException("Failed to fetch item indexes.", e);
  332. }
  333. for (RowItem item : getFilteredAddedItems()) {
  334. ids.add(item.getId());
  335. }
  336. return Collections.unmodifiableCollection(ids);
  337. }
  338. /*
  339. * (non-Javadoc)
  340. *
  341. * @see com.vaadin.data.Container#getType(java.lang.Object)
  342. */
  343. @Override
  344. public Class<?> getType(Object propertyId) {
  345. if (!propertyIds.contains(propertyId)) {
  346. return null;
  347. }
  348. return propertyTypes.get(propertyId);
  349. }
  350. /*
  351. * (non-Javadoc)
  352. *
  353. * @see com.vaadin.data.Container#size()
  354. */
  355. @Override
  356. public int size() {
  357. updateCount();
  358. return size + sizeOfAddedItems() - removedItems.size();
  359. }
  360. /*
  361. * (non-Javadoc)
  362. *
  363. * @see com.vaadin.data.Container#removeItem(java.lang.Object)
  364. */
  365. @Override
  366. public boolean removeItem(Object itemId)
  367. throws UnsupportedOperationException {
  368. if (!containsId(itemId)) {
  369. return false;
  370. }
  371. for (RowItem item : addedItems) {
  372. if (item.getId().equals(itemId)) {
  373. addedItems.remove(item);
  374. fireContentsChange();
  375. return true;
  376. }
  377. }
  378. if (autoCommit) {
  379. /* Remove and commit instantly. */
  380. Item i = getItem(itemId);
  381. if (i == null) {
  382. return false;
  383. }
  384. try {
  385. delegate.beginTransaction();
  386. boolean success = delegate.removeRow((RowItem) i);
  387. delegate.commit();
  388. refresh();
  389. if (notificationsEnabled) {
  390. CacheFlushNotifier.notifyOfCacheFlush(this);
  391. }
  392. if (success) {
  393. getLogger().log(Level.FINER, "Row removed from DB...");
  394. }
  395. return success;
  396. } catch (SQLException e) {
  397. getLogger().log(Level.WARNING,
  398. "Failed to remove row, rolling back", e);
  399. try {
  400. delegate.rollback();
  401. } catch (SQLException ee) {
  402. /* Nothing can be done here */
  403. getLogger().log(Level.SEVERE,
  404. "Failed to rollback row removal", ee);
  405. }
  406. return false;
  407. } catch (OptimisticLockException e) {
  408. getLogger().log(Level.WARNING,
  409. "Failed to remove row, rolling back", e);
  410. try {
  411. delegate.rollback();
  412. } catch (SQLException ee) {
  413. /* Nothing can be done here */
  414. getLogger().log(Level.SEVERE,
  415. "Failed to rollback row removal", ee);
  416. }
  417. throw e;
  418. }
  419. } else {
  420. removedItems.put((RowId) itemId, (RowItem) getItem(itemId));
  421. cachedItems.remove(itemId);
  422. refresh();
  423. return true;
  424. }
  425. }
  426. /*
  427. * (non-Javadoc)
  428. *
  429. * @see com.vaadin.data.Container#removeAllItems()
  430. */
  431. @Override
  432. public boolean removeAllItems() throws UnsupportedOperationException {
  433. if (autoCommit) {
  434. /* Remove and commit instantly. */
  435. try {
  436. delegate.beginTransaction();
  437. boolean success = true;
  438. for (Object id : getItemIds()) {
  439. if (!delegate.removeRow((RowItem) getItem(id))) {
  440. success = false;
  441. }
  442. }
  443. if (success) {
  444. delegate.commit();
  445. getLogger().log(Level.FINER, "All rows removed from DB...");
  446. refresh();
  447. if (notificationsEnabled) {
  448. CacheFlushNotifier.notifyOfCacheFlush(this);
  449. }
  450. } else {
  451. delegate.rollback();
  452. }
  453. return success;
  454. } catch (SQLException e) {
  455. getLogger().log(Level.WARNING,
  456. "removeAllItems() failed, rolling back", e);
  457. try {
  458. delegate.rollback();
  459. } catch (SQLException ee) {
  460. /* Nothing can be done here */
  461. getLogger().log(Level.SEVERE, "Failed to roll back", ee);
  462. }
  463. return false;
  464. } catch (OptimisticLockException e) {
  465. getLogger().log(Level.WARNING,
  466. "removeAllItems() failed, rolling back", e);
  467. try {
  468. delegate.rollback();
  469. } catch (SQLException ee) {
  470. /* Nothing can be done here */
  471. getLogger().log(Level.SEVERE, "Failed to roll back", ee);
  472. }
  473. throw e;
  474. }
  475. } else {
  476. for (Object id : getItemIds()) {
  477. removedItems.put((RowId) id, (RowItem) getItem(id));
  478. cachedItems.remove(id);
  479. }
  480. refresh();
  481. return true;
  482. }
  483. }
  484. /*************************************************/
  485. /** Methods from interface Container.Filterable **/
  486. /*************************************************/
  487. /**
  488. * {@inheritDoc}
  489. */
  490. @Override
  491. public void addContainerFilter(Filter filter)
  492. throws UnsupportedFilterException {
  493. // filter.setCaseSensitive(!ignoreCase);
  494. filters.add(filter);
  495. refresh();
  496. }
  497. /**
  498. * {@inheritDoc}
  499. */
  500. @Override
  501. public void removeContainerFilter(Filter filter) {
  502. filters.remove(filter);
  503. refresh();
  504. }
  505. /**
  506. * {@inheritDoc}
  507. */
  508. public void addContainerFilter(Object propertyId, String filterString,
  509. boolean ignoreCase, boolean onlyMatchPrefix) {
  510. if (propertyId == null || !propertyIds.contains(propertyId)) {
  511. return;
  512. }
  513. /* Generate Filter -object */
  514. String likeStr = onlyMatchPrefix ? filterString + "%" : "%"
  515. + filterString + "%";
  516. Like like = new Like(propertyId.toString(), likeStr);
  517. like.setCaseSensitive(!ignoreCase);
  518. filters.add(like);
  519. refresh();
  520. }
  521. /**
  522. * {@inheritDoc}
  523. */
  524. public void removeContainerFilters(Object propertyId) {
  525. ArrayList<Filter> toRemove = new ArrayList<Filter>();
  526. for (Filter f : filters) {
  527. if (f.appliesToProperty(propertyId)) {
  528. toRemove.add(f);
  529. }
  530. }
  531. filters.removeAll(toRemove);
  532. refresh();
  533. }
  534. /**
  535. * {@inheritDoc}
  536. */
  537. @Override
  538. public void removeAllContainerFilters() {
  539. filters.clear();
  540. refresh();
  541. }
  542. /**********************************************/
  543. /** Methods from interface Container.Indexed **/
  544. /**********************************************/
  545. /*
  546. * (non-Javadoc)
  547. *
  548. * @see com.vaadin.data.Container.Indexed#indexOfId(java.lang.Object)
  549. */
  550. @Override
  551. public int indexOfId(Object itemId) {
  552. // First check if the id is in the added items
  553. for (int ix = 0; ix < addedItems.size(); ix++) {
  554. RowItem item = addedItems.get(ix);
  555. if (item.getId().equals(itemId)) {
  556. if (itemPassesFilters(item)) {
  557. updateCount();
  558. return size + ix;
  559. } else {
  560. return -1;
  561. }
  562. }
  563. }
  564. if (!containsId(itemId)) {
  565. return -1;
  566. }
  567. if (cachedItems.isEmpty()) {
  568. getPage();
  569. }
  570. int size = size();
  571. boolean wrappedAround = false;
  572. while (!wrappedAround) {
  573. for (Integer i : itemIndexes.keySet()) {
  574. if (itemIndexes.get(i).equals(itemId)) {
  575. return i;
  576. }
  577. }
  578. // load in the next page.
  579. int nextIndex = (currentOffset / (pageLength * CACHE_RATIO) + 1)
  580. * (pageLength * CACHE_RATIO);
  581. if (nextIndex >= size) {
  582. // Container wrapped around, start from index 0.
  583. wrappedAround = true;
  584. nextIndex = 0;
  585. }
  586. updateOffsetAndCache(nextIndex);
  587. }
  588. return -1;
  589. }
  590. /*
  591. * (non-Javadoc)
  592. *
  593. * @see com.vaadin.data.Container.Indexed#getIdByIndex(int)
  594. */
  595. @Override
  596. public Object getIdByIndex(int index) {
  597. if (index < 0) {
  598. throw new IndexOutOfBoundsException("Index is negative! index="
  599. + index);
  600. }
  601. // make sure the size field is valid
  602. updateCount();
  603. if (index < size) {
  604. if (itemIndexes.keySet().contains(index)) {
  605. return itemIndexes.get(index);
  606. }
  607. updateOffsetAndCache(index);
  608. return itemIndexes.get(index);
  609. } else {
  610. // The index is in the added items
  611. int offset = index - size;
  612. // TODO this is very inefficient if looping - should improve
  613. // getItemIds(int, int)
  614. return getFilteredAddedItems().get(offset).getId();
  615. }
  616. }
  617. @Override
  618. public List<Object> getItemIds(int startIndex, int numberOfIds) {
  619. // TODO create a better implementation
  620. return (List<Object>) ContainerHelpers.getItemIdsUsingGetIdByIndex(
  621. startIndex, numberOfIds, this);
  622. }
  623. /**********************************************/
  624. /** Methods from interface Container.Ordered **/
  625. /**********************************************/
  626. /*
  627. * (non-Javadoc)
  628. *
  629. * @see com.vaadin.data.Container.Ordered#nextItemId(java.lang.Object)
  630. */
  631. @Override
  632. public Object nextItemId(Object itemId) {
  633. int index = indexOfId(itemId) + 1;
  634. try {
  635. return getIdByIndex(index);
  636. } catch (IndexOutOfBoundsException e) {
  637. return null;
  638. }
  639. }
  640. /*
  641. * (non-Javadoc)
  642. *
  643. * @see com.vaadin.data.Container.Ordered#prevItemId(java.lang.Object)
  644. */
  645. @Override
  646. public Object prevItemId(Object itemId) {
  647. return getIdByIndex(indexOfId(itemId) - 1);
  648. }
  649. /*
  650. * (non-Javadoc)
  651. *
  652. * @see com.vaadin.data.Container.Ordered#firstItemId()
  653. */
  654. @Override
  655. public Object firstItemId() {
  656. updateCount();
  657. if (size == 0) {
  658. if (addedItems.isEmpty()) {
  659. return null;
  660. } else {
  661. int ix = -1;
  662. do {
  663. ix++;
  664. } while (!itemPassesFilters(addedItems.get(ix))
  665. && ix < addedItems.size());
  666. if (ix < addedItems.size()) {
  667. return addedItems.get(ix).getId();
  668. }
  669. }
  670. }
  671. if (!itemIndexes.containsKey(0)) {
  672. updateOffsetAndCache(0);
  673. }
  674. return itemIndexes.get(0);
  675. }
  676. /*
  677. * (non-Javadoc)
  678. *
  679. * @see com.vaadin.data.Container.Ordered#lastItemId()
  680. */
  681. @Override
  682. public Object lastItemId() {
  683. if (addedItems.isEmpty()) {
  684. int lastIx = size() - 1;
  685. if (!itemIndexes.containsKey(lastIx)) {
  686. updateOffsetAndCache(size - 1);
  687. }
  688. return itemIndexes.get(lastIx);
  689. } else {
  690. int ix = addedItems.size();
  691. do {
  692. ix--;
  693. } while (!itemPassesFilters(addedItems.get(ix)) && ix >= 0);
  694. if (ix >= 0) {
  695. return addedItems.get(ix).getId();
  696. } else {
  697. return null;
  698. }
  699. }
  700. }
  701. /*
  702. * (non-Javadoc)
  703. *
  704. * @see com.vaadin.data.Container.Ordered#isFirstId(java.lang.Object)
  705. */
  706. @Override
  707. public boolean isFirstId(Object itemId) {
  708. return firstItemId().equals(itemId);
  709. }
  710. /*
  711. * (non-Javadoc)
  712. *
  713. * @see com.vaadin.data.Container.Ordered#isLastId(java.lang.Object)
  714. */
  715. @Override
  716. public boolean isLastId(Object itemId) {
  717. return lastItemId().equals(itemId);
  718. }
  719. /***********************************************/
  720. /** Methods from interface Container.Sortable **/
  721. /***********************************************/
  722. /*
  723. * (non-Javadoc)
  724. *
  725. * @see com.vaadin.data.Container.Sortable#sort(java.lang.Object[],
  726. * boolean[])
  727. */
  728. @Override
  729. public void sort(Object[] propertyId, boolean[] ascending) {
  730. sorters.clear();
  731. if (propertyId == null || propertyId.length == 0) {
  732. refresh();
  733. return;
  734. }
  735. /* Generate OrderBy -objects */
  736. boolean asc = true;
  737. for (int i = 0; i < propertyId.length; i++) {
  738. /* Check that the property id is valid */
  739. if (propertyId[i] instanceof String
  740. && propertyIds.contains(propertyId[i])) {
  741. try {
  742. asc = ascending[i];
  743. } catch (Exception e) {
  744. getLogger().log(Level.WARNING, "", e);
  745. }
  746. sorters.add(new OrderBy((String) propertyId[i], asc));
  747. }
  748. }
  749. refresh();
  750. }
  751. /*
  752. * (non-Javadoc)
  753. *
  754. * @see com.vaadin.data.Container.Sortable#getSortableContainerPropertyIds()
  755. */
  756. @Override
  757. public Collection<?> getSortableContainerPropertyIds() {
  758. return getContainerPropertyIds();
  759. }
  760. /**************************************/
  761. /** Methods specific to SQLContainer **/
  762. /**************************************/
  763. /**
  764. * Refreshes the container - clears all caches and resets size and offset.
  765. * Does NOT remove sorting or filtering rules!
  766. */
  767. public void refresh() {
  768. sizeDirty = true;
  769. currentOffset = 0;
  770. cachedItems.clear();
  771. itemIndexes.clear();
  772. fireContentsChange();
  773. }
  774. /**
  775. * Returns modify state of the container.
  776. *
  777. * @return true if contents of this container have been modified
  778. */
  779. public boolean isModified() {
  780. return !removedItems.isEmpty() || !addedItems.isEmpty()
  781. || !modifiedItems.isEmpty();
  782. }
  783. /**
  784. * Set auto commit mode enabled or disabled. Auto commit mode means that all
  785. * changes made to items of this container will be immediately written to
  786. * the underlying data source.
  787. *
  788. * @param autoCommitEnabled
  789. * true to enable auto commit mode
  790. */
  791. public void setAutoCommit(boolean autoCommitEnabled) {
  792. autoCommit = autoCommitEnabled;
  793. }
  794. /**
  795. * Returns status of the auto commit mode.
  796. *
  797. * @return true if auto commit mode is enabled
  798. */
  799. public boolean isAutoCommit() {
  800. return autoCommit;
  801. }
  802. /**
  803. * Returns the currently set page length.
  804. *
  805. * @return current page length
  806. */
  807. public int getPageLength() {
  808. return pageLength;
  809. }
  810. /**
  811. * Sets the page length used in lazy fetching of items from the data source.
  812. * Also resets the cache size to match the new page length.
  813. *
  814. * As a side effect the container will be refreshed.
  815. *
  816. * @param pageLength
  817. * new page length
  818. */
  819. public void setPageLength(int pageLength) {
  820. setPageLengthInternal(pageLength);
  821. refresh();
  822. }
  823. /**
  824. * Sets the page length internally, without refreshing the container.
  825. *
  826. * @param pageLength
  827. * the new page length
  828. */
  829. private void setPageLengthInternal(int pageLength) {
  830. this.pageLength = pageLength > 0 ? pageLength : DEFAULT_PAGE_LENGTH;
  831. cachedItems.setCacheLimit(CACHE_RATIO * getPageLength());
  832. }
  833. /**
  834. * Adds the given OrderBy to this container and refreshes the container
  835. * contents with the new sorting rules.
  836. *
  837. * Note that orderBy.getColumn() must return a column name that exists in
  838. * this container.
  839. *
  840. * @param orderBy
  841. * OrderBy to be added to the container sorting rules
  842. */
  843. public void addOrderBy(OrderBy orderBy) {
  844. if (orderBy == null) {
  845. return;
  846. }
  847. if (!propertyIds.contains(orderBy.getColumn())) {
  848. throw new IllegalArgumentException(
  849. "The column given for sorting does not exist in this container.");
  850. }
  851. sorters.add(orderBy);
  852. refresh();
  853. }
  854. /**
  855. * Commits all the changes, additions and removals made to the items of this
  856. * container.
  857. *
  858. * @throws UnsupportedOperationException
  859. * @throws SQLException
  860. */
  861. public void commit() throws UnsupportedOperationException, SQLException {
  862. try {
  863. getLogger().log(Level.FINER,
  864. "Commiting changes through delegate...");
  865. delegate.beginTransaction();
  866. /* Perform buffered deletions */
  867. for (RowItem item : removedItems.values()) {
  868. if (!delegate.removeRow(item)) {
  869. throw new SQLException("Removal failed for row with ID: "
  870. + item.getId());
  871. }
  872. }
  873. /* Perform buffered modifications */
  874. for (RowItem item : modifiedItems) {
  875. if (delegate.storeRow(item) > 0) {
  876. /*
  877. * Also reset the modified state in the item in case it is
  878. * reused e.g. in a form.
  879. */
  880. item.commit();
  881. } else {
  882. delegate.rollback();
  883. refresh();
  884. throw new ConcurrentModificationException(
  885. "Item with the ID '" + item.getId()
  886. + "' has been externally modified.");
  887. }
  888. }
  889. /* Perform buffered additions */
  890. for (RowItem item : addedItems) {
  891. delegate.storeRow(item);
  892. }
  893. delegate.commit();
  894. removedItems.clear();
  895. addedItems.clear();
  896. modifiedItems.clear();
  897. refresh();
  898. if (notificationsEnabled) {
  899. CacheFlushNotifier.notifyOfCacheFlush(this);
  900. }
  901. } catch (SQLException e) {
  902. delegate.rollback();
  903. throw e;
  904. } catch (OptimisticLockException e) {
  905. delegate.rollback();
  906. throw e;
  907. }
  908. }
  909. /**
  910. * Rolls back all the changes, additions and removals made to the items of
  911. * this container.
  912. *
  913. * @throws UnsupportedOperationException
  914. * @throws SQLException
  915. */
  916. public void rollback() throws UnsupportedOperationException, SQLException {
  917. getLogger().log(Level.FINE, "Rolling back changes...");
  918. removedItems.clear();
  919. addedItems.clear();
  920. modifiedItems.clear();
  921. refresh();
  922. }
  923. /**
  924. * Notifies this container that a property in the given item has been
  925. * modified. The change will be buffered or made instantaneously depending
  926. * on auto commit mode.
  927. *
  928. * @param changedItem
  929. * item that has a modified property
  930. */
  931. void itemChangeNotification(RowItem changedItem) {
  932. if (autoCommit) {
  933. try {
  934. delegate.beginTransaction();
  935. if (delegate.storeRow(changedItem) == 0) {
  936. delegate.rollback();
  937. refresh();
  938. throw new ConcurrentModificationException(
  939. "Item with the ID '" + changedItem.getId()
  940. + "' has been externally modified.");
  941. }
  942. delegate.commit();
  943. if (notificationsEnabled) {
  944. CacheFlushNotifier.notifyOfCacheFlush(this);
  945. }
  946. getLogger().log(Level.FINER, "Row updated to DB...");
  947. } catch (SQLException e) {
  948. getLogger().log(Level.WARNING,
  949. "itemChangeNotification failed, rolling back...", e);
  950. try {
  951. delegate.rollback();
  952. } catch (SQLException ee) {
  953. /* Nothing can be done here */
  954. getLogger().log(Level.SEVERE, "Rollback failed", e);
  955. }
  956. throw new RuntimeException(e);
  957. }
  958. } else {
  959. if (!(changedItem.getId() instanceof TemporaryRowId)
  960. && !modifiedItems.contains(changedItem)) {
  961. modifiedItems.add(changedItem);
  962. }
  963. }
  964. }
  965. /**
  966. * Determines a new offset for updating the row cache. The offset is
  967. * calculated from the given index, and will be fixed to match the start of
  968. * a page, based on the value of pageLength.
  969. *
  970. * @param index
  971. * Index of the item that was requested, but not found in cache
  972. */
  973. private void updateOffsetAndCache(int index) {
  974. if (itemIndexes.containsKey(index)) {
  975. return;
  976. }
  977. currentOffset = (index / (pageLength * CACHE_RATIO))
  978. * (pageLength * CACHE_RATIO);
  979. if (currentOffset < 0) {
  980. currentOffset = 0;
  981. }
  982. getPage();
  983. }
  984. /**
  985. * Fetches new count of rows from the data source, if needed.
  986. */
  987. private void updateCount() {
  988. if (!sizeDirty
  989. && new Date().getTime() < sizeUpdated.getTime()
  990. + sizeValidMilliSeconds) {
  991. return;
  992. }
  993. try {
  994. try {
  995. delegate.setFilters(filters);
  996. } catch (UnsupportedOperationException e) {
  997. getLogger().log(Level.FINE,
  998. "The query delegate doesn't support filtering", e);
  999. }
  1000. try {
  1001. delegate.setOrderBy(sorters);
  1002. } catch (UnsupportedOperationException e) {
  1003. getLogger().log(Level.FINE,
  1004. "The query delegate doesn't support filtering", e);
  1005. }
  1006. int newSize = delegate.getCount();
  1007. if (newSize != size) {
  1008. size = newSize;
  1009. refresh();
  1010. }
  1011. sizeUpdated = new Date();
  1012. sizeDirty = false;
  1013. getLogger().log(Level.FINER,
  1014. "Updated row count. New count is: " + size);
  1015. } catch (SQLException e) {
  1016. throw new RuntimeException("Failed to update item set size.", e);
  1017. }
  1018. }
  1019. /**
  1020. * Fetches property id's (column names and their types) from the data
  1021. * source.
  1022. *
  1023. * @throws SQLException
  1024. */
  1025. private void getPropertyIds() throws SQLException {
  1026. propertyIds.clear();
  1027. propertyTypes.clear();
  1028. delegate.setFilters(null);
  1029. delegate.setOrderBy(null);
  1030. ResultSet rs = null;
  1031. ResultSetMetaData rsmd = null;
  1032. try {
  1033. delegate.beginTransaction();
  1034. rs = delegate.getResults(0, 1);
  1035. boolean resultExists = rs.next();
  1036. rsmd = rs.getMetaData();
  1037. Class<?> type = null;
  1038. for (int i = 1; i <= rsmd.getColumnCount(); i++) {
  1039. if (!isColumnIdentifierValid(rsmd.getColumnLabel(i))) {
  1040. continue;
  1041. }
  1042. String colName = rsmd.getColumnLabel(i);
  1043. /*
  1044. * Make sure not to add the same colName twice. This can easily
  1045. * happen if the SQL query joins many tables with an ID column.
  1046. */
  1047. if (!propertyIds.contains(colName)) {
  1048. propertyIds.add(colName);
  1049. }
  1050. /* Try to determine the column's JDBC class by all means. */
  1051. if (resultExists && rs.getObject(i) != null) {
  1052. type = rs.getObject(i).getClass();
  1053. } else {
  1054. try {
  1055. type = Class.forName(rsmd.getColumnClassName(i));
  1056. } catch (Exception e) {
  1057. getLogger().log(Level.WARNING, "Class not found", e);
  1058. /* On failure revert to Object and hope for the best. */
  1059. type = Object.class;
  1060. }
  1061. }
  1062. /*
  1063. * Determine read only and nullability status of the column. A
  1064. * column is read only if it is reported as either read only or
  1065. * auto increment by the database, and also it is set as the
  1066. * version column in a TableQuery delegate.
  1067. */
  1068. boolean readOnly = rsmd.isAutoIncrement(i)
  1069. || rsmd.isReadOnly(i);
  1070. boolean persistable = !rsmd.isReadOnly(i);
  1071. if (delegate instanceof TableQuery) {
  1072. if (rsmd.getColumnLabel(i).equals(
  1073. ((TableQuery) delegate).getVersionColumn())) {
  1074. readOnly = true;
  1075. }
  1076. }
  1077. propertyReadOnly.put(colName, readOnly);
  1078. propertyPersistable.put(colName, persistable);
  1079. propertyNullable.put(colName,
  1080. rsmd.isNullable(i) == ResultSetMetaData.columnNullable);
  1081. propertyPrimaryKey.put(colName, delegate.getPrimaryKeyColumns()
  1082. .contains(rsmd.getColumnLabel(i)));
  1083. propertyTypes.put(colName, type);
  1084. }
  1085. rs.getStatement().close();
  1086. rs.close();
  1087. delegate.commit();
  1088. getLogger().log(Level.FINER, "Property IDs fetched.");
  1089. } catch (SQLException e) {
  1090. getLogger().log(Level.WARNING,
  1091. "Failed to fetch property ids, rolling back", e);
  1092. try {
  1093. delegate.rollback();
  1094. } catch (SQLException e1) {
  1095. getLogger().log(Level.SEVERE, "Failed to roll back", e1);
  1096. }
  1097. try {
  1098. if (rs != null) {
  1099. if (rs.getStatement() != null) {
  1100. rs.getStatement().close();
  1101. }
  1102. rs.close();
  1103. }
  1104. } catch (SQLException e1) {
  1105. getLogger().log(Level.WARNING, "Failed to close session", e1);
  1106. }
  1107. throw e;
  1108. }
  1109. }
  1110. /**
  1111. * Fetches a page from the data source based on the values of pageLenght and
  1112. * currentOffset. Also updates the set of primary keys, used in
  1113. * identification of RowItems.
  1114. */
  1115. private void getPage() {
  1116. updateCount();
  1117. ResultSet rs = null;
  1118. ResultSetMetaData rsmd = null;
  1119. cachedItems.clear();
  1120. itemIndexes.clear();
  1121. try {
  1122. try {
  1123. delegate.setOrderBy(sorters);
  1124. } catch (UnsupportedOperationException e) {
  1125. /* The query delegate doesn't support sorting. */
  1126. /* No need to do anything. */
  1127. getLogger().log(Level.FINE,
  1128. "The query delegate doesn't support sorting", e);
  1129. }
  1130. delegate.beginTransaction();
  1131. rs = delegate.getResults(currentOffset, pageLength * CACHE_RATIO);
  1132. rsmd = rs.getMetaData();
  1133. List<String> pKeys = delegate.getPrimaryKeyColumns();
  1134. // }
  1135. /* Create new items and column properties */
  1136. ColumnProperty cp = null;
  1137. int rowCount = currentOffset;
  1138. if (!delegate.implementationRespectsPagingLimits()) {
  1139. rowCount = currentOffset = 0;
  1140. setPageLengthInternal(size);
  1141. }
  1142. while (rs.next()) {
  1143. List<ColumnProperty> itemProperties = new ArrayList<ColumnProperty>();
  1144. /* Generate row itemId based on primary key(s) */
  1145. Object[] itemId = new Object[pKeys.size()];
  1146. for (int i = 0; i < pKeys.size(); i++) {
  1147. itemId[i] = rs.getObject(pKeys.get(i));
  1148. }
  1149. RowId id = null;
  1150. if (pKeys.isEmpty()) {
  1151. id = new ReadOnlyRowId(rs.getRow());
  1152. } else {
  1153. id = new RowId(itemId);
  1154. }
  1155. List<String> propertiesToAdd = new ArrayList<String>(
  1156. propertyIds);
  1157. if (!removedItems.containsKey(id)) {
  1158. for (int i = 1; i <= rsmd.getColumnCount(); i++) {
  1159. if (!isColumnIdentifierValid(rsmd.getColumnLabel(i))) {
  1160. continue;
  1161. }
  1162. String colName = rsmd.getColumnLabel(i);
  1163. Object value = rs.getObject(i);
  1164. Class<?> type = value != null ? value.getClass()
  1165. : Object.class;
  1166. if (value == null) {
  1167. for (String propName : propertyTypes.keySet()) {
  1168. if (propName.equals(rsmd.getColumnLabel(i))) {
  1169. type = propertyTypes.get(propName);
  1170. break;
  1171. }
  1172. }
  1173. }
  1174. /*
  1175. * In case there are more than one column with the same
  1176. * name, add only the first one. This can easily happen
  1177. * if you join many tables where each table has an ID
  1178. * column.
  1179. */
  1180. if (propertiesToAdd.contains(colName)) {
  1181. cp = new ColumnProperty(colName,
  1182. propertyReadOnly.get(colName),
  1183. propertyPersistable.get(colName),
  1184. propertyNullable.get(colName),
  1185. propertyPrimaryKey.get(colName), value,
  1186. type);
  1187. itemProperties.add(cp);
  1188. propertiesToAdd.remove(colName);
  1189. }
  1190. }
  1191. /* Cache item */
  1192. itemIndexes.put(rowCount, id);
  1193. // if an item with the id is contained in the modified
  1194. // cache, then use this record and add it to the cached
  1195. // items. Otherwise create a new item
  1196. int modifiedIndex = indexInModifiedCache(id);
  1197. if (modifiedIndex != -1) {
  1198. cachedItems.put(id, modifiedItems.get(modifiedIndex));
  1199. } else {
  1200. cachedItems.put(id, new RowItem(this, id,
  1201. itemProperties));
  1202. }
  1203. rowCount++;
  1204. }
  1205. }
  1206. rs.getStatement().close();
  1207. rs.close();
  1208. delegate.commit();
  1209. getLogger().log(
  1210. Level.FINER,
  1211. "Fetched " + pageLength * CACHE_RATIO
  1212. + " rows starting from " + currentOffset);
  1213. } catch (SQLException e) {
  1214. getLogger().log(Level.WARNING,
  1215. "Failed to fetch rows, rolling back", e);
  1216. try {
  1217. delegate.rollback();
  1218. } catch (SQLException e1) {
  1219. getLogger().log(Level.SEVERE, "Failed to roll back", e1);
  1220. }
  1221. try {
  1222. if (rs != null) {
  1223. if (rs.getStatement() != null) {
  1224. rs.getStatement().close();
  1225. rs.close();
  1226. }
  1227. }
  1228. } catch (SQLException e1) {
  1229. getLogger().log(Level.WARNING, "Failed to close session", e1);
  1230. }
  1231. throw new RuntimeException("Failed to fetch page.", e);
  1232. }
  1233. }
  1234. /**
  1235. * Returns the index of the item with the given itemId for the modified
  1236. * cache.
  1237. *
  1238. * @param itemId
  1239. * @return the index of the item with the itemId in the modified cache. Or
  1240. * -1 if not found.
  1241. */
  1242. private int indexInModifiedCache(Object itemId) {
  1243. for (int ix = 0; ix < modifiedItems.size(); ix++) {
  1244. RowItem item = modifiedItems.get(ix);
  1245. if (item.getId().equals(itemId)) {
  1246. return ix;
  1247. }
  1248. }
  1249. return -1;
  1250. }
  1251. private int sizeOfAddedItems() {
  1252. return getFilteredAddedItems().size();
  1253. }
  1254. private List<RowItem> getFilteredAddedItems() {
  1255. ArrayList<RowItem> filtered = new ArrayList<RowItem>(addedItems);
  1256. if (filters != null && !filters.isEmpty()) {
  1257. for (RowItem item : addedItems) {
  1258. if (!itemPassesFilters(item)) {
  1259. filtered.remove(item);
  1260. }
  1261. }
  1262. }
  1263. return filtered;
  1264. }
  1265. private boolean itemPassesFilters(RowItem item) {
  1266. for (Filter filter : filters) {
  1267. if (!filter.passesFilter(item.getId(), item)) {
  1268. return false;
  1269. }
  1270. }
  1271. return true;
  1272. }
  1273. /**
  1274. * Checks is the given column identifier valid to be used with SQLContainer.
  1275. * Currently the only non-valid identifier is "rownum" when MSSQL or Oracle
  1276. * is used. This is due to the way the SELECT queries are constructed in
  1277. * order to implement paging in these databases.
  1278. *
  1279. * @param identifier
  1280. * Column identifier
  1281. * @return true if the identifier is valid
  1282. */
  1283. private boolean isColumnIdentifierValid(String identifier) {
  1284. if (identifier.equalsIgnoreCase("rownum")
  1285. && delegate instanceof TableQuery) {
  1286. TableQuery tq = (TableQuery) delegate;
  1287. if (tq.getSqlGenerator() instanceof MSSQLGenerator
  1288. || tq.getSqlGenerator() instanceof OracleGenerator) {
  1289. return false;
  1290. }
  1291. }
  1292. return true;
  1293. }
  1294. /**
  1295. * Returns the QueryDelegate set for this SQLContainer.
  1296. *
  1297. * @return current querydelegate
  1298. */
  1299. protected QueryDelegate getQueryDelegate() {
  1300. return delegate;
  1301. }
  1302. /************************************/
  1303. /** UNSUPPORTED CONTAINER FEATURES **/
  1304. /************************************/
  1305. /*
  1306. * (non-Javadoc)
  1307. *
  1308. * @see com.vaadin.data.Container#addContainerProperty(java.lang.Object,
  1309. * java.lang.Class, java.lang.Object)
  1310. */
  1311. @Override
  1312. public boolean addContainerProperty(Object propertyId, Class<?> type,
  1313. Object defaultValue) throws UnsupportedOperationException {
  1314. throw new UnsupportedOperationException();
  1315. }
  1316. /*
  1317. * (non-Javadoc)
  1318. *
  1319. * @see com.vaadin.data.Container#removeContainerProperty(java.lang.Object)
  1320. */
  1321. @Override
  1322. public boolean removeContainerProperty(Object propertyId)
  1323. throws UnsupportedOperationException {
  1324. throw new UnsupportedOperationException();
  1325. }
  1326. /*
  1327. * (non-Javadoc)
  1328. *
  1329. * @see com.vaadin.data.Container#addItem(java.lang.Object)
  1330. */
  1331. @Override
  1332. public Item addItem(Object itemId) throws UnsupportedOperationException {
  1333. throw new UnsupportedOperationException();
  1334. }
  1335. /*
  1336. * (non-Javadoc)
  1337. *
  1338. * @see com.vaadin.data.Container.Ordered#addItemAfter(java.lang.Object,
  1339. * java.lang.Object)
  1340. */
  1341. @Override
  1342. public Item addItemAfter(Object previousItemId, Object newItemId)
  1343. throws UnsupportedOperationException {
  1344. throw new UnsupportedOperationException();
  1345. }
  1346. /*
  1347. * (non-Javadoc)
  1348. *
  1349. * @see com.vaadin.data.Container.Indexed#addItemAt(int, java.lang.Object)
  1350. */
  1351. @Override
  1352. public Item addItemAt(int index, Object newItemId)
  1353. throws UnsupportedOperationException {
  1354. throw new UnsupportedOperationException();
  1355. }
  1356. /*
  1357. * (non-Javadoc)
  1358. *
  1359. * @see com.vaadin.data.Container.Indexed#addItemAt(int)
  1360. */
  1361. @Override
  1362. public Object addItemAt(int index) throws UnsupportedOperationException {
  1363. throw new UnsupportedOperationException();
  1364. }
  1365. /*
  1366. * (non-Javadoc)
  1367. *
  1368. * @see com.vaadin.data.Container.Ordered#addItemAfter(java.lang.Object)
  1369. */
  1370. @Override
  1371. public Object addItemAfter(Object previousItemId)
  1372. throws UnsupportedOperationException {
  1373. throw new UnsupportedOperationException();
  1374. }
  1375. /******************************************/
  1376. /** ITEMSETCHANGENOTIFIER IMPLEMENTATION **/
  1377. /******************************************/
  1378. /*
  1379. * (non-Javadoc)
  1380. *
  1381. * @see
  1382. * com.vaadin.data.Container.ItemSetChangeNotifier#addListener(com.vaadin
  1383. * .data.Container.ItemSetChangeListener)
  1384. */
  1385. @Override
  1386. public void addItemSetChangeListener(
  1387. Container.ItemSetChangeListener listener) {
  1388. if (itemSetChangeListeners == null) {
  1389. itemSetChangeListeners = new LinkedList<Container.ItemSetChangeListener>();
  1390. }
  1391. itemSetChangeListeners.add(listener);
  1392. }
  1393. /**
  1394. * @deprecated As of 7.0, replaced by
  1395. * {@link #addItemSetChangeListener(com.vaadin.data.Container.ItemSetChangeListener)}
  1396. **/
  1397. @Override
  1398. @Deprecated
  1399. public void addListener(Container.ItemSetChangeListener listener) {
  1400. addItemSetChangeListener(listener);
  1401. }
  1402. /*
  1403. * (non-Javadoc)
  1404. *
  1405. * @see
  1406. * com.vaadin.data.Container.ItemSetChangeNotifier#removeListener(com.vaadin
  1407. * .data.Container.ItemSetChangeListener)
  1408. */
  1409. @Override
  1410. public void removeItemSetChangeListener(
  1411. Container.ItemSetChangeListener listener) {
  1412. if (itemSetChangeListeners != null) {
  1413. itemSetChangeListeners.remove(listener);
  1414. }
  1415. }
  1416. /**
  1417. * @deprecated As of 7.0, replaced by
  1418. * {@link #removeItemSetChangeListener(com.vaadin.data.Container.ItemSetChangeListener)}
  1419. **/
  1420. @Override
  1421. @Deprecated
  1422. public void removeListener(Container.ItemSetChangeListener listener) {
  1423. removeItemSetChangeListener(listener);
  1424. }
  1425. protected void fireContentsChange() {
  1426. if (itemSetChangeListeners != null) {
  1427. final Object[] l = itemSetChangeListeners.toArray();
  1428. final Container.ItemSetChangeEvent event = new SQLContainer.ItemSetChangeEvent(
  1429. this);
  1430. for (int i = 0; i < l.length; i++) {
  1431. ((Container.ItemSetChangeListener) l[i])
  1432. .containerItemSetChange(event);
  1433. }
  1434. }
  1435. }
  1436. /**
  1437. * Simple ItemSetChangeEvent implementation.
  1438. */
  1439. @SuppressWarnings("serial")
  1440. public static class ItemSetChangeEvent extends EventObject implements
  1441. Container.ItemSetChangeEvent {
  1442. private ItemSetChangeEvent(SQLContainer source) {
  1443. super(source);
  1444. }
  1445. @Override
  1446. public Container getContainer() {
  1447. return (Container) getSource();
  1448. }
  1449. }
  1450. /**************************************************/
  1451. /** ROWIDCHANGELISTENER PASSING TO QUERYDELEGATE **/
  1452. /**************************************************/
  1453. /**
  1454. * Adds a RowIdChangeListener to the QueryDelegate
  1455. *
  1456. * @param listener
  1457. */
  1458. public void addRowIdChangeListener(RowIdChangeListener listener) {
  1459. if (delegate instanceof QueryDelegate.RowIdChangeNotifier) {
  1460. ((QueryDelegate.RowIdChangeNotifier) delegate)
  1461. .addListener(listener);
  1462. }
  1463. }
  1464. /**
  1465. * @deprecated As of 7.0, replaced by
  1466. * {@link #addRowIdChangeListener(RowIdChangeListener)}
  1467. **/
  1468. @Deprecated
  1469. public void addListener(RowIdChangeListener listener) {
  1470. addRowIdChangeListener(listener);
  1471. }
  1472. /**
  1473. * Removes a RowIdChangeListener from the QueryDelegate
  1474. *
  1475. * @param listener
  1476. */
  1477. public void removeRowIdChangeListener(RowIdChangeListener listener) {
  1478. if (delegate instanceof QueryDelegate.RowIdChangeNotifier) {
  1479. ((QueryDelegate.RowIdChangeNotifier) delegate)
  1480. .removeListener(listener);
  1481. }
  1482. }
  1483. /**
  1484. * @deprecated As of 7.0, replaced by
  1485. * {@link #removeRowIdChangeListener(RowIdChangeListener)}
  1486. **/
  1487. @Deprecated
  1488. public void removeListener(RowIdChangeListener listener) {
  1489. removeRowIdChangeListener(listener);
  1490. }
  1491. /**
  1492. * Calling this will enable this SQLContainer to send and receive cache
  1493. * flush notifications for its lifetime.
  1494. */
  1495. public void enableCacheFlushNotifications() {
  1496. if (!notificationsEnabled) {
  1497. notificationsEnabled = true;
  1498. CacheFlushNotifier.addInstance(this);
  1499. }
  1500. }
  1501. /******************************************/
  1502. /** Referencing mechanism implementation **/
  1503. /******************************************/
  1504. /**
  1505. * Adds a new reference to the given SQLContainer. In addition to the
  1506. * container you must provide the column (property) names used for the
  1507. * reference in both this and the referenced SQLContainer.
  1508. *
  1509. * Note that multiple references pointing to the same SQLContainer are not
  1510. * supported.
  1511. *
  1512. * @param refdCont
  1513. * Target SQLContainer of the new reference
  1514. * @param refingCol
  1515. * Column (property) name in this container storing the (foreign
  1516. * key) reference
  1517. * @param refdCol
  1518. * Column (property) name in the referenced container storing the
  1519. * referenced key
  1520. */
  1521. public void addReference(SQLContainer refdCont, String refingCol,
  1522. String refdCol) {
  1523. if (refdCont == null) {
  1524. throw new IllegalArgumentException(
  1525. "Referenced SQLContainer can not be null.");
  1526. }
  1527. if (!getContainerPropertyIds().contains(refingCol)) {
  1528. throw new IllegalArgumentException(
  1529. "Given referencing column name is invalid."
  1530. + " Please ensure that this container"
  1531. + " contains a property ID named: " + refingCol);
  1532. }
  1533. if (!refdCont.getContainerPropertyIds().contains(refdCol)) {
  1534. throw new IllegalArgumentException(
  1535. "Given referenced column name is invalid."
  1536. + " Please ensure that the referenced container"
  1537. + " contains a property ID named: " + refdCol);
  1538. }
  1539. if (references.keySet().contains(refdCont)) {
  1540. throw new IllegalArgumentException(
  1541. "An SQLContainer instance can only be referenced once.");
  1542. }
  1543. references.put(refdCont, new Reference(refdCont, refingCol, refdCol));
  1544. }
  1545. /**
  1546. * Removes the reference pointing to the given SQLContainer.
  1547. *
  1548. * @param refdCont
  1549. * Target SQLContainer of the reference
  1550. * @return true if successful, false if the reference did not exist
  1551. */
  1552. public boolean removeReference(SQLContainer refdCont) {
  1553. if (refdCont == null) {
  1554. throw new IllegalArgumentException(
  1555. "Referenced SQLContainer can not be null.");
  1556. }
  1557. return references.remove(refdCont) == null ? false : true;
  1558. }
  1559. /**
  1560. * Sets the referenced item. The referencing column of the item in this
  1561. * container is updated accordingly.
  1562. *
  1563. * @param itemId
  1564. * Item Id of the reference source (from this container)
  1565. * @param refdItemId
  1566. * Item Id of the reference target (from referenced container)
  1567. * @param refdCont
  1568. * Target SQLContainer of the reference
  1569. * @return true if the referenced item was successfully set, false on
  1570. * failure
  1571. */
  1572. public boolean setReferencedItem(Object itemId, Object refdItemId,
  1573. SQLContainer refdCont) {
  1574. if (refdCont == null) {
  1575. throw new IllegalArgumentException(
  1576. "Referenced SQLContainer can not be null.");
  1577. }
  1578. Reference r = references.get(refdCont);
  1579. if (r == null) {
  1580. throw new IllegalArgumentException(
  1581. "Reference to the given SQLContainer not defined.");
  1582. }
  1583. try {
  1584. getContainerProperty(itemId, r.getReferencingColumn()).setValue(
  1585. refdCont.getContainerProperty(refdItemId,
  1586. r.getReferencedColumn()));
  1587. return true;
  1588. } catch (Exception e) {
  1589. getLogger()
  1590. .log(Level.WARNING, "Setting referenced item failed.", e);
  1591. return false;
  1592. }
  1593. }
  1594. /**
  1595. * Fetches the Item Id of the referenced item from the target SQLContainer.
  1596. *
  1597. * @param itemId
  1598. * Item Id of the reference source (from this container)
  1599. * @param refdCont
  1600. * Target SQLContainer of the reference
  1601. * @return Item Id of the referenced item, or null if not found
  1602. */
  1603. public Object getReferencedItemId(Object itemId, SQLContainer refdCont) {
  1604. if (refdCont == null) {
  1605. throw new IllegalArgumentException(
  1606. "Referenced SQLContainer can not be null.");
  1607. }
  1608. Reference r = references.get(refdCont);
  1609. if (r == null) {
  1610. throw new IllegalArgumentException(
  1611. "Reference to the given SQLContainer not defined.");
  1612. }
  1613. Object refKey = getContainerProperty(itemId, r.getReferencingColumn())
  1614. .getValue();
  1615. refdCont.removeAllContainerFilters();
  1616. refdCont.addContainerFilter(new Equal(r.getReferencedColumn(), refKey));
  1617. Object toReturn = refdCont.firstItemId();
  1618. refdCont.removeAllContainerFilters();
  1619. return toReturn;
  1620. }
  1621. /**
  1622. * Fetches the referenced item from the target SQLContainer.
  1623. *
  1624. * @param itemId
  1625. * Item Id of the reference source (from this container)
  1626. * @param refdCont
  1627. * Target SQLContainer of the reference
  1628. * @return The referenced item, or null if not found
  1629. */
  1630. public Item getReferencedItem(Object itemId, SQLContainer refdCont) {
  1631. return refdCont.getItem(getReferencedItemId(itemId, refdCont));
  1632. }
  1633. private void writeObject(java.io.ObjectOutputStream out) throws IOException {
  1634. out.defaultWriteObject();
  1635. }
  1636. private void readObject(java.io.ObjectInputStream in) throws IOException,
  1637. ClassNotFoundException {
  1638. in.defaultReadObject();
  1639. if (notificationsEnabled) {
  1640. /*
  1641. * Register instance with CacheFlushNotifier after de-serialization
  1642. * if notifications are enabled
  1643. */
  1644. CacheFlushNotifier.addInstance(this);
  1645. }
  1646. }
  1647. private static final Logger getLogger() {
  1648. return Logger.getLogger(SQLContainer.class.getName());
  1649. }
  1650. }