Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

SQLContainer.java 59KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708
  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, 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. ArrayList<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. ArrayList<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. ArrayList<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 Object[] l = itemSetChangeListeners.toArray();
  1349. final Container.ItemSetChangeEvent event = new SQLContainer.ItemSetChangeEvent(
  1350. this);
  1351. for (int i = 0; i < l.length; i++) {
  1352. ((Container.ItemSetChangeListener) l[i])
  1353. .containerItemSetChange(event);
  1354. }
  1355. }
  1356. }
  1357. /**
  1358. * Simple ItemSetChangeEvent implementation.
  1359. */
  1360. @SuppressWarnings("serial")
  1361. @Deprecated
  1362. public static class ItemSetChangeEvent extends EventObject
  1363. implements Container.ItemSetChangeEvent {
  1364. private ItemSetChangeEvent(SQLContainer source) {
  1365. super(source);
  1366. }
  1367. @Override
  1368. public Container getContainer() {
  1369. return (Container) getSource();
  1370. }
  1371. }
  1372. /**************************************************/
  1373. /** ROWIDCHANGELISTENER PASSING TO QUERYDELEGATE **/
  1374. /**************************************************/
  1375. /**
  1376. * Adds a RowIdChangeListener to the QueryDelegate
  1377. *
  1378. * @param listener
  1379. */
  1380. public void addRowIdChangeListener(RowIdChangeListener listener) {
  1381. if (queryDelegate instanceof QueryDelegate.RowIdChangeNotifier) {
  1382. ((QueryDelegate.RowIdChangeNotifier) queryDelegate)
  1383. .addListener(listener);
  1384. }
  1385. }
  1386. /**
  1387. * @deprecated As of 7.0, replaced by
  1388. * {@link #addRowIdChangeListener(RowIdChangeListener)}
  1389. **/
  1390. @Deprecated
  1391. public void addListener(RowIdChangeListener listener) {
  1392. addRowIdChangeListener(listener);
  1393. }
  1394. /**
  1395. * Removes a RowIdChangeListener from the QueryDelegate
  1396. *
  1397. * @param listener
  1398. */
  1399. public void removeRowIdChangeListener(RowIdChangeListener listener) {
  1400. if (queryDelegate instanceof QueryDelegate.RowIdChangeNotifier) {
  1401. ((QueryDelegate.RowIdChangeNotifier) queryDelegate)
  1402. .removeListener(listener);
  1403. }
  1404. }
  1405. /**
  1406. * @deprecated As of 7.0, replaced by
  1407. * {@link #removeRowIdChangeListener(RowIdChangeListener)}
  1408. **/
  1409. @Deprecated
  1410. public void removeListener(RowIdChangeListener listener) {
  1411. removeRowIdChangeListener(listener);
  1412. }
  1413. /**
  1414. * Calling this will enable this SQLContainer to send and receive cache
  1415. * flush notifications for its lifetime.
  1416. */
  1417. public void enableCacheFlushNotifications() {
  1418. if (!notificationsEnabled) {
  1419. notificationsEnabled = true;
  1420. CacheFlushNotifier.addInstance(this);
  1421. }
  1422. }
  1423. /******************************************/
  1424. /** Referencing mechanism implementation **/
  1425. /******************************************/
  1426. /**
  1427. * Adds a new reference to the given SQLContainer. In addition to the
  1428. * container you must provide the column (property) names used for the
  1429. * reference in both this and the referenced SQLContainer.
  1430. *
  1431. * Note that multiple references pointing to the same SQLContainer are not
  1432. * supported.
  1433. *
  1434. * @param refdCont
  1435. * Target SQLContainer of the new reference
  1436. * @param refingCol
  1437. * Column (property) name in this container storing the (foreign
  1438. * key) reference
  1439. * @param refdCol
  1440. * Column (property) name in the referenced container storing the
  1441. * referenced key
  1442. */
  1443. public void addReference(SQLContainer refdCont, String refingCol,
  1444. String refdCol) {
  1445. if (refdCont == null) {
  1446. throw new IllegalArgumentException(
  1447. "Referenced SQLContainer can not be null.");
  1448. }
  1449. if (!getContainerPropertyIds().contains(refingCol)) {
  1450. throw new IllegalArgumentException(
  1451. "Given referencing column name is invalid."
  1452. + " Please ensure that this container"
  1453. + " contains a property ID named: " + refingCol);
  1454. }
  1455. if (!refdCont.getContainerPropertyIds().contains(refdCol)) {
  1456. throw new IllegalArgumentException(
  1457. "Given referenced column name is invalid."
  1458. + " Please ensure that the referenced container"
  1459. + " contains a property ID named: " + refdCol);
  1460. }
  1461. if (references.keySet().contains(refdCont)) {
  1462. throw new IllegalArgumentException(
  1463. "An SQLContainer instance can only be referenced once.");
  1464. }
  1465. references.put(refdCont, new Reference(refdCont, refingCol, refdCol));
  1466. }
  1467. /**
  1468. * Removes the reference pointing to the given SQLContainer.
  1469. *
  1470. * @param refdCont
  1471. * Target SQLContainer of the reference
  1472. * @return true if successful, false if the reference did not exist
  1473. */
  1474. public boolean removeReference(SQLContainer refdCont) {
  1475. if (refdCont == null) {
  1476. throw new IllegalArgumentException(
  1477. "Referenced SQLContainer can not be null.");
  1478. }
  1479. return references.remove(refdCont) == null ? false : true;
  1480. }
  1481. /**
  1482. * Sets the referenced item. The referencing column of the item in this
  1483. * container is updated accordingly.
  1484. *
  1485. * @param itemId
  1486. * Item Id of the reference source (from this container)
  1487. * @param refdItemId
  1488. * Item Id of the reference target (from referenced container)
  1489. * @param refdCont
  1490. * Target SQLContainer of the reference
  1491. * @return true if the referenced item was successfully set, false on
  1492. * failure
  1493. */
  1494. public boolean setReferencedItem(Object itemId, Object refdItemId,
  1495. SQLContainer refdCont) {
  1496. if (refdCont == null) {
  1497. throw new IllegalArgumentException(
  1498. "Referenced SQLContainer can not be null.");
  1499. }
  1500. Reference r = references.get(refdCont);
  1501. if (r == null) {
  1502. throw new IllegalArgumentException(
  1503. "Reference to the given SQLContainer not defined.");
  1504. }
  1505. try {
  1506. getContainerProperty(itemId, r.getReferencingColumn())
  1507. .setValue(refdCont.getContainerProperty(refdItemId,
  1508. r.getReferencedColumn()));
  1509. return true;
  1510. } catch (Exception e) {
  1511. getLogger().log(Level.WARNING, "Setting referenced item failed.",
  1512. e);
  1513. return false;
  1514. }
  1515. }
  1516. /**
  1517. * Fetches the Item Id of the referenced item from the target SQLContainer.
  1518. *
  1519. * @param itemId
  1520. * Item Id of the reference source (from this container)
  1521. * @param refdCont
  1522. * Target SQLContainer of the reference
  1523. * @return Item Id of the referenced item, or null if not found
  1524. */
  1525. public Object getReferencedItemId(Object itemId, SQLContainer refdCont) {
  1526. if (refdCont == null) {
  1527. throw new IllegalArgumentException(
  1528. "Referenced SQLContainer can not be null.");
  1529. }
  1530. Reference r = references.get(refdCont);
  1531. if (r == null) {
  1532. throw new IllegalArgumentException(
  1533. "Reference to the given SQLContainer not defined.");
  1534. }
  1535. Object refKey = getContainerProperty(itemId, r.getReferencingColumn())
  1536. .getValue();
  1537. refdCont.removeAllContainerFilters();
  1538. refdCont.addContainerFilter(new Equal(r.getReferencedColumn(), refKey));
  1539. Object toReturn = refdCont.firstItemId();
  1540. refdCont.removeAllContainerFilters();
  1541. return toReturn;
  1542. }
  1543. /**
  1544. * Fetches the referenced item from the target SQLContainer.
  1545. *
  1546. * @param itemId
  1547. * Item Id of the reference source (from this container)
  1548. * @param refdCont
  1549. * Target SQLContainer of the reference
  1550. * @return The referenced item, or null if not found
  1551. */
  1552. public Item getReferencedItem(Object itemId, SQLContainer refdCont) {
  1553. return refdCont.getItem(getReferencedItemId(itemId, refdCont));
  1554. }
  1555. private void writeObject(ObjectOutputStream out)
  1556. throws IOException {
  1557. out.defaultWriteObject();
  1558. }
  1559. private void readObject(ObjectInputStream in)
  1560. throws IOException, ClassNotFoundException {
  1561. in.defaultReadObject();
  1562. if (notificationsEnabled) {
  1563. /*
  1564. * Register instance with CacheFlushNotifier after de-serialization
  1565. * if notifications are enabled
  1566. */
  1567. CacheFlushNotifier.addInstance(this);
  1568. }
  1569. }
  1570. private static final Logger getLogger() {
  1571. return Logger.getLogger(SQLContainer.class.getName());
  1572. }
  1573. }