public Object perform()

in jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemSaveOperation.java [89:292]


    public Object perform(SessionContext context) throws RepositoryException {
        SessionItemStateManager stateMgr = context.getItemStateManager();

        /**
         * build list of transient (i.e. new & modified) states that
         * should be persisted
         */
        Collection<ItemState> dirty;
        try {
            dirty = getTransientStates(context.getItemStateManager());
        } catch (ConcurrentModificationException e) {
            String msg = "Concurrent modification; session is closed";
            log.error(msg, e);
            context.getSessionImpl().logout();
            throw e;
        }
        if (dirty.size() == 0) {
            // no transient items, nothing to do here
            return this;
        }

        /**
         * build list of transient descendants in the attic
         * (i.e. those marked as 'removed')
         */
        Collection<ItemState> removed =
            getRemovedStates(context.getItemStateManager());

        // All affected item states. The keys are used to look up whether
        // an item is affected, and the values are iterated through below
        Map<ItemId, ItemState> affected =
            new HashMap<ItemId, ItemState>(dirty.size() + removed.size());
        for (ItemState state : dirty) {
            affected.put(state.getId(), state);
        }
        for (ItemState state : removed) {
            affected.put(state.getId(), state);
        }

        /**
         * make sure that this save operation is totally 'self-contained'
         * and independent; items within the scope of this save operation
         * must not have 'external' dependencies;
         * (e.g. moving a node requires that the target node including both
         * old and new parents are saved)
         */
        for (ItemState transientState : affected.values()) {
            if (transientState.isNode()) {
                NodeState nodeState = (NodeState) transientState;
                Set<NodeId> dependentIDs = new HashSet<NodeId>();
                if (nodeState.hasOverlayedState()) {
                    NodeState overlayedState =
                            (NodeState) nodeState.getOverlayedState();
                    NodeId oldParentId = overlayedState.getParentId();
                    NodeId newParentId = nodeState.getParentId();
                    if (oldParentId != null) {
                        if (newParentId == null) {
                            // node has been removed, add old parents
                            // to dependencies
                            if (overlayedState.isShareable()) {
                                dependentIDs.addAll(overlayedState.getSharedSet());
                            } else {
                                dependentIDs.add(oldParentId);
                            }
                        } else {
                            if (!oldParentId.equals(newParentId)) {
                                // node has been moved to a new location,
                                // add old and new parent to dependencies
                                dependentIDs.add(oldParentId);
                                dependentIDs.add(newParentId);
                            } else {
                                // parent id hasn't changed, check whether
                                // the node has been renamed (JCR-1034)
                                if (!affected.containsKey(newParentId)
                                        && stateMgr.hasTransientItemState(newParentId)) {
                                    try {
                                        NodeState parent = (NodeState) stateMgr.getTransientItemState(newParentId);
                                        // check parent's renamed child node entries
                                        for (ChildNodeEntry cne : parent.getRenamedChildNodeEntries()) {
                                            if (cne.getId().equals(nodeState.getId())) {
                                                // node has been renamed,
                                                // add parent to dependencies
                                                dependentIDs.add(newParentId);
                                            }
                                        }
                                    } catch (ItemStateException ise) {
                                        // should never get here
                                        log.warn("failed to retrieve transient state: " + newParentId, ise);
                                    }
                                }
                            }
                        }
                    }
                }

                // removed child node entries
                for (ChildNodeEntry cne : nodeState.getRemovedChildNodeEntries()) {
                    dependentIDs.add(cne.getId());
                }
                // added child node entries
                for (ChildNodeEntry cne : nodeState.getAddedChildNodeEntries()) {
                    dependentIDs.add(cne.getId());
                }

                // now walk through dependencies and check whether they
                // are within the scope of this save operation
                for (NodeId id : dependentIDs) {
                    if (!affected.containsKey(id)) {
                        // JCR-1359 workaround: check whether unresolved
                        // dependencies originate from 'this' session;
                        // otherwise ignore them
                        if (stateMgr.hasTransientItemState(id)
                                || stateMgr.hasTransientItemStateInAttic(id)) {
                            // need to save dependency as well
                            String msg =
                                context.getItemManager().safeGetJCRPath(id)
                                + " needs to be saved as well.";
                            log.debug(msg);
                            throw new ConstraintViolationException(msg);
                        }
                    }
                }
            }
        }

        // validate access and node type constraints
        // (this will also validate child removals)
        validateTransientItems(context, dirty, removed);

        // start the update operation
        try {
            stateMgr.edit();
        } catch (IllegalStateException e) {
            throw new RepositoryException("Unable to start edit operation", e);
        }

        boolean succeeded = false;
        try {
            // process transient items marked as 'removed'
            removeTransientItems(context.getItemStateManager(), removed);

            // process transient items that have change in mixins
            processShareableNodes(
                    context.getRepositoryContext().getNodeTypeRegistry(),
                    dirty);

            // initialize version histories for new nodes (might generate new transient state)
            if (initVersionHistories(context, dirty)) {
                // re-build the list of transient states because the previous call
                // generated new transient state
                dirty = getTransientStates(context.getItemStateManager());
            }

            // process 'new' or 'modified' transient states
            persistTransientItems(context.getItemManager(), dirty);

            // dispose the transient states marked 'new' or 'modified'
            // at this point item state data is pushed down one level,
            // node instances are disconnected from the transient
            // item state and connected to the 'overlayed' item state.
            // transient item states must be removed now. otherwise
            // the session item state provider will return an orphaned
            // item state which is not referenced by any node instance.
            for (ItemState transientState : dirty) {
                // dispose the transient state, it is no longer used
                stateMgr.disposeTransientItemState(transientState);
            }

            // end update operation
            stateMgr.update();
            // update operation succeeded
            succeeded = true;
        } catch (StaleItemStateException e) {
            throw new InvalidItemStateException(
                    "Unable to update a stale item: " + this, e);
        } catch (ItemStateException e) {
            throw new RepositoryException(
                    "Unable to update item: " + this, e);
        } finally {
            if (!succeeded) {
                // update operation failed, cancel all modifications
                stateMgr.cancel();

                // JCR-288: if an exception has been thrown during
                // update() the transient changes have already been
                // applied by persistTransientItems() and we need to
                // restore transient state, i.e. undo the effect of
                // persistTransientItems()
                restoreTransientItems(context, dirty);
            }
        }

        // now it is safe to dispose the transient states:
        // dispose the transient states marked 'removed'.
        // item states in attic are removed after store, because
        // the observation mechanism needs to build paths of removed
        // items in store().
        for (ItemState transientState : removed) {
            // dispose the transient state, it is no longer used
            stateMgr.disposeTransientItemStateInAttic(transientState);
        }

        return this;
    }