/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 org.apache.jackrabbit.core.state; import java.util.Map; import org.apache.commons.collections.map.LinkedMap; import org.apache.jackrabbit.core.id.ItemId; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.version.VersionItemStateManager; /** * Registers changes made to states and references and consolidates * empty changes. */ public class ChangeLog { /** * Added states */ @SuppressWarnings("unchecked") private final Map<ItemId, ItemState> addedStates = (Map<ItemId, ItemState>) new LinkedMap(); /** * Modified states */ @SuppressWarnings("unchecked") private final Map<ItemId, ItemState> modifiedStates = (Map<ItemId, ItemState>) new LinkedMap(); /** * Deleted states */ @SuppressWarnings("unchecked") private final Map<ItemId, ItemState> deletedStates = (Map<ItemId, ItemState>) new LinkedMap(); /** * Modified references */ @SuppressWarnings("unchecked") private final Map<NodeId, NodeReferences> modifiedRefs = (Map<NodeId, NodeReferences>) new LinkedMap(); private long updateSize; /** * Checks whether this change log contains any changes. This method is * used to avoid extra work on updates that contain no changes. * * @since Apache Jackrabbit 1.5 * @see <a href="https://issues.apache.org/jira/browse/JCR-1813">JCR-1813</a> * @return <code>true</code> if this log contains at least one change, * <code>false</code> otherwise */ public boolean hasUpdates() { return !(addedStates.isEmpty() && modifiedStates.isEmpty() && deletedStates.isEmpty() && modifiedRefs.isEmpty()); } /** * A state has been added * * @param state state that has been added */ public void added(ItemState state) { addedStates.put(state.getId(), state); } /** * A state has been modified. If the state is not a new state * (not in the collection of added ones), then disconnect * the local state from its underlying shared state and add * it to the modified states collection. * * @param state state that has been modified */ public void modified(ItemState state) { if (!addedStates.containsKey(state.getId())) { state.disconnect(); modifiedStates.put(state.getId(), state); } } /** * A state has been deleted. If the state is not a new state * (not in the collection of added ones), then disconnect * the local state from its underlying shared state, remove * it from the modified states collection and add it to the * deleted states collection. * * @param state state that has been deleted */ public void deleted(ItemState state) { assert state != null; if (addedStates.remove(state.getId()) == null) { state.disconnect(); modifiedStates.remove(state.getId()); deletedStates.put(state.getId(), state); } } /** * A references has been modified * * @param refs refs that has been modified */ public void modified(NodeReferences refs) { modifiedRefs.put(refs.id, refs); } /** * Removes the references entry with the given target node id. * This method is called by {@link VersionItemStateManager} to drop * references to virtual nodes. * * @param targetId target node id */ public void removeReferencesEntry(NodeId targetId) { modifiedRefs.remove(targetId); } /** * Return an item state given its id. Returns <code>null</code> * if the item state is neither in the added nor in the modified * section. Throws a <code>NoSuchItemStateException</code> if * the item state is in the deleted section. * * @return item state or <code>null</code> * @throws NoSuchItemStateException if the item has been deleted */ public ItemState get(ItemId id) throws NoSuchItemStateException { ItemState state = addedStates.get(id); if (state == null) { state = modifiedStates.get(id); if (state == null) { if (deletedStates.containsKey(id)) { throw new NoSuchItemStateException("State has been marked destroyed: " + id); } } } return state; } /** * Return a flag indicating whether a given item state exists. * * @return <code>true</code> if item state exists within this * log; <code>false</code> otherwise */ public boolean has(ItemId id) { return addedStates.containsKey(id) || modifiedStates.containsKey(id); } /** * Return a flag indicating whether a given item state is marked as * deleted in this log. * * @return <code>true</code> if item state is marked as deleted in this * log; <code>false</code> otherwise */ public boolean deleted(ItemId id) { return deletedStates.containsKey(id); } /** * Return a flag indicating whether a given item state is marked as * added in this log. * * @return <code>true</code> if item state is marked as added in this * log; <code>false</code> otherwise */ public boolean isAdded(ItemId id) { return addedStates.containsKey(id); } /** * Returns a flag indicating whether a given item state is marked as * modified in this log. * * @param id the id of the item. * @return <code>true</code> if the item state is marked as modified in this * log; <code>false</code> otherwise. */ public boolean isModified(ItemId id) { return modifiedStates.containsKey(id); } /** * Return a node references object given the target node id. Returns * <code>null</code> if the node reference is not in the modified * section. * * @return node references or <code>null</code> */ public NodeReferences getReferencesTo(NodeId id) { return modifiedRefs.get(id); } /** * Return the added states in this change log. * * @return added states */ public Iterable<ItemState> addedStates() { return addedStates.values(); } /** * Return the modified states in this change log. * <p> * Note that this change log must not be modified while iterating * through the returned states. * * @return modified states */ public Iterable<ItemState> modifiedStates() { return modifiedStates.values(); } /** * Return the deleted states in this change log. * <p> * Note that this change log must not be modified while iterating * through the returned states. * * @return deleted states */ public Iterable<ItemState> deletedStates() { return deletedStates.values(); } /** * Return the modified references in this change log. * <p> * Note that this change log must not be modified while iterating * through the returned states. * * @return modified references */ public Iterable<NodeReferences> modifiedRefs() { return modifiedRefs.values(); } /** * Merge another change log with this change log * * @param other other change log */ public void merge(ChangeLog other) { // Remove all states from our 'added' set that have now been deleted for (ItemState state : other.deletedStates()) { if (addedStates.remove(state.getId()) == null) { deletedStates.put(state.getId(), state); } // also remove from possibly modified state modifiedStates.remove(state.getId()); } // only add modified states that are not already 'added' for (ItemState state : other.modifiedStates()) { if (!addedStates.containsKey(state.getId())) { modifiedStates.put(state.getId(), state); } else { // adapt status and replace 'added' state.setStatus(ItemState.STATUS_NEW); addedStates.put(state.getId(), state); } } // add 'added' states for (ItemState state : other.addedStates()) { addedStates.put(state.getId(), state); } // add refs modifiedRefs.putAll(other.modifiedRefs); } /** * Push all states contained in the various maps of * items we have. */ public void push() { for (ItemState state : modifiedStates()) { state.push(); } for (ItemState state : deletedStates()) { state.push(); } for (ItemState state : addedStates()) { state.push(); } } /** * After the states have actually been persisted, update their * internal states and notify listeners. */ public void persisted() { for (ItemState state : modifiedStates()) { state.setStatus(ItemState.STATUS_EXISTING); state.notifyStateUpdated(); } for (ItemState state : deletedStates()) { state.setStatus(ItemState.STATUS_EXISTING_REMOVED); state.notifyStateDestroyed(); state.discard(); } for (ItemState state : addedStates()) { state.setStatus(ItemState.STATUS_EXISTING); state.notifyStateCreated(); } } /** * Reset this change log, removing all members inside the * maps we built. */ public void reset() { addedStates.clear(); modifiedStates.clear(); deletedStates.clear(); modifiedRefs.clear(); } /** * Disconnect all states in the change log from their overlaid * states. */ public void disconnect() { for (ItemState state : modifiedStates()) { state.disconnect(); } for (ItemState state : deletedStates()) { state.disconnect(); } for (ItemState state : addedStates()) { state.disconnect(); } } /** * Undo changes made to items in the change log. Discards * added items, refreshes modified and resurrects deleted * items. * * @param parent parent manager that will hold current data */ public void undo(ItemStateManager parent) { for (ItemState state : modifiedStates()) { try { state.connect(parent.getItemState(state.getId())); state.pull(); } catch (ItemStateException e) { state.discard(); } } for (ItemState state : deletedStates()) { try { state.connect(parent.getItemState(state.getId())); state.pull(); } catch (ItemStateException e) { state.discard(); } } for (ItemState state : addedStates()) { state.discard(); } reset(); } /** * Returns the update size of the change log. * * @return The update size. */ public long getUpdateSize() { return updateSize; } /** * Sets the update size of the change log. * * @param updateSize The update size. */ public void setUpdateSize(long updateSize) { this.updateSize = updateSize; } /** * Returns a string representation of this change log for diagnostic * purposes. * * @return a string representation of this change log */ public String toString() { StringBuilder buf = new StringBuilder(); buf.append("{"); buf.append("#addedStates=").append(addedStates.size()); buf.append(", #modifiedStates=").append(modifiedStates.size()); buf.append(", #deletedStates=").append(deletedStates.size()); buf.append(", #modifiedRefs=").append(modifiedRefs.size()); buf.append("}"); return buf.toString(); } }