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

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