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