/**
* Copyright 2009-2013 Oy Vaadin Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.vaadin.addon.jpacontainer;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Logger;
import com.vaadin.addon.jpacontainer.EntityProviderChangeEvent.EntityPropertyUpdatedEvent;
import com.vaadin.addon.jpacontainer.filter.util.AdvancedFilterableSupport;
import com.vaadin.addon.jpacontainer.metadata.EntityClassMetadata;
import com.vaadin.addon.jpacontainer.metadata.MetadataFactory;
import com.vaadin.addon.jpacontainer.metadata.PersistentPropertyMetadata;
import com.vaadin.addon.jpacontainer.metadata.PropertyKind;
import com.vaadin.addon.jpacontainer.util.CollectionUtil;
import com.vaadin.v7.data.Container;
import com.vaadin.v7.data.Item;
import com.vaadin.v7.data.Property;
import com.vaadin.v7.data.Validator.InvalidValueException;
import com.vaadin.v7.data.util.filter.And;
import com.vaadin.v7.data.util.filter.Compare.Equal;
import com.vaadin.v7.data.util.filter.IsNull;
import com.vaadin.v7.data.util.filter.SimpleStringFilter;
import com.vaadin.v7.data.util.filter.UnsupportedFilterException;
/**
* This is the main container class of JPAContainer (and the default
* implementation of {@link EntityContainer}). You can use it in your
* applications like so: <code><pre>
* EntityContainer<MyEntity> container = new JPAContainer<MyEntity>(
* MyEntity.class);
* container.setEntityProvider(myEntityProvider);
* ...
* myTable.setContainerDataSource(container);
* </pre></code> In the example code, <code>myEntityProvider</code> is an
* instance of an {@link EntityProvider} that, like the name suggests, is
* responsible for providing the entities to the container. If the container
* should be writable, the entity provider must implement the
* {@link MutableEntityProvider} interface and if buffering is desired (i.e.
* write-through turned off) the {@link BatchableEntityProvider} interface as
* well. There are ready-made implementations of all these interfaces that can
* be used out-of-the-box (check the See Also section of this Javadoc).
* <p>
* All sorting and filtering is handled by the entity provider, which in turn
* normally delegates it to the database. Therefore, only persistent properties
* can be filtered and/or sorted by.
* <p>
* It is possible to use JPAContainer as a hierarchical container if the
* entities in the container can be related to each other by means of a parent
* property. For example:
*
* <pre>
* <code>
* @Entity
* public class Node {
* ...
* @ManyToOne
* private Node parent;
* ...
* }
* </code>
* </pre>
*
* Note, however, that the implementation of {@link HierarchicalEntityContainer}
* is still experimental and has some limitations. For example, the data is
* always read directly from the entity provider regardless of whether buffering
* is used or not. Therefore, this feature should be used with care in
* production systems.
*
* <h2>Buffering</h2>
* Here follows some notes on how buffering has been implemented in this class.
* If you are not going to use buffering, you can skip this section.
* <p>
* When using buffered mode, the following rules apply:
* <ul>
* <li>All operations that add, update or remove entities are recorded
* internally.</li>
* <li>If an item is added and then removed later within the same transaction,
* the add operation will be removed and no update operation will be recorded.</li>
* <li>If an item is added and then updated later within the same transaction,
* only the add operation will be recorded.</li>
* <li>If an item is updated and then removed later within the same transaction,
* only the remove operation will be recorded.</li>
* <li>In the case of an update or edit all changes are applied at once.</li>
* <li>When the changes are committed, all recorded operations are carried out
* on the {@link BatchableEntityProvider} in the same order that they were
* recorded.</li>
* </ul>
* <p>
* Please note, that if an entity is modified twice or more, updates are
* "merged". This is something that implementations of
* {@link BatchableEntityProvider} need to be aware of.
* <p>
* Also note, that it is possible to use buffered mode even if the entities
* returned from the entity provider are not explicitly detached (see
* {@link EntityProvider#isEntitiesDetached() }), but this should be avoided
* unless you know what you are doing.
*
* @see com.vaadin.addon.jpacontainer.provider.LocalEntityProvider
* @see com.vaadin.addon.jpacontainer.provider.CachingLocalEntityProvider
* @see com.vaadin.addon.jpacontainer.provider.BatchableLocalEntityProvider
* @author Petter Holmström (Vaadin Ltd)
* @since 1.0
*/
public class JPAContainer<T> implements EntityContainer<T>,
EntityProviderChangeListener<T>, HierarchicalEntityContainer<T>,
Container.Indexed {
private static final long serialVersionUID = -4031940552175752858L;
/**
* Rate on which cache cleanup (of empty weak references to instantiated
* entities) is performed.
*/
private static final int CLEANUPRATE = 200;
private EntityProvider<T> entityProvider;
private AdvancedFilterableSupport filterSupport;
private LinkedList<ItemSetChangeListener> listeners;
private EntityClassMetadata<T> entityClassMetadata;
private List<SortBy> sortByList;
private PropertyList<T> propertyList;
private BufferedContainerDelegate<T> bufferingDelegate;
private boolean readOnly = false;
private boolean writeThrough = false;
transient private HashMap<Object, LinkedList<WeakReference<JPAContainerItem<T>>>> itemRegistry;
private QueryModifierDelegate queryModifierDelegate;
/**
* Creates a new <code>JPAContainer</code> instance for entities of class
* <code>entityClass</code>. An entity provider must be provided using the
* {@link #setEntityProvider(com.vaadin.addon.jpacontainer.EntityProvider) }
* before the container can be used.
*
* @param entityClass
* the class of the entities that will reside in this container
* (must not be null).
*/
public JPAContainer(Class<T> entityClass) {
assert entityClass != null : "entityClass must not be null";
this.entityClassMetadata = MetadataFactory.getInstance()
.getEntityClassMetadata(entityClass);
this.propertyList = new PropertyList<T>(entityClassMetadata);
this.filterSupport = new AdvancedFilterableSupport();
this.bufferingDelegate = new BufferedContainerDelegate<T>(this);
/*
* Add a listener to filterSupport, so that we can notify all clients
* that use our container that the data has been filtered.
*/
this.filterSupport
.addListener(new AdvancedFilterableSupport.ApplyFiltersListener() {
private static final long serialVersionUID = -23196201919497112L;
public void filtersApplied(AdvancedFilterableSupport sender) {
fireContainerItemSetChange(new FiltersAppliedEvent<JPAContainer<T>>(
JPAContainer.this));
}
});
updateFilterablePropertyIds();
}
private Collection<String> additionalFilterablePropertyIds;
/**
* Sometimes, it may be necessary to filter by properties that do not show
* up in the container. This method can be used to add additional property
* IDs to the {@link #getFilterablePropertyIds() } collection. This method
* performs no propertyId validation, so it is up to the client to make sure
* the propertyIds are valid.
*
* @param propertyIds
* an array of additional propertyIds, may be null.
*/
public void setAdditionalFilterablePropertyIds(String... propertyIds) {
if (propertyIds == null || propertyIds.length == 0) {
additionalFilterablePropertyIds = null;
} else {
additionalFilterablePropertyIds = Arrays.asList(propertyIds);
}
updateFilterablePropertyIds();
}
protected void updateFilterablePropertyIds() {
// this.filterSupport
// .setFilterablePropertyIds((Collection<?>) propertyList
// .getPersistentPropertyNames());
HashSet<String> properties = new HashSet<String>();
properties.addAll(propertyList.getPersistentPropertyNames());
if (additionalFilterablePropertyIds != null) {
properties.addAll(additionalFilterablePropertyIds);
}
this.filterSupport.setFilterablePropertyIds(properties);
}
/**
* Gets the mapping metadata of the entity class.
*
* @see EntityClassMetadata
*
* @return the metadata (never null).
*/
protected EntityClassMetadata<T> getEntityClassMetadata() {
return entityClassMetadata;
}
public void addListener(ItemSetChangeListener listener) {
if (listener == null) {
return;
}
if (listeners == null) {
listeners = new LinkedList<ItemSetChangeListener>();
}
listeners.add(listener);
}
public void removeListener(ItemSetChangeListener listener) {
if (listener != null && listeners != null) {
listeners.remove(listener);
}
}
/**
* Publishes <code>event</code> to all registered
* <code>ItemSetChangeListener</code>s.
*
* @param event
* the event to publish (must not be null).
*/
@SuppressWarnings("unchecked")
protected void fireContainerItemSetChange(final ItemSetChangeEvent event) {
assert event != null : "event must not be null";
if (listeners == null || !fireContainerItemSetChangeEvents) {
return;
}
LinkedList<ItemSetChangeListener> list = (LinkedList<ItemSetChangeListener>) listeners
.clone();
for (ItemSetChangeListener l : list) {
l.containerItemSetChange(event);
}
}
private boolean fireContainerItemSetChangeEvents = true;
/**
* Specifies whether the container should fire an item set change event when
* it detects a change in the entity provider (such as an entity being
* added, updated or deleted).
*/
public void setFireContainerItemSetChangeEvents(boolean value) {
this.fireContainerItemSetChangeEvents = value;
}
/**
* Tests whether the container should fire an item set change event when it
* detects a change in the entity provider (such as an entity being added,
* updated or deleted).
*
* @return true if item set change events should be fired (default), false
* otherwise.
*/
public boolean isFireContainerItemSetChangeEvents() {
return fireContainerItemSetChangeEvents;
}
public void addNestedContainerProperty(String nestedProperty)
throws UnsupportedOperationException {
propertyList.addNestedProperty(nestedProperty);
updateFilterablePropertyIds();
}
public Class<T> getEntityClass() {
return getEntityClassMetadata().getMappedClass();
}
public EntityProvider<T> getEntityProvider() {
return entityProvider;
}
/**
* Checks that the entity provider is not null and returns it.
*
* @return the entity provider (never null).
* @throws IllegalStateException
* if the entity provider was null.
*/
protected EntityProvider<T> doGetEntityProvider()
throws IllegalStateException {
if (entityProvider == null) {
throw new IllegalStateException("No EntityProvider has been set");
}
return entityProvider;
}
public boolean isReadOnly() {
return !(doGetEntityProvider() instanceof MutableEntityProvider)
|| readOnly;
}
@SuppressWarnings("unchecked")
public void setEntityProvider(EntityProvider<T> entityProvider) {
assert entityProvider != null : "entityProvider must not be null";
// Remove listener from old provider
if (this.entityProvider != null
&& this.entityProvider instanceof EntityProviderChangeNotifier) {
((EntityProviderChangeNotifier<T>) this.entityProvider)
.removeListener(this);
}
this.entityProvider = entityProvider;
// Register listener with new provider
registerProvider();
}
@SuppressWarnings("unchecked")
private void registerProvider() {
if (this.entityProvider instanceof EntityProviderChangeNotifier) {
((EntityProviderChangeNotifier<T>) this.entityProvider)
.addListener(this);
}
}
private void readObject(java.io.ObjectInputStream in) throws IOException,
ClassNotFoundException {
in.defaultReadObject();
// reattach to weak listener list of provider
registerProvider();
}
private boolean fireItemSetChangeOnProviderChange = true;
/**
* Specifies whether the container should fire an ItemSetChangeEvent when an
* EntityProviderChangeEvent is received. This is used to prevent clients
* from receiving duplicate ItemSetChangeEvents when the container modifies
* data and wants to handle ItemSetChangeEvents itself.
*
* @param fireItemSetChangeOnProviderChange
* true fo fire an ItemSetChangeEvent when the provider changes,
* false not to.
*/
protected void setFireItemSetChangeOnProviderChange(
boolean fireItemSetChangeOnProviderChange) {
this.fireItemSetChangeOnProviderChange = fireItemSetChangeOnProviderChange;
}
/**
* @see #setFireItemSetChangeOnProviderChange(boolean)
* @return true if an ItemSetChangeEvent should be fired when the provider
* changes, false if it should not.
*/
protected boolean isFireItemSetChangeOnProviderChange() {
return fireItemSetChangeOnProviderChange;
}
public void entityProviderChange(EntityProviderChangeEvent<T> event) {
if (isItemSetChangeEvent(event)
&& isFireItemSetChangeOnProviderChange()) {
fireContainerItemSetChange(new ProviderChangedEvent(event));
} else {
if (event instanceof EntityPropertyUpdatedEvent) {
// TODO fire itemSetChange event in case property of a sort
// column has changed
EntityPropertyUpdatedEvent<T> evt = (EntityPropertyUpdatedEvent<T>) event;
Collection<T> affectedEntities = evt.getAffectedEntities();
if (affectedEntities.isEmpty()) {
return;
}
for (T t : affectedEntities) {
if (entityClassMetadata.hasIdentifierProperty()) {
PersistentPropertyMetadata identifierProperty = entityClassMetadata
.getIdentifierProperty();
Object itemId = entityClassMetadata.getPropertyValue(t,
identifierProperty.getName());
firePropertyValueChangeEvent(itemId,
((EntityPropertyUpdatedEvent<T>) event)
.getPropertyId());
}
}
}
}
}
@SuppressWarnings("unchecked")
private void firePropertyValueChangeEvent(Object itemId, String propertyId) {
LinkedList<WeakReference<JPAContainerItem<T>>> linkedList;
synchronized (getItemRegistry()) {
LinkedList<WeakReference<JPAContainerItem<T>>> origList = getItemRegistry()
.get(itemId);
if (origList != null) {
linkedList = (LinkedList<WeakReference<JPAContainerItem<T>>>) origList
.clone();
} else {
return;
}
}
for (Iterator<WeakReference<JPAContainerItem<T>>> iterator = linkedList
.iterator(); iterator.hasNext();) {
WeakReference<JPAContainerItem<T>> weakReference = iterator.next();
JPAContainerItem<T> jpaContainerItem = weakReference.get();
if (jpaContainerItem != null) {
EntityItemProperty itemProperty = jpaContainerItem
.getItemProperty(propertyId);
itemProperty.fireValueChangeEvent();
}
}
}
private boolean isItemSetChangeEvent(EntityProviderChangeEvent<T> event) {
if (event instanceof EntityPropertyUpdatedEvent) {
return false;
}
return true;
}
public void setReadOnly(boolean readOnly)
throws UnsupportedOperationException {
if (readOnly) {
this.readOnly = readOnly;
} else {
if (doGetEntityProvider() instanceof MutableEntityProvider) {
this.readOnly = readOnly;
} else {
throw new UnsupportedOperationException(
"EntityProvider is not mutable");
}
}
}
/**
* Configures a property to be sortable based on another property, normally
* a sub-property of the main property to sort.
* <p>
* For example, let's say there is a property named <code>address</code> and
* that this property's type in turn has the property <code>street</code>.
* Addresses are not directly sortable as they are not simple properties.
* <p>
* If we want to be able to sort addresses based on the street property, we
* can set the sort property for <code>address</code> to be
* <code>address.street</code> using this method.
* <p>
* Normally the sort property should be of the form
* <code>propertyId + "." + subPropertyId</code>. Sort properties must be
* persistent and usable in JPQL, but need not be registered as separate
* properties in the container.
* <p>
* Note that the sort property is not checked when this method is called. If
* it is not a valid sort property, an exception will be thrown when trying
* to sort a container.
*
* @param propertyId
* property for which sorting should be configured
* @param sortProperty
* property or other JPQL string that should be used when sorting
* by propertyId is requested, typically a sub-property
* propertyId
* @throws IllegalArgumentException
* if the property <code>propertyId</code> is not in the
* container
* @since 1.2.1
*/
public void setSortProperty(String propertyId, String sortProperty)
throws IllegalArgumentException {
propertyList.setSortProperty(propertyId, sortProperty);
}
public Collection<String> getSortableContainerPropertyIds() {
// This includes properties for which a separate sort property has been
// defined.
return propertyList.getSortablePropertyMap().keySet();
}
public void sort(Object[] propertyId, boolean[] ascending) {
assert propertyId != null : "propertyId must not be null";
assert ascending != null : "ascending must not be null";
assert propertyId.length == ascending.length : "propertyId and ascending must have the same length";
sortByList = new LinkedList<SortBy>();
for (int i = 0; i < propertyId.length; ++i) {
if (!getSortableContainerPropertyIds().contains(
propertyId[i].toString())) {
throw new IllegalArgumentException(
"No such sortable property ID: " + propertyId[i]);
}
// #7711 map property ID to a sortable sub-property if configured
Object sortProperty = propertyList.getSortablePropertyMap().get(
propertyId[i]);
sortByList.add(new SortBy(sortProperty.toString(), ascending[i]));
}
sortByList = Collections.unmodifiableList(sortByList);
fireContainerItemSetChange(new ContainerSortedEvent());
}
/**
* Gets all the properties that the items should be sorted by, if any.
*
* @return an unmodifiable, possible empty list of <code>SortBy</code>
* instances (never null).
*/
protected List<SortBy> getSortByList() {
if (sortByList == null) {
return Collections.emptyList();
} else {
return sortByList;
}
}
/**
* <strong>This functionality is not supported by this
* implementation.</strong>
* <p>
* {@inheritDoc }
*/
public Object addItemAfter(Object previousItemId)
throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
/**
* <strong>This functionality is not supported by this
* implementation.</strong>
* <p>
* {@inheritDoc }
*/
public Item addItemAfter(Object previousItemId, Object newItemId)
throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
public Object firstItemId() {
if (isWriteThrough() || bufferingDelegate.getAddedItemIds().isEmpty()) {
Object itemId = doGetEntityProvider().getFirstEntityIdentifier(
this, getAppliedFiltersAsConjunction(), getSortByList());
if (itemId != null && !isWriteThrough()
&& bufferingDelegate.getDeletedItemIds().contains(itemId)) {
itemId = nextItemId(itemId);
}
return itemId;
} else {
return bufferingDelegate.getAddedItemIds().get(0);
}
}
public boolean isFirstId(Object itemId) {
assert itemId != null : "itemId must not be null";
return itemId.equals(firstItemId());
}
public boolean isLastId(Object itemId) {
assert itemId != null : "itemId must not be null";
return itemId.equals(lastItemId());
}
public Object lastItemId() {
Object itemId = doGetEntityProvider().getLastEntityIdentifier(this,
getAppliedFiltersAsConjunction(), getSortByList());
if (isWriteThrough() || bufferingDelegate.getAddedItemIds().isEmpty()) {
return itemId;
} else {
if (itemId == null) {
return bufferingDelegate.getAddedItemIds().get(
bufferingDelegate.getAddedItemIds().size() - 1);
} else {
return itemId;
}
}
}
public Object nextItemId(Object itemId) {
// Note, we do not check if given itemId is deleted as we use this
// method recursively to get itemId that is not deleted
if (isWriteThrough() || bufferingDelegate.getAddedItemIds().isEmpty()
|| !bufferingDelegate.isAdded(itemId)) {
Object id = doGetEntityProvider().getNextEntityIdentifier(this,
itemId, getAppliedFiltersAsConjunction(), getSortByList());
if (id != null && !isWriteThrough()
&& bufferingDelegate.isDeleted(id)) {
id = nextItemId(id);
}
return id;
} else {
int ix = bufferingDelegate.getAddedItemIds().indexOf(itemId);
if (ix == bufferingDelegate.getAddedItemIds().size() - 1) {
Object id = doGetEntityProvider()
.getFirstEntityIdentifier(this,
getAppliedFiltersAsConjunction(),
getSortByList());
if (id != null && bufferingDelegate.isDeleted(id)) {
id = nextItemId(id);
}
return id;
} else {
return bufferingDelegate.getAddedItemIds().get(ix + 1);
}
}
}
public Object prevItemId(Object itemId) {
// Note, we do not check if given itemId is deleted as we use this
// method recursively to get itemId that is not deleted
if (isWriteThrough() || bufferingDelegate.getAddedItemIds().isEmpty()) {
Object id = doGetEntityProvider().getPreviousEntityIdentifier(this,
itemId, getAppliedFiltersAsConjunction(), getSortByList());
if (id != null && !isWriteThrough()
&& bufferingDelegate.isDeleted(id)) {
id = prevItemId(id);
}
return id;
} else {
if (bufferingDelegate.isAdded(itemId)) {
int ix = bufferingDelegate.getAddedItemIds().indexOf(itemId);
if (ix == 0) {
return null;
} else {
return bufferingDelegate.getAddedItemIds().get(ix - 1);
}
} else {
Object prevId = doGetEntityProvider()
.getPreviousEntityIdentifier(this, itemId,
getAppliedFiltersAsConjunction(),
getSortByList());
if (prevId == null) {
return bufferingDelegate.getAddedItemIds().get(
bufferingDelegate.getAddedItemIds().size() - 1);
} else {
if (!isWriteThrough()
&& bufferingDelegate.isDeleted(prevId)) {
prevId = prevItemId(prevId);
}
return prevId;
}
}
}
}
/**
* <strong>This functionality is not supported by this
* implementation.</strong>
* <p>
* {@inheritDoc }
*/
public boolean addContainerProperty(Object propertyId, Class<?> type,
Object defaultValue) throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
/**
* <strong>This functionality is not supported by this
* implementation.</strong>
* <p>
* {@inheritDoc }
*/
public Item addItem(Object itemId) throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
/**
* <strong>This functionality is not fully supported by this
* implementation.</strong> The implementation tries to call empty parameter
* constructor and add entity as such to database. If identifiers are not
* autogenerated or empty parameter constructor does not exist, the
* operation will fail and throw UnSupportedOperationException.
* <p>
* {@inheritDoc }
*/
public Object addItem() throws UnsupportedOperationException {
try {
T newInstance = getEntityClass().newInstance();
Object id = addEntity(newInstance);
return id;
} catch (InstantiationException e) {
} catch (IllegalAccessException e) {
}
throw new UnsupportedOperationException();
}
public boolean containsId(Object itemId) {
boolean result = doContainsId(itemId);
if (containsIdFiresItemSetChangeIfNotFound && !result) {
fireContainerItemSetChange(new ItemNotFoundEvent());
}
return result;
}
private boolean containsIdFiresItemSetChangeIfNotFound = false;
private int cleanupCount;
/**
* Returns whether the {@link #containsId(java.lang.Object) } method will
* fire an item set change event if it returns false. This may be necessary
* when using the container together with a {@link com.vaadin.ui.Table} and
* there are multiple users modifying the same data source.
* <p>
* When a user selects an item in a Table, the table checks with the
* container if the item exists or not. If it does not exist, nothing
* happens. Normally, the item should always exist, but if the container has
* been changed after the initial set of items were fetched and cached by
* the table, there may be items in the Table that are not present in the
* container.
* <p>
* By enabling this flag, the Table will repaint itself if it tries to
* select a nonexistent item, causing the item to dissapear from the table
* as well.
*/
public boolean isContainsIdFiresItemSetChangeIfNotFound() {
return containsIdFiresItemSetChangeIfNotFound;
}
/**
* See {@link #isContainsIdFiresItemSetChangeIfNotFound() }.
*
* @param value
*/
public void setContainsIdFiresItemSetChangeIfNotFound(boolean value) {
this.containsIdFiresItemSetChangeIfNotFound = value;
}
/**
* @see Container#containsId(java.lang.Object)
*/
protected boolean doContainsId(Object itemId) {
if (isWriteThrough()) {
return doGetEntityProvider().containsEntity(this, itemId,
getAppliedFiltersAsConjunction());
} else {
return bufferingDelegate.isAdded(itemId)
|| (!bufferingDelegate.isDeleted(itemId) && doGetEntityProvider()
.containsEntity(this, itemId,
getAppliedFiltersAsConjunction()));
}
}
public Property getContainerProperty(Object itemId, Object propertyId) {
Item item = getItem(itemId);
return item == null ? null : item.getItemProperty(propertyId);
}
public Collection<String> getContainerPropertyIds() {
return propertyList.getPropertyNames();
}
/**
* Method used by {@link EntityItem} to gain access to the property list.
* Not to be used by clients directly.
*
* @return the property list.
*/
PropertyList<T> getPropertyList() {
// TODO Not sure whether this is a good idea, maybe the property list
// could be passed to EntityItem as a constructor parameter?
return propertyList;
}
/**
* {@inheritDoc }
* <p>
* Please note, that this method will create a new instance of
* {@link EntityItem} upon every execution. That is, two subsequent calls to
* this method with the same <code>itemId</code> will <b>not</b> return the
* same {@link EntityItem} instance. The actual entity instance may still be
* the same though, depending on the implementation of the entity provider.
*/
public EntityItem<T> getItem(Object itemId) {
if (itemId == null) {
return null;
}
if (isWriteThrough() || !bufferingDelegate.isModified()) {
T entity = doGetEntityProvider().getEntity(this, itemId);
return entity != null ? new JPAContainerItem<T>(this, entity)
: null;
} else {
if (bufferingDelegate.isAdded(itemId)) {
JPAContainerItem<T> item = new JPAContainerItem<T>(this,
bufferingDelegate.getAddedEntity(itemId), itemId, false);
return item;
} else if (bufferingDelegate.isUpdated(itemId)) {
JPAContainerItem<T> item = new JPAContainerItem<T>(this,
bufferingDelegate.getUpdatedEntity(itemId));
item.setDirty(true);
return item;
} else if (bufferingDelegate.isDeleted(itemId)) {
T entity = doGetEntityProvider().getEntity(this, itemId);
if (entity != null) {
JPAContainerItem<T> item = new JPAContainerItem<T>(this,
entity);
item.setDeleted(true);
return item;
} else {
return null;
}
} else {
T entity = doGetEntityProvider().getEntity(this, itemId);
return entity != null ? new JPAContainerItem<T>(this, entity)
: null;
}
}
}
/**
* Called by JPAContainerItem when item is created. Container can then keep
* (weak) references to all instantiated items. Those are needed e.g. for
* property value changes to happen correctly.
*
* @param item
*/
void registerItem(JPAContainerItem<T> item) {
// TODO write tests to ensure the registry gets cleaned up properly
synchronized (getItemRegistry()) {
doItemRegistryCleanup();
LinkedList<WeakReference<JPAContainerItem<T>>> listOfItemsForEntity = itemRegistry
.get(item.getItemId());
if (listOfItemsForEntity == null) {
listOfItemsForEntity = new LinkedList<WeakReference<JPAContainerItem<T>>>();
getItemRegistry().put(item.getItemId(), listOfItemsForEntity);
}
listOfItemsForEntity.add(new WeakReference<JPAContainerItem<T>>(
item));
}
}
private HashMap<Object, LinkedList<WeakReference<JPAContainerItem<T>>>> getItemRegistry() {
if (itemRegistry == null) {
itemRegistry = new HashMap<Object, LinkedList<WeakReference<JPAContainerItem<T>>>>();
}
return itemRegistry;
}
private void doItemRegistryCleanup() {
final boolean cleanup = (cleanupCount++) % CLEANUPRATE == 0;
if (cleanup) {
HashMap<Object, LinkedList<WeakReference<JPAContainerItem<T>>>> itemRegistry = getItemRegistry();
for (Iterator<Object> idIterator = itemRegistry.keySet().iterator(); idIterator
.hasNext();) {
Object id = idIterator.next();
LinkedList<WeakReference<JPAContainerItem<T>>> linkedList = itemRegistry
.get(id);
for (Iterator<WeakReference<JPAContainerItem<T>>> iterator = linkedList
.iterator(); iterator.hasNext();) {
WeakReference<JPAContainerItem<T>> ref = iterator.next();
if (ref.get() == null) {
iterator.remove();
}
}
if (linkedList.isEmpty()) {
idIterator.remove();
}
}
}
}
/**
* <strong>This impementation does not use lazy loading and performs bad
* when the number of items is large! Do not use unless you absolutely have
* to!</strong>
* <p>
* {@inheritDoc }
*/
public Collection<Object> getItemIds() {
Collection<Object> ids = getEntityProvider().getAllEntityIdentifiers(
this, getAppliedFiltersAsConjunction(), getSortByList());
if (isWriteThrough() || !bufferingDelegate.isModified()) {
return ids;
} else {
List<Object> newIds = new LinkedList<Object>();
newIds.addAll(ids);
newIds.addAll(bufferingDelegate.getAddedItemIds());
newIds.removeAll(bufferingDelegate.getDeletedItemIds());
return Collections.unmodifiableCollection(newIds);
}
}
public EntityItem<T> createEntityItem(T entity) {
return new JPAContainerItem<T>(this, entity, null, false);
}
public Class<?> getType(Object propertyId) {
assert propertyId != null : "propertyId must not be null";
return propertyList.getPropertyType(propertyId.toString());
}
public boolean removeContainerProperty(Object propertyId)
throws UnsupportedOperationException {
assert propertyId != null : "propertyId must not be null";
boolean result = propertyList.removeProperty(propertyId.toString());
updateFilterablePropertyIds();
return result;
}
public int size() {
int origSize = doGetEntityProvider().getEntityCount(this,
getAppliedFiltersAsConjunction());
if (isWriteThrough()) {
return origSize;
} else {
int newSize = origSize + bufferingDelegate.getAddedItemIds().size()
- bufferingDelegate.getDeletedItemIds().size();
return newSize;
}
}
/**
* Returns a conjunction (filter1 AND filter2 AND ... AND filterN) of all
* the applied filters. If there are no applied filters, this method returns
* null.
*
* @see #getAppliedFilters()
* @return a conjunction filter or null.
*/
protected Filter getAppliedFiltersAsConjunction() {
if (getAppliedFilters().isEmpty()) {
return null;
} else if (getAppliedFilters().size() == 1) {
return getAppliedFilters().iterator().next();
} else {
return new And(CollectionUtil.toArray(Filter.class,
getAppliedFilters()));
}
}
public Collection<Object> getFilterablePropertyIds() {
return filterSupport.getFilterablePropertyIds();
}
public boolean isFilterable(Object propertyId) {
return filterSupport.isFilterable(propertyId);
}
public List<Filter> getFilters() {
return filterSupport.getFilters();
}
public List<Filter> getAppliedFilters() {
return filterSupport.getAppliedFilters();
}
public void setApplyFiltersImmediately(boolean applyFiltersImmediately) {
filterSupport.setApplyFiltersImmediately(applyFiltersImmediately);
}
public boolean isApplyFiltersImmediately() {
return filterSupport.isApplyFiltersImmediately();
}
public void applyFilters() {
filterSupport.applyFilters();
}
public boolean hasUnappliedFilters() {
return filterSupport.hasUnappliedFilters();
}
public void addContainerFilter(Object propertyId, String filterString,
boolean ignoreCase, boolean onlyMatchPrefix) {
addContainerFilter(new SimpleStringFilter(propertyId, filterString,
ignoreCase, onlyMatchPrefix));
if (!isApplyFiltersImmediately()) {
applyFilters();
}
}
public void removeAllContainerFilters() {
filterSupport.removeAllFilters();
}
public void removeContainerFilters(Object propertyId) {
removeAllContainerFilters();
applyFilters();
}
/**
* {@inheritDoc}
*
* <p>
* <strong>Note</strong> that JPAContainer don't support custom
* {@link Filter}s as filtering is done on DB level. Only basic Filter
* implementations are supported. If more complex filtering is needed,
* developers should tend to {@link QueryModifierDelegate} that allows
* developers to use JPA Criteria API to modify queries.
*
* @see com.vaadin.data.Container.Filterable#addContainerFilter(com.vaadin.data.Container.Filter)
*/
public void addContainerFilter(Filter filter)
throws UnsupportedFilterException {
filterSupport.addFilter(filter);
}
public void removeContainerFilter(Filter filter) {
filterSupport.removeFilter(filter);
}
/**
* <strong>This functionality is not supported by this
* implementation.</strong>
* <p>
* {@inheritDoc }
*/
public Object addItemAt(int index) throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
/**
* <strong>This functionality is not supported by this
* implementation.</strong>
* <p>
* {@inheritDoc }
*/
public Item addItemAt(int index, Object newItemId)
throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
public Object getIdByIndex(int index) {
if (isWriteThrough()) {
return doGetEntityProvider().getEntityIdentifierAt(this,
getAppliedFiltersAsConjunction(), getSortByList(), index);
} else {
int addedItems = bufferingDelegate.getAddedItemIds().size();
if (index < addedItems) {
return bufferingDelegate.getAddedItemIds().get(index);
} else {
index -= addedItems;
index = bufferingDelegate.fixDbIndexWithDeletedItems(index);
Object itemId = doGetEntityProvider().getEntityIdentifierAt(
this, getAppliedFiltersAsConjunction(),
getSortByList(), index);
return itemId;
}
}
}
/**
* <strong>This implementation does not use lazy loading and performs
* <b>extremely</b> bad when the number of items is large! Do not use unless
* you absolutely have to!</strong>
* <p>
* {@inheritDoc }
*/
public int indexOfId(Object itemId) {
/*
* This is intentionally an ugly implementation! This method should not
* be used!
*/
int size = size();
if (size > 100) {
Logger.getLogger(getClass().getName())
.warning(
"(JPAContainer) WARNING! Invoking indexOfId() when size > 100 is not recommended!");
}
for (int i = 0; i < size; i++) {
Object id = getIdByIndex(i);
if (id == null) {
return -1;
} else if (id.equals(itemId)) {
if (!isWriteThrough() && bufferingDelegate.isDeleted(id)) {
return -1;
}
return i;
}
}
return -1;
}
/**
* Checks that the container is writable, i.e. the entity provider
* implements the {@link MutableEntityProvider} interface and the container
* is not marked as read only.
*
* @throws IllegalStateException
* if the container is read only.
* @throws UnsupportedOperationException
* if the entity provider does not support editing.
*/
protected void requireWritableContainer() throws IllegalStateException,
UnsupportedOperationException {
if (!(entityProvider instanceof MutableEntityProvider)) {
throw new UnsupportedOperationException(
"EntityProvider does not support editing");
}
if (readOnly) {
throw new IllegalStateException("Container is read only");
}
}
public Object addEntity(T entity) throws UnsupportedOperationException,
IllegalStateException {
assert entity != null : "entity must not be null";
requireWritableContainer();
Object id;
if (isWriteThrough()) {
T result = ((MutableEntityProvider<T>) getEntityProvider())
.addEntity(entity);
id = getEntityClassMetadata().getPropertyValue(result,
getEntityClassMetadata().getIdentifierProperty().getName());
} else {
id = bufferingDelegate.addEntity(entity);
}
setFireItemSetChangeOnProviderChange(false); // Prevent the container
// from firing duplicate
// events
try {
fireContainerItemSetChange(new ItemAddedEvent(id));
} finally {
setFireItemSetChangeOnProviderChange(true);
}
return id;
}
/**
* <strong>This feature is not well optimized. Using direct access to db is
* much faster.</strong>
* <p>
* {@inheritDoc }
*/
public boolean removeAllItems() {
try {
Collection<Object> itemIds = getItemIds();
for (Object id : itemIds) {
removeItem(id);
}
if (!isWriteThrough()) {
commit();
}
} catch (Exception e) {
return false;
}
return true;
}
public boolean removeItem(Object itemId)
throws UnsupportedOperationException {
assert itemId != null : "itemId must not be null";
requireWritableContainer();
if (isWriteThrough()) {
if (getEntityProvider().containsEntity(this, itemId, null)) {
((MutableEntityProvider<T>) getEntityProvider())
.removeEntity(itemId);
setFireItemSetChangeOnProviderChange(false);
try {
fireContainerItemSetChange(new ItemRemovedEvent(itemId));
} finally {
setFireItemSetChangeOnProviderChange(true);
}
return true;
} else {
return false;
}
} else {
if (bufferingDelegate.isAdded(itemId)
|| getEntityProvider().containsEntity(this, itemId, null)) {
bufferingDelegate.deleteItem(itemId);
setFireItemSetChangeOnProviderChange(false);
try {
fireContainerItemSetChange(new ItemRemovedEvent(itemId));
} finally {
setFireItemSetChangeOnProviderChange(true);
}
return true;
} else {
return false;
}
}
}
/**
* This method is used by the {@link JPAContainerItem} class and <b>should
* not be used by other classes</b>. It is only called when the item is in
* write through mode, i.e. when an updated property value is directly
* reflected in the backed entity instance. If the item is in buffered mode
* (write through is off), {@link #containerItemModified(JPAContainerItem)}
* is used instead.
* <p>
* This method notifies the container that the specified property of
* <code>item</code> has been modified. The container will then take
* appropriate actions to pass the changes on to the entity provider,
* depending on the state of the <code>writeThrough</code> property <i>of
* the container</i>.
* <p>
* If <code>item</code> has no item ID ({@link JPAContainerItem#getItemId() }
* ), this method does nothing.
*
* @see #isWriteThrough()
* @param item
* the item that has been modified (must not be null).
* @param propertyId
* the ID of the modified property (must not be null).
*/
void containerItemPropertyModified(JPAContainerItem<T> item,
String propertyId) {
assert item != null : "item must not be null";
assert propertyId != null : "propertyId must not be null";
if (item.getItemId() != null) {
requireWritableContainer();
Object itemId = item.getItemId();
if (isWriteThrough()) {
((MutableEntityProvider<T>) getEntityProvider())
.updateEntityProperty(itemId, propertyId, item
.getItemProperty(propertyId).getValue());
item.setDirty(false);
} else {
bufferingDelegate.updateEntity(itemId, item.getEntity());
}
}
}
/**
* This method is used by the {@link JPAContainerItem} class and <b>should
* not be used by other classes</b>. It is only called when the item is in
* buffered mode (write through is off), i.e. when updated property values
* are not reflected in the backend entity instance until the item's commit
* method has been invoked. If write through is turned on,
* {@link #containerItemPropertyModified(JPAContainerItem, String)} is used
* instead.
* <p>
* This method notifies the container that the specified <code>item</code>
* has been modified. The container will then take appropriate actions to
* pass the changes on to the entity provider, depending on the state of the
* <code>writeThrough</code> property <i>of the container</i>.
* <p>
* If <code>item</code> has no item ID ({@link JPAContainerItem#getItemId() }
* ), this method does nothing.
*
* @see #isWriteThrough()
* @param item
* the item that has been modified (must not be null).
*/
void containerItemModified(JPAContainerItem<T> item) {
assert item != null : "item must not be null";
if (item.getItemId() != null) {
requireWritableContainer();
Object itemId = item.getItemId();
if (isWriteThrough()) {
((MutableEntityProvider<T>) getEntityProvider())
.updateEntity(item.getEntity());
item.setDirty(false);
} else {
bufferingDelegate.updateEntity(itemId, item.getEntity());
}
}
}
public void commit() throws SourceException, InvalidValueException {
if (!isWriteThrough() && isModified()) {
bufferingDelegate.commit();
setFireItemSetChangeOnProviderChange(false);
try {
fireContainerItemSetChange(new ChangesCommittedEvent());
} finally {
setFireItemSetChangeOnProviderChange(true);
}
}
}
public void discard() throws SourceException {
if (!isWriteThrough() && isModified()) {
bufferingDelegate.discard();
setFireItemSetChangeOnProviderChange(false);
try {
fireContainerItemSetChange(new ChangesDiscardedEvent());
} finally {
setFireItemSetChangeOnProviderChange(true);
}
}
}
public boolean isModified() {
if (isWriteThrough()) {
return false;
} else {
return bufferingDelegate.isModified();
}
}
public boolean isReadThrough() {
EntityProvider<T> ep = doGetEntityProvider();
if (ep instanceof CachingEntityProvider) {
return !((CachingEntityProvider<T>) ep).isCacheEnabled();
}
return true; // There is no cache at all
}
public boolean isWriteThrough() {
return !(doGetEntityProvider() instanceof BatchableEntityProvider)
|| writeThrough;
}
/**
* <strong>This functionality is not supported by this
* implementation.</strong>
* <p>
* {@inheritDoc }
*/
public void setReadThrough(boolean readThrough) throws SourceException {
throw new UnsupportedOperationException();
}
/**
* {@inheritDoc }
* <p>
* <b>Note</b>, that write-through mode can only be turned off if the entity
* provider implements the {@link BatchableEntityProvider} interface.
*/
public void setWriteThrough(boolean writeThrough) throws SourceException,
InvalidValueException {
if (writeThrough) {
commit();
this.writeThrough = writeThrough;
} else {
if (doGetEntityProvider() instanceof BatchableEntityProvider) {
this.writeThrough = writeThrough;
} else {
throw new UnsupportedOperationException(
"EntityProvider is not batchable");
}
}
}
public void setAutoCommit(boolean autoCommit) throws SourceException,
InvalidValueException {
setWriteThrough(autoCommit);
}
public boolean isAutoCommit() {
return isWriteThrough();
}
private String parentProperty;
private String parentIdProperty;
public String getParentProperty() {
return parentProperty;
}
public void setParentProperty(String parentProperty) {
this.parentProperty = parentProperty;
if (parentProperty == null) {
parentIdProperty = null;
} else {
StringBuilder sb = new StringBuilder(parentProperty);
sb.append('.');
sb.append(getEntityClassMetadata().getIdentifierProperty()
.getName());
parentIdProperty = sb.toString();
}
}
public boolean areChildrenAllowed(Object itemId) {
assert itemId != null : "itemId must not be null";
return parentProperty != null && containsId(itemId);
}
private Filter getChildrenFilter(Object parentId) {
Filter parentFilter;
if (parentId == null) {
parentFilter = new IsNull(parentProperty);
} else {
parentFilter = new Equal(parentIdProperty, parentId);
}
Filter appliedFilter = getAppliedFiltersAsConjunction();
if (appliedFilter == null) {
return parentFilter;
} else {
return new And(parentFilter, appliedFilter);
}
}
public Collection<?> getChildren(Object itemId) {
if (getParentProperty() == null) {
if (itemId == null) {
return getItemIds();
} else {
return Collections.emptyList();
}
} else {
return doGetEntityProvider().getAllEntityIdentifiers(this,
getChildrenFilter(itemId), getSortByList());
}
}
public Object getParent(Object itemId) {
if (parentProperty == null) {
return null;
} else {
EntityItem<T> item = getItem(itemId);
@SuppressWarnings("unchecked")
T parent = item == null ? null : (T) item.getItemProperty(
parentProperty).getValue();
if (parent == null) {
return null;
} else {
return getEntityClassMetadata().getPropertyValue(
parent,
getEntityClassMetadata().getIdentifierProperty()
.getName());
}
}
}
public boolean hasChildren(Object itemId) {
return !getChildren(itemId).isEmpty();
}
public boolean isRoot(Object itemId) {
return getParent(itemId) == null;
}
public Collection<?> rootItemIds() {
return getChildren(null);
}
/**
* <strong>This functionality is not supported by this
* implementation.</strong>
* <p>
* {@inheritDoc }
*/
public boolean setChildrenAllowed(Object itemId, boolean areChildrenAllowed)
throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
/**
* <strong>This functionality is not supported by this
* implementation.</strong>
* <p>
* {@inheritDoc }
*/
public boolean setParent(Object itemId, Object newParentId)
throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
/**
* Event indicating that the container has been resorted.
*
* @author Petter Holmström (Vaadin Ltd)
* @since 1.0
*/
public final class ContainerSortedEvent implements ItemSetChangeEvent {
private static final long serialVersionUID = -4330673683011445634L;
protected ContainerSortedEvent() {
}
public Container getContainer() {
return JPAContainer.this;
}
}
/**
* Event indicating that the changes have been committed. It will be fired
* when the container has write-through/auto-commit turned off and
* {@link JPAContainer#commit()} is called.
*
* @author Petter Holmström (Vaadin Ltd)
* @since 1.0
*/
public final class ChangesCommittedEvent implements ItemSetChangeEvent {
private static final long serialVersionUID = -7802570988994951818L;
protected ChangesCommittedEvent() {
}
public Container getContainer() {
return JPAContainer.this;
}
}
/**
* Event indicating that the changes have been discarded. This event is
* fired when the container has write-through/auto-commit turned off and
* {@link JPAContainer#discard() } is called.
*
* @author Petter Holmström (Vaadin Ltd)
* @since 1.0
*/
public final class ChangesDiscardedEvent implements ItemSetChangeEvent {
private static final long serialVersionUID = 1192258036968002982L;
protected ChangesDiscardedEvent() {
}
public Container getContainer() {
return JPAContainer.this;
}
}
/**
* Event indicating that all the items have been removed from the container.
* This event is fired by {@link JPAContainer#removeAllItems() }.
*
* @author Petter Holmström (Vaadin Ltd)
* @since 1.0
*/
public final class AllItemsRemovedEvent implements ItemSetChangeEvent {
private static final long serialVersionUID = -7429226164483121998L;
protected AllItemsRemovedEvent() {
}
public Container getContainer() {
return JPAContainer.this;
}
}
/**
* Event fired by {@link JPAContainer#containsId(java.lang.Object) } when the
* result is false and {@link #isContainsIdFiresItemSetChangeIfNotFound() }
* is true.
*
* @author Petter Holmström (Vaadin Ltd)
* @since 1.0
*/
public final class ItemNotFoundEvent implements ItemSetChangeEvent {
private static final long serialVersionUID = 7542676056363040711L;
protected ItemNotFoundEvent() {
}
public Container getContainer() {
return JPAContainer.this;
}
}
/**
* Event fired when a {@link EntityProviderChangeEvent} is received by the
* container.
*
* @author Petter Holmström (Vaadin Ltd)
* @since 1.0
*/
public final class ProviderChangedEvent implements ItemSetChangeEvent {
private static final long serialVersionUID = -2719424959990430585L;
private final EntityProviderChangeEvent<?> event;
protected ProviderChangedEvent(EntityProviderChangeEvent<?> event) {
this.event = event;
}
public Container getContainer() {
return JPAContainer.this;
}
/**
* Gets the {@link EntityProviderChangeEvent} that caused this container
* event to be fired.
*/
public EntityProviderChangeEvent<?> getChangeEvent() {
return event;
}
}
/**
* Abstract base class for events concerning single {@link EntityItem}s.
*
* @author Petter Holmström (Vaadin Ltd)
* @since 1.0
*/
public abstract class ItemEvent implements ItemSetChangeEvent {
private static final long serialVersionUID = -7867054889972105067L;
protected final Object itemId;
protected ItemEvent(Object itemId) {
this.itemId = itemId;
}
public Container getContainer() {
return JPAContainer.this;
}
/**
* Gets the ID of the item that this event concerns.
*
* @return the item ID.
*/
public Object getItemId() {
return itemId;
}
}
/**
* Event indicating that an item has been added to the container. This event
* is fired by {@link JPAContainer#addEntity(java.lang.Object) }.
*
* @author Petter Holmström (Vaadin Ltd)
* @since 1.0
*/
public final class ItemAddedEvent extends ItemEvent {
private static final long serialVersionUID = 197074826066153230L;
protected ItemAddedEvent(Object itemId) {
super(itemId);
}
}
/**
* Event indicating that an item has been updated inside the container.
*
* @author Petter Holmström (Vaadin Ltd)
* @since 1.0
*/
public final class ItemUpdatedEvent extends ItemEvent {
private static final long serialVersionUID = 4464120712728895566L;
protected ItemUpdatedEvent(Object itemId) {
super(itemId);
}
}
/**
* Event indicating that an item has been removed from the container. This
* event is fired by {@link JPAContainer#removeItem(java.lang.Object) }.
*
* @author Petter Holmström (Vaadin Ltd)
* @since 1.0
*/
public final class ItemRemovedEvent extends ItemEvent {
private static final long serialVersionUID = 530688830477630703L;
protected ItemRemovedEvent(Object itemId) {
super(itemId);
}
}
public final class AllItemsRefreshedEvent implements ItemSetChangeEvent {
private static final long serialVersionUID = 530180436710345623L;
protected AllItemsRefreshedEvent() {
}
public Container getContainer() {
return JPAContainer.this;
}
}
public PropertyKind getPropertyKind(Object propertyId) {
assert propertyId != null : "propertyId must not be null";
return propertyList.getPropertyKind(propertyId.toString());
}
@SuppressWarnings("unchecked")
public void refreshItem(Object itemId) {
LinkedList<WeakReference<JPAContainerItem<T>>> linkedList = null;
synchronized (getItemRegistry()) {
LinkedList<WeakReference<JPAContainerItem<T>>> origList = getItemRegistry()
.get(itemId);
if (origList != null) {
linkedList = (LinkedList<WeakReference<JPAContainerItem<T>>>) origList
.clone();
}
}
if (linkedList != null) {
for (WeakReference<JPAContainerItem<T>> weakReference : linkedList) {
JPAContainerItem<T> jpaContainerItem = weakReference.get();
if (jpaContainerItem != null) {
jpaContainerItem.refresh();
}
}
}
}
/*
* (non-Javadoc)
*
* @see com.vaadin.addon.jpacontainer.EntityContainer#refresh()
*/
public void refresh() {
doGetEntityProvider().refresh();
bufferingDelegate.discard();
synchronized (getItemRegistry()) {
for (Object id : getItemRegistry().keySet().toArray()) {
refreshItem(id);
}
}
fireContainerItemSetChange(new AllItemsRefreshedEvent());
}
public QueryModifierDelegate getQueryModifierDelegate() {
return queryModifierDelegate;
}
/**
* Sets the {@link QueryModifierDelegate}, which is called in the different
* stages that the EntityProvider builds a criteria query.
*
* @param queryModifierDelegate
* the delegate.
*/
public void setQueryModifierDelegate(
QueryModifierDelegate queryModifierDelegate) {
this.queryModifierDelegate = queryModifierDelegate;
}
public void setBuffered(boolean buffered) {
// setReadThrough is an unsupported operation, so just set write
// through.
setWriteThrough(!buffered);
}
public boolean isBuffered() {
return !isReadThrough() && isWriteThrough();
}
public void addItemSetChangeListener(ItemSetChangeListener listener) {
addListener(listener);
}
public void removeItemSetChangeListener(ItemSetChangeListener listener) {
removeListener(listener);
}
public List<?> getItemIds(int startIndex, int numberOfItems) {
// FIXME this should be optimized
ArrayList<Object> ids = new ArrayList<Object>();
for (int i = 0; i < numberOfItems; i++) {
ids.add(getIdByIndex(startIndex + i));
}
return ids;
}
@Override
public Collection<Filter> getContainerFilters() {
return filterSupport.getAppliedFilters();
}
}