/** * 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.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.UUID; import com.vaadin.v7.data.Buffered.SourceException; import com.vaadin.v7.data.Validator.InvalidValueException; /** * A delegate class used by {@link JPAContainer} to handle buffered changes. * This class is not part of the public API and should not be used outside of * JPAContainer. * <p> * If the entity implements the {@link Cloneable} interface, clones of the * entities will be stored instead of the entities themselves. This has the * advantage of tracking exactly which changes have been made to an entity and * in which order (e.g. if the same entity is modified twice before the changes * are committed). * * @author Petter Holmström (Vaadin Ltd) * @since 1.0 */ final class BufferedContainerDelegate<T> implements Serializable { private static final long serialVersionUID = -4471665710680629463L; /** * Creates a new <code>BufferedContainerDelegate</code> for the specified * container. * * @param container * the <code>JPAContainer</code> (must not be null). */ BufferedContainerDelegate(JPAContainer<T> container) { assert container != null : "container must not be null"; this.container = container; } enum DeltaType { ADD, UPDATE, DELETE } final class Delta implements Serializable { private static final long serialVersionUID = -5907859901553818040L; final DeltaType type; final Object itemId; final T entity; Delta(DeltaType type, Object itemId, T entity) { this.type = type; this.itemId = itemId; this.entity = entity; } } private JPAContainer<T> container; // Delta list contains all changes private List<Delta> deltaList = new LinkedList<Delta>(); // We need a list to maintain the order in which the items were added... private List<Object> addedItemIdsCache = new ArrayList<Object>(); // ... and a map for storing the actual entities. private Map<Object, T> addedEntitiesCache = new HashMap<Object, T>(); // The same goes for the other caches private HashMap<Object, Integer> deletedItemIdsCache = new HashMap<Object, Integer>(); private Map<Object, T> updatedEntitiesCache = new HashMap<Object, T>(); /** * Gets a list of IDs of added entity items. The IDs appear in the order in * which they were added. * * @return an unmodifiable list of entity item IDs (never null). */ public List<Object> getAddedItemIds() { return Collections.unmodifiableList(addedItemIdsCache); } /** * Gets a list of IDs of deleted entity items. * * @return an unmodifiable list of entity item IDs (never null). */ public Collection<Object> getDeletedItemIds() { return Collections.unmodifiableCollection(deletedItemIdsCache.keySet()); } /** * Gets a list of IDs of update entity items. * * @return an unmodifiable list of entity item IDs (never null); */ public Collection<Object> getUpdatedItemIds() { return Collections .unmodifiableCollection(updatedEntitiesCache.keySet()); } /** * Gets the added entity whose item ID is <code>itemId</code>. * * @param itemId * the ID of the added item (must not be null). * @return the entity, or null if not found. */ public T getAddedEntity(Object itemId) { assert itemId != null : "itemId must not be null"; return addedEntitiesCache.get(itemId); } /** * Gets the updated entity whose item ID is <code>itemId</code>. * * @param itemId * the ID of the updated item (must not be null). * @return the entity, or null if not found. */ public T getUpdatedEntity(Object itemId) { assert itemId != null : "itemId must not be null"; return updatedEntitiesCache.get(itemId); } /** * Checks if <code>itemId</code> is in the list of added item IDs. * * @see #getAddedItemIds() * @param itemId * the item ID to check (must not be null). * @return true if the item ID is in the list, false if not. */ public boolean isAdded(Object itemId) { assert itemId != null : "itemId must not be null"; return addedEntitiesCache.containsKey(itemId); } /** * Checks if <code>itemId</code> is in the collection of deleted item IDs. * * @see #getDeletedItemIds() * @param itemId * the item ID to check (must not be null). * @return true if the item ID is in the collection, false if not. */ public boolean isDeleted(Object itemId) { assert itemId != null : "itemId must not be null"; return deletedItemIdsCache.containsKey(itemId); } /** * Checks if <code>itemId</code> is in the collection of updated item IDs. * * @see #getUpdatedItemIds() * @param itemId * the item ID to check (must not be null). * @return true if the item ID is in the collection, false if not. */ public boolean isUpdated(Object itemId) { assert itemId != null : "itemId must not be null"; return updatedEntitiesCache.containsKey(itemId); } /** * Checks if there are any uncommitted changes. * * @return true if there are uncommitted changes, false otherwise. */ public boolean isModified() { return !deltaList.isEmpty(); } private void clear() { deltaList.clear(); addedEntitiesCache.clear(); addedItemIdsCache.clear(); updatedEntitiesCache.clear(); deletedItemIdsCache.clear(); } /** * Commits the changes to the {@link BatchableEntityProvider} of the * JPAContainer. * * @throws com.vaadin.data.Buffered.SourceException * if any errors occured. * @throws com.vaadin.data.Validator.InvalidValueException * currently never thrown by this implementation. */ public void commit() throws SourceException, InvalidValueException { assert container.getEntityProvider() instanceof BatchableEntityProvider : "entityProvider is not batchable"; BatchableEntityProvider<T> ep = (BatchableEntityProvider<T>) container .getEntityProvider(); ep.batchUpdate(new BatchableEntityProvider.BatchUpdateCallback<T>() { private static final long serialVersionUID = -5385980617323427732L; public void batchUpdate( MutableEntityProvider<T> batchEnabledEntityProvider) { try { for (Delta delta : deltaList) { if (delta.type == DeltaType.ADD) { batchEnabledEntityProvider.addEntity(delta.entity); } else if (delta.type == DeltaType.UPDATE) { batchEnabledEntityProvider .updateEntity(delta.entity); } else if (delta.type == DeltaType.DELETE) { batchEnabledEntityProvider .removeEntity(delta.itemId); } } } catch (Exception e) { throw new SourceException(container, e); } } }); // Clean up clear(); } /** * Clears all the buffered changes. * * @throws com.vaadin.data.Buffered.SourceException * currently never thrown by this implementation. */ public void discard() throws SourceException { clear(); } /** * Adds <code>entity</code> to the list of entities to be saved when the * changes are committed. * * @param entity * the entity to save (must not be null). * @return the temporary item ID to be used to access the entity's item * (never null). */ public Object addEntity(T entity) { assert entity != null : "entity must not be null"; UUID uuid = UUID.randomUUID(); deltaList.add(new Delta(DeltaType.ADD, uuid, entity)); addedEntitiesCache.put(uuid, entity); addedItemIdsCache.add(0, uuid); return uuid; } /** * Marks the item identified by <code>itemId</code> for deletion when the * changes are committed. * * @param itemId * the ID of the item to be deleted (must not be null). */ public void deleteItem(Object itemId) { assert itemId != null : "itemId must not be null"; if (isAdded(itemId)) { addedEntitiesCache.remove(itemId); addedItemIdsCache.remove(itemId); for (int i = deltaList.size() - 1; i >= 0; i--) { if (deltaList.get(i).itemId.equals(itemId)) { deltaList.remove(i); } } } else { removeUpdateDelta(itemId); deltaList.add(new Delta(DeltaType.DELETE, itemId, null)); List<Object> allDbEntityIdentifiers = container.getEntityProvider() .getAllEntityIdentifiers(container, container.getAppliedFiltersAsConjunction(), container.getSortByList()); int dbIndexOfDeletedItem = allDbEntityIdentifiers.indexOf(itemId); deletedItemIdsCache.put(itemId, dbIndexOfDeletedItem); } } private void removeUpdateDelta(Object itemId) { if (isUpdated(itemId)) { updatedEntitiesCache.remove(itemId); for (int i = deltaList.size() - 1; i >= 0; i--) { if (deltaList.get(i).itemId.equals(itemId)) { deltaList.remove(i); } } } } /** * Adds <code>entity</code> to the list of entities to be updated when the * changes are committed. * * @param itemId * the item ID of the entity (must not be null). * @param entity * the entity to save (must not be null). */ public void updateEntity(Object itemId, T entity) { assert entity != null : "entity must not be null"; assert itemId != null : "itemId must not be null"; if (!isAdded(itemId)) { // remove possible old update, so that only the last update is // applied and order will be dictated by the last update removeUpdateDelta(itemId); deltaList.add(new Delta(DeltaType.UPDATE, itemId, entity)); updatedEntitiesCache.put(itemId, entity); } } public int fixDbIndexWithDeletedItems(int index) { Integer[] removedDbIndexes = getDbIndexesOfDeletedItems(); for (int i = 0; i < removedDbIndexes.length; i++) { if (removedDbIndexes[i] <= index) { index++; } } return index; } private Integer[] getDbIndexesOfDeletedItems() { Integer[] removedDbIndexes = new Integer[deletedItemIdsCache.size()]; removedDbIndexes = deletedItemIdsCache.values().toArray( removedDbIndexes); return removedDbIndexes; } }