com.vaadin.v7.data.util.sqlcontainer.SQLContainer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of vaadin-compatibility-server Show documentation
Show all versions of vaadin-compatibility-server Show documentation
Vaadin 7 compatibility package for Vaadin 8
The newest version!
/*
* Copyright (C) 2000-2024 Vaadin Ltd
*
* This program is available under Vaadin Commercial License and Service Terms.
*
* See for the full
* license.
*/
package com.vaadin.v7.data.util.sqlcontainer;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.Date;
import java.util.EventObject;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.vaadin.data.provider.DataProvider;
import com.vaadin.v7.data.Container;
import com.vaadin.v7.data.ContainerHelpers;
import com.vaadin.v7.data.Item;
import com.vaadin.v7.data.Property;
import com.vaadin.v7.data.util.filter.Compare.Equal;
import com.vaadin.v7.data.util.filter.Like;
import com.vaadin.v7.data.util.filter.UnsupportedFilterException;
import com.vaadin.v7.data.util.sqlcontainer.query.OrderBy;
import com.vaadin.v7.data.util.sqlcontainer.query.QueryDelegate;
import com.vaadin.v7.data.util.sqlcontainer.query.QueryDelegate.RowIdChangeListener;
import com.vaadin.v7.data.util.sqlcontainer.query.TableQuery;
import com.vaadin.v7.data.util.sqlcontainer.query.generator.MSSQLGenerator;
import com.vaadin.v7.data.util.sqlcontainer.query.generator.OracleGenerator;
/**
* @deprecated As of 8.0, no replacement available, see {@link DataProvider}.
*/
@Deprecated
public class SQLContainer implements Container.Filterable,
Container.Indexed, Container.Sortable, Container.ItemSetChangeNotifier {
/** Query delegate */
private QueryDelegate queryDelegate;
/** Auto commit mode, default = false */
private boolean autoCommit = false;
/** Page length = number of items contained in one page */
private int pageLength = DEFAULT_PAGE_LENGTH;
public static final int DEFAULT_PAGE_LENGTH = 100;
/** Number of items to cache = CACHE_RATIO x pageLength. */
public static final int CACHE_RATIO = 2;
/** Amount of cache to overlap with previous page */
private int cacheOverlap = pageLength;
/** Item and index caches */
private final Map itemIndexes = new HashMap();
private final CacheMap cachedItems = new CacheMap();
/** Container properties = column names, data types and statuses */
private final List propertyIds = new ArrayList();
private final Map> propertyTypes = new HashMap>();
private final Map propertyReadOnly = new HashMap();
private final Map propertyPersistable = new HashMap();
private final Map propertyNullable = new HashMap();
private final Map propertyPrimaryKey = new HashMap();
/** Filters (WHERE) and sorters (ORDER BY) */
private final List filters = new ArrayList();
private final List sorters = new ArrayList();
/**
* Total number of items available in the data source using the current
* query, filters and sorters.
*/
private int size;
/**
* Size updating logic. Do not update size from data source if it has been
* updated in the last sizeValidMilliSeconds milliseconds.
*/
private final int sizeValidMilliSeconds = 10000;
private boolean sizeDirty = true;
private Date sizeUpdated = new Date();
/** Starting row number of the currently fetched page */
private int currentOffset;
/** ItemSetChangeListeners */
private LinkedList itemSetChangeListeners;
/**
* Temporary storage for modified items and items to be removed and added
*/
private final Map removedItems = new HashMap();
private final List addedItems = new ArrayList();
private final List modifiedItems = new ArrayList();
/** List of references to other SQLContainers */
private final Map references = new HashMap();
/** Cache flush notification system enabled. Disabled by default. */
private boolean notificationsEnabled;
/**
* Prevent instantiation without a QueryDelegate.
*/
@SuppressWarnings("unused")
private SQLContainer() {
}
/**
* Creates and initializes SQLContainer using the given QueryDelegate.
*
* @param delegate
* QueryDelegate implementation
* @throws SQLException
*/
public SQLContainer(QueryDelegate delegate) throws SQLException {
if (delegate == null) {
throw new IllegalArgumentException(
"QueryDelegate must not be null.");
}
queryDelegate = delegate;
getPropertyIds();
cachedItems.setCacheLimit(CACHE_RATIO * getPageLength() + cacheOverlap);
}
/**************************************/
/** Methods from interface Container **/
/**************************************/
/**
* Note! If auto commit mode is enabled, this method will still return the
* temporary row ID assigned for the item. Implement
* QueryDelegate.RowIdChangeListener to receive the actual Row ID value
* after the addition has been committed.
*
* {@inheritDoc}
*/
@Override
public Object addItem() throws UnsupportedOperationException {
Object[] emptyKey = new Object[queryDelegate.getPrimaryKeyColumns()
.size()];
RowId itemId = new TemporaryRowId(emptyKey);
// Create new empty column properties for the row item.
List itemProperties = new ArrayList();
for (String propertyId : propertyIds) {
/* Default settings for new item properties. */
ColumnProperty cp = new ColumnProperty(propertyId,
propertyReadOnly.get(propertyId),
propertyPersistable.get(propertyId),
propertyNullable.get(propertyId),
propertyPrimaryKey.get(propertyId), null,
getType(propertyId));
itemProperties.add(cp);
}
RowItem newRowItem = new RowItem(this, itemId, itemProperties);
if (autoCommit) {
/* Add and commit instantly */
try {
if (queryDelegate instanceof TableQuery) {
itemId = ((TableQuery) queryDelegate)
.storeRowImmediately(newRowItem);
} else {
queryDelegate.beginTransaction();
queryDelegate.storeRow(newRowItem);
queryDelegate.commit();
}
refresh();
if (notificationsEnabled) {
CacheFlushNotifier.notifyOfCacheFlush(this);
}
getLogger().log(Level.FINER, "Row added to DB...");
return itemId;
} catch (SQLException e) {
getLogger().log(Level.WARNING,
"Failed to add row to DB. Rolling back.", e);
try {
queryDelegate.rollback();
} catch (SQLException ee) {
getLogger().log(Level.SEVERE,
"Failed to roll back row addition", e);
}
return null;
}
} else {
addedItems.add(newRowItem);
fireContentsChange();
return itemId;
}
}
@Override
public boolean containsId(Object itemId) {
if (itemId == null) {
return false;
}
if (cachedItems.containsKey(itemId)) {
return true;
} else {
for (RowItem item : addedItems) {
if (item.getId().equals(itemId)) {
return itemPassesFilters(item);
}
}
}
if (removedItems.containsKey(itemId)) {
return false;
}
if (itemId instanceof ReadOnlyRowId) {
int rowNum = ((ReadOnlyRowId) itemId).getRowNum();
return rowNum >= 0 && rowNum < size;
}
if (itemId instanceof RowId && !(itemId instanceof TemporaryRowId)) {
try {
return queryDelegate
.containsRowWithKey(((RowId) itemId).getId());
} catch (Exception e) {
/* Query failed, just return false. */
getLogger().log(Level.WARNING, "containsId query failed", e);
}
}
return false;
}
@Override
public Property getContainerProperty(Object itemId, Object propertyId) {
Item item = getItem(itemId);
if (item == null) {
return null;
}
return item.getItemProperty(propertyId);
}
@Override
public Collection> getContainerPropertyIds() {
return Collections.unmodifiableCollection(propertyIds);
}
@Override
public Item getItem(Object itemId) {
if (!cachedItems.containsKey(itemId)) {
int index = indexOfId(itemId);
if (index >= size) {
// The index is in the added items
int offset = index - size;
RowItem item = addedItems.get(offset);
if (itemPassesFilters(item)) {
return item;
} else {
return null;
}
} else {
// load the item into cache
updateOffsetAndCache(index);
}
}
return cachedItems.get(itemId);
}
/**
* Bypasses in-memory filtering to return items that are cached in memory.
* NOTE: This does not bypass database-level filtering.
*
* @param itemId
* the id of the item to retrieve.
* @return the item represented by itemId.
*/
public Item getItemUnfiltered(Object itemId) {
if (!cachedItems.containsKey(itemId)) {
for (RowItem item : addedItems) {
if (item.getId().equals(itemId)) {
return item;
}
}
}
return cachedItems.get(itemId);
}
/**
* NOTE! Do not use this method if in any way avoidable. This method doesn't
* (and cannot) use lazy loading, which means that all rows in the database
* will be loaded into memory.
*
* {@inheritDoc}
*/
@Override
public Collection> getItemIds() {
updateCount();
List ids = new ArrayList();
ResultSet rs = null;
try {
// Load ALL rows :(
queryDelegate.beginTransaction();
rs = queryDelegate.getResults(0, 0);
List pKeys = queryDelegate.getPrimaryKeyColumns();
while (rs.next()) {
RowId id = null;
if (pKeys.isEmpty()) {
/* Create a read only itemId */
id = new ReadOnlyRowId(rs.getRow());
} else {
/* Generate itemId for the row based on primary key(s) */
Object[] itemId = new Object[pKeys.size()];
for (int i = 0; i < pKeys.size(); i++) {
itemId[i] = rs.getObject(pKeys.get(i));
}
id = new RowId(itemId);
}
if (id != null && !removedItems.containsKey(id)) {
ids.add(id);
}
}
rs.getStatement().close();
rs.close();
queryDelegate.commit();
} catch (SQLException e) {
getLogger().log(Level.WARNING, "getItemIds() failed, rolling back.",
e);
try {
queryDelegate.rollback();
} catch (SQLException e1) {
getLogger().log(Level.SEVERE, "Failed to roll back state", e1);
}
try {
rs.getStatement().close();
rs.close();
} catch (SQLException e1) {
getLogger().log(Level.WARNING, "Closing session failed", e1);
}
throw new RuntimeException("Failed to fetch item indexes.", e);
}
for (RowItem item : getFilteredAddedItems()) {
ids.add(item.getId());
}
return Collections.unmodifiableCollection(ids);
}
@Override
public Class> getType(Object propertyId) {
if (!propertyIds.contains(propertyId)) {
return null;
}
return propertyTypes.get(propertyId);
}
@Override
public int size() {
updateCount();
return size + sizeOfAddedItems() - removedItems.size();
}
@Override
public boolean removeItem(Object itemId)
throws UnsupportedOperationException {
if (!containsId(itemId)) {
return false;
}
for (RowItem item : addedItems) {
if (item.getId().equals(itemId)) {
addedItems.remove(item);
fireContentsChange();
return true;
}
}
if (autoCommit) {
/* Remove and commit instantly. */
Item i = getItem(itemId);
if (i == null) {
return false;
}
try {
queryDelegate.beginTransaction();
boolean success = queryDelegate.removeRow((RowItem) i);
queryDelegate.commit();
refresh();
if (notificationsEnabled) {
CacheFlushNotifier.notifyOfCacheFlush(this);
}
if (success) {
getLogger().log(Level.FINER, "Row removed from DB...");
}
return success;
} catch (SQLException e) {
getLogger().log(Level.WARNING,
"Failed to remove row, rolling back", e);
try {
queryDelegate.rollback();
} catch (SQLException ee) {
/* Nothing can be done here */
getLogger().log(Level.SEVERE,
"Failed to rollback row removal", ee);
}
return false;
} catch (OptimisticLockException e) {
getLogger().log(Level.WARNING,
"Failed to remove row, rolling back", e);
try {
queryDelegate.rollback();
} catch (SQLException ee) {
/* Nothing can be done here */
getLogger().log(Level.SEVERE,
"Failed to rollback row removal", ee);
}
throw e;
}
} else {
removedItems.put((RowId) itemId, (RowItem) getItem(itemId));
cachedItems.remove(itemId);
refresh();
return true;
}
}
@Override
public boolean removeAllItems() throws UnsupportedOperationException {
if (autoCommit) {
/* Remove and commit instantly. */
try {
queryDelegate.beginTransaction();
boolean success = true;
for (Object id : getItemIds()) {
if (!queryDelegate.removeRow((RowItem) getItem(id))) {
success = false;
}
}
if (success) {
queryDelegate.commit();
getLogger().log(Level.FINER, "All rows removed from DB...");
refresh();
if (notificationsEnabled) {
CacheFlushNotifier.notifyOfCacheFlush(this);
}
} else {
queryDelegate.rollback();
}
return success;
} catch (SQLException e) {
getLogger().log(Level.WARNING,
"removeAllItems() failed, rolling back", e);
try {
queryDelegate.rollback();
} catch (SQLException ee) {
/* Nothing can be done here */
getLogger().log(Level.SEVERE, "Failed to roll back", ee);
}
return false;
} catch (OptimisticLockException e) {
getLogger().log(Level.WARNING,
"removeAllItems() failed, rolling back", e);
try {
queryDelegate.rollback();
} catch (SQLException ee) {
/* Nothing can be done here */
getLogger().log(Level.SEVERE, "Failed to roll back", ee);
}
throw e;
}
} else {
for (Object id : getItemIds()) {
removedItems.put((RowId) id, (RowItem) getItem(id));
cachedItems.remove(id);
}
refresh();
return true;
}
}
/*************************************************/
/** Methods from interface Container.Filterable **/
/*************************************************/
/**
* {@inheritDoc}
*/
@Override
public void addContainerFilter(Filter filter)
throws UnsupportedFilterException {
// filter.setCaseSensitive(!ignoreCase);
filters.add(filter);
refresh();
}
/**
* {@inheritDoc}
*/
@Override
public void removeContainerFilter(Filter filter) {
filters.remove(filter);
refresh();
}
/**
* {@inheritDoc}
*/
public void addContainerFilter(Object propertyId, String filterString,
boolean ignoreCase, boolean onlyMatchPrefix) {
if (propertyId == null || !propertyIds.contains(propertyId)) {
return;
}
/* Generate Filter -object */
String likeStr = onlyMatchPrefix ? filterString + "%"
: "%" + filterString + "%";
Like like = new Like(propertyId.toString(), likeStr);
like.setCaseSensitive(!ignoreCase);
filters.add(like);
refresh();
}
/**
* {@inheritDoc}
*/
public void removeContainerFilters(Object propertyId) {
List toRemove = new ArrayList();
for (Filter f : filters) {
if (f.appliesToProperty(propertyId)) {
toRemove.add(f);
}
}
filters.removeAll(toRemove);
refresh();
}
/**
* {@inheritDoc}
*/
@Override
public void removeAllContainerFilters() {
filters.clear();
refresh();
}
/**
* Returns true if any filters have been applied to the container.
*
* @return true if the container has filters applied, false otherwise
* @since 7.1
*/
public boolean hasContainerFilters() {
return !getContainerFilters().isEmpty();
}
@Override
public Collection getContainerFilters() {
return Collections.unmodifiableCollection(filters);
}
/**********************************************/
/** Methods from interface Container.Indexed **/
/**********************************************/
@Override
public int indexOfId(Object itemId) {
// First check if the id is in the added items
for (int ix = 0; ix < addedItems.size(); ix++) {
RowItem item = addedItems.get(ix);
if (item.getId().equals(itemId)) {
if (itemPassesFilters(item)) {
updateCount();
return size + ix;
} else {
return -1;
}
}
}
if (!containsId(itemId)) {
return -1;
}
if (cachedItems.isEmpty()) {
getPage();
}
// this protects against infinite looping
int counter = 0;
int oldIndex;
while (counter < size) {
if (itemIndexes.containsValue(itemId)) {
for (Integer idx : itemIndexes.keySet()) {
if (itemIndexes.get(idx).equals(itemId)) {
return idx;
}
}
}
oldIndex = currentOffset;
// load in the next page.
int nextIndex = currentOffset + pageLength * CACHE_RATIO
+ cacheOverlap;
if (nextIndex >= size) {
// Container wrapped around, start from index 0.
nextIndex = 0;
}
updateOffsetAndCache(nextIndex);
// Update counter
if (currentOffset > oldIndex) {
counter += currentOffset - oldIndex;
} else {
counter += size - oldIndex;
}
}
// safeguard in case item not found
return -1;
}
@Override
public Object getIdByIndex(int index) {
if (index < 0) {
throw new IndexOutOfBoundsException(
"Index is negative! index=" + index);
}
// make sure the size field is valid
updateCount();
if (index < size) {
if (itemIndexes.keySet().contains(index)) {
return itemIndexes.get(index);
}
updateOffsetAndCache(index);
return itemIndexes.get(index);
} else {
// The index is in the added items
int offset = index - size;
// TODO this is very inefficient if looping - should improve
// getItemIds(int, int)
return getFilteredAddedItems().get(offset).getId();
}
}
@Override
public List