Return-Path: Delivered-To: apmail-jackrabbit-commits-archive@www.apache.org Received: (qmail 77017 invoked from network); 26 Oct 2006 11:02:31 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (140.211.11.2) by minotaur.apache.org with SMTP; 26 Oct 2006 11:02:31 -0000 Received: (qmail 37711 invoked by uid 500); 26 Oct 2006 11:02:41 -0000 Delivered-To: apmail-jackrabbit-commits-archive@jackrabbit.apache.org Received: (qmail 37673 invoked by uid 500); 26 Oct 2006 11:02:41 -0000 Mailing-List: contact commits-help@jackrabbit.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@jackrabbit.apache.org Delivered-To: mailing list commits@jackrabbit.apache.org Received: (qmail 37654 invoked by uid 99); 26 Oct 2006 11:02:41 -0000 Received: from herse.apache.org (HELO herse.apache.org) (140.211.11.133) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 26 Oct 2006 04:02:41 -0700 X-ASF-Spam-Status: No, hits=0.6 required=10.0 tests=NO_REAL_NAME X-Spam-Check-By: apache.org Received-SPF: pass (herse.apache.org: local policy) Received: from [140.211.11.3] (HELO eris.apache.org) (140.211.11.3) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 26 Oct 2006 04:02:26 -0700 Received: by eris.apache.org (Postfix, from userid 65534) id 59D051A984D; Thu, 26 Oct 2006 04:02:06 -0700 (PDT) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r467956 [2/3] - in /jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi: ./ state/ version/ Date: Thu, 26 Oct 2006 11:02:04 -0000 To: commits@jackrabbit.apache.org From: angela@apache.org X-Mailer: svnmailer-1.1.0 Message-Id: <20061026110206.59D051A984D@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Modified: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/NodeState.java URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/NodeState.java?view=diff&rev=467956&r1=467955&r2=467956 ============================================================================== --- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/NodeState.java (original) +++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/NodeState.java Thu Oct 26 04:02:02 2006 @@ -18,6 +18,7 @@ import org.apache.commons.collections.list.AbstractLinkedList; import org.apache.commons.collections.iterators.UnmodifiableIterator; +import org.apache.commons.collections.iterators.IteratorChain; import org.apache.jackrabbit.spi.IdFactory; import org.apache.jackrabbit.spi.QNodeDefinition; import org.apache.jackrabbit.name.Path; @@ -25,7 +26,6 @@ import org.apache.jackrabbit.spi.NodeId; import org.apache.jackrabbit.spi.ItemId; import org.apache.jackrabbit.spi.Event; -import org.apache.jackrabbit.spi.PropertyId; import org.apache.jackrabbit.jcr2spi.state.entry.ChildNodeEntry; import org.apache.jackrabbit.jcr2spi.state.entry.ChildPropertyEntry; import org.apache.jackrabbit.jcr2spi.state.entry.PropertyReference; @@ -80,7 +80,7 @@ /** * the names of this node's mixin types */ - private QName[] mixinTypeNames = new QName[0]; + private QName[] mixinTypeNames = QName.EMPTY_ARRAY; /** * insertion-ordered collection of ChildNodeEntry objects @@ -106,12 +106,6 @@ private NodeReferences references; /** - * The ItemStateFactory which is used to create new - * ItemState instances. - */ - private final ItemStateFactory isf; - - /** * Constructs a new node state that is not connected. * * @param name the name of this NodeState @@ -129,12 +123,12 @@ QName nodeTypeName, QNodeDefinition definition, int initialStatus, ItemStateFactory isf, IdFactory idFactory, boolean isWorkspaceState) { - super(parent, initialStatus, idFactory, isWorkspaceState); + super(parent, initialStatus, isf, idFactory, isWorkspaceState); this.name = name; this.uuid = uuid; this.nodeTypeName = nodeTypeName; this.definition = definition; - this.isf = isf; + assertAvailability(); } /** @@ -150,11 +144,28 @@ protected NodeState(NodeState overlayedState, NodeState parent, int initialStatus, ItemStateFactory isf, IdFactory idFactory) { - super(overlayedState, parent, initialStatus, idFactory); - this.isf = isf; - reset(); + super(overlayedState, parent, initialStatus, isf, idFactory); + if (overlayedState != null) { + synchronized (overlayedState) { + NodeState wspState = (NodeState) overlayedState; + name = wspState.name; + uuid = wspState.uuid; + nodeTypeName = wspState.nodeTypeName; + definition = wspState.definition; + + init(wspState.getMixinTypeNames(), wspState.getChildNodeEntries(), wspState.getPropertyNames(), wspState.getNodeReferences()); + } + } + assertAvailability(); } + /** + * + * @param mixinTypeNames + * @param childEntries + * @param propertyNames + * @param references + */ void init(QName[] mixinTypeNames, Collection childEntries, Collection propertyNames, NodeReferences references) { if (mixinTypeNames != null) { this.mixinTypeNames = mixinTypeNames; @@ -165,19 +176,34 @@ Iterator it = propertyNames.iterator(); while (it.hasNext()) { QName propName = (QName) it.next(); - properties.put(propName, PropertyReference.create(this, propName, isf, idFactory)); + addPropertyEntry(PropertyReference.create(this, propName, isf, idFactory)); } // re-create child node entries childNodeEntries.removeAll(); it = childEntries.iterator(); while (it.hasNext()) { ChildNodeEntry cne = (ChildNodeEntry) it.next(); - childNodeEntries.add(cne.getName(), cne.getUUID()); + childNodeEntries.add(cne.getName(), cne.getUUID(), cne.getIndex()); } // set the node references this.references = references; } + private void assertAvailability() { + // TODO: improve this. + if (uuid != null) { + // make sure this state is connected to its childNode-entry + ChildNodeEntry cne = parent.childNodeEntries.get(uuid); + if (!cne.isAvailable()) { + try { + cne.getNodeState(); + } catch (ItemStateException e) { + // ignore + } + } + } + } + //----------------------------------------------------------< ItemState >--- /** * Determines if this item state represents a node. @@ -211,6 +237,7 @@ * @return the id of this node state. */ public NodeId getNodeId() { + NodeState parent = getParent(); if (uuid != null) { return idFactory.createNodeId(uuid); } else if (parent != null) { @@ -238,11 +265,30 @@ * @return the UUID of this node state or null if this * node cannot be identified with a UUID. */ - public final String getUUID() { + public String getUUID() { return uuid; } /** + * Modify the uuid of this state and make sure, that the parent state + * contains a proper childNodeEntry for this state. If the given uuid is + * not different from the uuid of this state, the method returns silently + * without changing neither the parent nor this state. + * + * @param uuid + */ + private void setUUID(String uuid) { + String oldUUID = this.uuid; + boolean mod = (oldUUID == null) ? uuid != null : !oldUUID.equals(uuid); + if (mod) { + this.uuid = uuid; + if (getParent() != null) { + getParent().childNodeEntries.replaceEntry(this); + } + } + } + + /** * Returns the name of this node's node type. * * @return the name of this node's node type. @@ -295,11 +341,7 @@ * @return references */ NodeReferences getNodeReferences() { - if (getStatus() == Status.NEW) { - return null; - } else { - return references; - } + return references; } /** @@ -504,6 +546,46 @@ } /** + * + * @param propEntry + */ + private void addPropertyEntry(ChildPropertyEntry propEntry) { + QName propName = propEntry.getName(); + properties.put(propName, propEntry); + try { + if (isWorkspaceState() && isUuidOrMixin(propName)) { + if (QName.JCR_UUID.equals(propName) && uuid == null) { + PropertyState ps = propEntry.getPropertyState(); + setUUID(ps.getValue().getString()); + } else if (QName.JCR_MIXINTYPES.equals(propName) && (mixinTypeNames == null || mixinTypeNames.length == 0)) { + PropertyState ps = propEntry.getPropertyState(); + mixinTypeNames = getMixinNames(ps); + } + } + } catch (ItemStateException e) { + log.error("Internal Error", e); + } catch (RepositoryException e) { + log.error("Internal Error", e); + } + } + + /** + * + * @param propName + */ + private void removePropertyEntry(QName propName) { + if (properties.remove(propName) != null) { + if (isWorkspaceState()) { + if (QName.JCR_UUID.equals(propName)) { + setUUID(null); + } else if (QName.JCR_MIXINTYPES.equals(propName)) { + mixinTypeNames = QName.EMPTY_ARRAY; + } + } + } + } + + /** * TODO: find a better way to provide the index of a child node entry * Returns the index of the given ChildNodeEntry and with * name. @@ -538,77 +620,37 @@ /** * * @param event - * @param changeLog - * @see ItemState#refresh(Event, ChangeLog) + * @see ItemState#refresh(Event) */ - synchronized void refresh(Event event, ChangeLog changeLog) { + synchronized void refresh(Event event) { checkIsWorkspaceState(); NodeId id = getNodeId(); + QName name = event.getQPath().getNameElement().getName(); switch (event.getType()) { case Event.NODE_ADDED: - case Event.PROPERTY_ADDED: - if (!id.equals(event.getParentId())) { - // TODO: TOBEFIXED. this should never occur and indicates severe consistency issue. - throw new IllegalArgumentException("Event parent (" + event.getParentId() + ") does not match this state with id: " + id); - } - ItemId evId = event.getItemId(); - ItemState newState = null; - - if (evId.denotesNode()) { - QName name = event.getQPath().getNameElement().getName(); - int index = event.getQPath().getNameElement().getNormalizedIndex(); - String uuid = (((NodeId)evId).getPath() != null) ? null : ((NodeId)evId).getUUID(); - - // add new childNodeEntry if it has not been added by - // some earlier 'add' event - // TODO: TOBEFIXED for SNSs - ChildNodeEntry cne = getChildNodeEntry(name, index); - if (cne == null || ((uuid == null) ? cne.getUUID() != null : !uuid.equals(cne.getUUID()))) { - cne = childNodeEntries.add(name, uuid); - } - try { - newState = cne.getNodeState(); - } catch (ItemStateException e) { - log.error("Internal error", e); - } - } else { - QName pName = ((PropertyId) event.getItemId()).getQName(); - // create a new property reference if it has not been - // added by some earlier 'add' event - ChildPropertyEntry re; - if (hasPropertyName(pName)) { - re = (ChildPropertyEntry) properties.get(pName); - } else { - re = PropertyReference.create(this, pName, isf, idFactory); - properties.put(pName, re); - } - try { - newState = re.getPropertyState(); - } catch (ItemStateException e) { - log.error("Internal error", e); - } - // make sure this state is up to date (uuid/mixins) - refresh(pName, event.getType()); + int index = event.getQPath().getNameElement().getNormalizedIndex(); + NodeId evId = (NodeId) event.getItemId(); + String uuid = (evId.getPath() != null) ? null : evId.getUUID(); + + // add new childNodeEntry if it has not been added by + // some earlier 'add' event + // TODO: TOBEFIXED for SNSs + ChildNodeEntry cne = childNodeEntries.get(name, index); + if (cne == null || ((uuid == null) ? cne.getUUID() != null : !uuid.equals(cne.getUUID()))) { + cne = childNodeEntries.add(name, uuid, index); } + // and let the transiently modified session state now, that + // its workspace state has been touched. + setStatus(Status.MODIFIED); + break; - // connect the added state from the transient layer to the - // new workspaceState and make sure its data are updated. - if (newState != null && changeLog != null) { - for (Iterator it = changeLog.addedStates(); it.hasNext();) { - ItemState added = (ItemState) it.next(); - if (added.hasOverlayedState()) { - // already connected - continue; - } - // TODO: TOBEFIXED. may fail (produce wrong results) for SNSs, since currently events upon 'save' are not garantied to be 'local' changes only - // TODO: TOBEFIXED. equals to false if added-state is referenceable. - if (added.getId().equals(evId)) { - added.connect(newState); - added.merge(); - break; - } - } + case Event.PROPERTY_ADDED: + // create a new property reference if it has not been + // added by some earlier 'add' event + if (!hasPropertyName(name)) { + ChildPropertyEntry re = PropertyReference.create(this, name, isf, idFactory); + addPropertyEntry(re); } // and let the transiently modified session state now, that // its workspace state has been touched. @@ -617,9 +659,8 @@ case Event.NODE_REMOVED: if (id.equals(event.getParentId())) { - QName qName = event.getQPath().getNameElement().getName(); - int index = event.getQPath().getNameElement().getNormalizedIndex(); - childNodeEntries.remove(qName, index); + index = event.getQPath().getNameElement().getNormalizedIndex(); + childNodeEntries.remove(name, index); setStatus(Status.MODIFIED); } else if (id.equals(event.getItemId())) { setStatus(Status.REMOVED); @@ -630,27 +671,19 @@ break; case Event.PROPERTY_REMOVED: - if (id.equals(event.getParentId())) { - QName pName = ((PropertyId) event.getItemId()).getQName(); - properties.remove(pName); - // make sure this state is up to date (uuid/mixins) - refresh(pName, event.getType()); - setStatus(Status.MODIFIED); - } else { - // ILLEGAL - throw new IllegalArgumentException("Illegal event type " + event.getType() + " for NodeState."); - } + removePropertyEntry(name); + setStatus(Status.MODIFIED); break; case Event.PROPERTY_CHANGED: - if (id.equals(event.getParentId())) { - QName pName = ((PropertyId) event.getItemId()).getQName(); - if (refresh(pName, event.getType())) { - setStatus(Status.MODIFIED); + if (QName.JCR_UUID.equals(name) || QName.JCR_MIXINTYPES.equals(name)) { + try { + PropertyState ps = getPropertyState(name); + adjustNodeState(this, new PropertyState[] {ps}); + } catch (ItemStateException e) { + // should never occur. + log.error("Internal error while updating node state.", e); } - } else { - // ILLEGAL - throw new IllegalArgumentException("Illegal event type " + event.getType() + " for NodeState."); } break; default: @@ -659,143 +692,191 @@ } } + //----------------------------------------------------< Session - State >--- /** - * Returns true, if the uuid or the mixin types of this state have been - * modified. - * - * @param propertyName - * @param eventType - * @return + * {@inheritDoc} + * @see ItemState#refresh(Collection,ChangeLog) */ - private boolean refresh(QName propertyName, int eventType) { - if (QName.JCR_UUID.equals(propertyName)) { - // TODO: to be fixed. - } else if (QName.JCR_MIXINTYPES.equals(propertyName)) { - if (eventType == Event.PROPERTY_REMOVED) { - mixinTypeNames = QName.EMPTY_ARRAY; - } else { // added or changed - try { - PropertyState ps = getPropertyState(propertyName); - QValue[] values = ps.getValues(); - QName[] newMixins = new QName[values.length]; - for (int i = 0; i < values.length; i++) { - newMixins[i] = QName.valueOf(values[i].getString()); - } - mixinTypeNames = newMixins; - } catch (ItemStateException e) { - // should never occur. - log.error("Internal error while updating mixin types.", e); - } catch (RepositoryException e) { - // should never occur. - log.error("Internal error while updating mixin types.", e); + void refresh(Collection events, ChangeLog changeLog) throws IllegalStateException { + + // remember parent states that have need to adjust their uuid/mixintypes + // or that got a new child entry added or existing entries removed. + HashMap modParents = new HashMap(); + + // process deleted states from the changelog + for (Iterator it = changeLog.deletedStates(); it.hasNext();) { + ItemState state = (ItemState) it.next(); + state.setStatus(Status.REMOVED); + state.overlayedState.setStatus(Status.REMOVED); + + // adjust parent states unless the parent is removed as well + NodeState parent = state.getParent(); + if (!changeLog.deletedStates.contains(parent)) { + NodeState overlayedParent = (NodeState) parent.overlayedState; + if (state.isNode()) { + overlayedParent.childNodeEntries.remove((NodeState)state.overlayedState); + } else { + overlayedParent.removePropertyEntry(state.overlayedState.getQName()); } + modifiedParent(parent, state, modParents); } - return true; - } - return false; - } + // don't remove processed state from changelog, but from event list + // state on changelog is used for check if parent is deleted as well. + removeEvent(events, state); + } + + // process added states from the changelog. since the changlog maintains + // LinkedHashSet for its entries, the iterator will not return a added + // entry before its NEW parent. + for (Iterator it = changeLog.addedStates(); it.hasNext();) { + ItemState addedState = (ItemState) it.next(); + NodeState parent = addedState.getParent(); + // TODO: only retrieve overlayed state, if necessary + try { + // adjust parent child-entries + NodeState overlayedParent = (NodeState) parent.overlayedState; + QName addedName = addedState.getQName(); + if (addedState.isNode()) { + int index = parent.getChildNodeEntry((NodeState) addedState).getIndex(); + ChildNodeEntry cne; + if (overlayedParent.hasChildNodeEntry(addedName, index)) { + cne = overlayedParent.getChildNodeEntry(addedName, index); + } else { + cne = overlayedParent.childNodeEntries.add(addedState.getQName(), null, index); + } + NodeState overlayed = cne.getNodeState(); + if (overlayed.getUUID() != null) { + overlayedParent.childNodeEntries.replaceEntry(overlayed); + } + addedState.connect(overlayed); + } else { + ChildPropertyEntry pe; + if (overlayedParent.hasPropertyName(addedName)) { + pe = (ChildPropertyEntry) overlayedParent.properties.get(addedName); + } else { + pe = PropertyReference.create(overlayedParent, addedName, overlayedParent.isf, overlayedParent.idFactory); + overlayedParent.addPropertyEntry(pe); + } + addedState.connect(pe.getPropertyState()); + } - //----------------------------------------------------< Session - State >--- - /** - * {@inheritDoc} - * @see ItemState#reset() - */ - synchronized void reset() { - checkIsSessionState(); + // make sure the new state gets updated (e.g. uuid created by server) + addedState.reset(); + // and mark the added-state existing + addedState.setStatus(Status.EXISTING); + // if parent is modified -> remember for final status reset + if (parent.getStatus() == Status.EXISTING_MODIFIED) { + modifiedParent(parent, addedState, modParents); + } - if (overlayedState != null) { - synchronized (overlayedState) { - NodeState wspState = (NodeState) overlayedState; - name = wspState.name; - uuid = wspState.uuid; - nodeTypeName = wspState.nodeTypeName; - definition = wspState.definition; + it.remove(); + removeEvent(events, addedState); + } catch (ItemStateException e) { + log.error("Internal error.", e); + } + } - init(wspState.getMixinTypeNames(), wspState.getChildNodeEntries(), wspState.getPropertyNames(), wspState.getNodeReferences()); + for (Iterator it = changeLog.modifiedStates(); it.hasNext();) { + ItemState modState = (ItemState) it.next(); + if (modState.isNode()) { + continue; + } + // push changes down to overlayed state + int type = ((PropertyState) modState).getType(); + QValue[] values = ((PropertyState) modState).getValues(); + ((PropertyState) modState.overlayedState).init(type, values); + + modState.setStatus(Status.EXISTING); + // if property state defines a modified jcr:mixinTypes + // the parent is listed as modified state and needs to be + // processed at the end. + if (isUuidOrMixin(modState.getQName())) { + modifiedParent(this, modState, modParents); + } + // remove the processed event from the set + it.remove(); + removeEvent(events, modState); + } + + /* process all parent states that need their uuid or mixin-types being + adjusted because that property has been added or modified */ + for (Iterator it = modParents.keySet().iterator(); it.hasNext();) { + NodeState parent = (NodeState) it.next(); + List l = (List) modParents.get(parent); + adjustNodeState(parent, (PropertyState[]) l.toArray(new PropertyState[l.size()])); + } + + /* finally check if all entries in the changelog have been processed + and eventually force a reload in order not to have any states with + wrong transient status floating around. */ + Iterator[] its = new Iterator[] {changeLog.addedStates(), changeLog.deletedStates(), changeLog.modifiedStates()}; + IteratorChain chain = new IteratorChain(its); + while (chain.hasNext()) { + ItemState state = (ItemState) chain.next(); + if (!(state.getStatus() == Status.EXISTING || state.getStatus() == Status.REMOVED)) { + // error: state has not been processed + // TODO: discard state and force reload of all data } } } /** * {@inheritDoc} - * @see ItemState#merge() + * @see ItemState#reset() */ - synchronized void merge() { + synchronized void reset() { checkIsSessionState(); if (overlayedState != null) { synchronized (overlayedState) { NodeState wspState = (NodeState) overlayedState; name = wspState.name; - uuid = wspState.uuid; + setUUID(wspState.uuid); nodeTypeName = wspState.nodeTypeName; definition = wspState.definition; mixinTypeNames = wspState.mixinTypeNames; - references = wspState.getNodeReferences(); - // search for removed properties - Collection wspProps = wspState.getPropertyNames(); + // remove all entries in the attic + propertiesInAttic.clear(); + + // merge prop-names + Collection wspPropNames = wspState.getPropertyNames(); + for (Iterator it = wspPropNames.iterator(); it.hasNext();) { + QName propName = (QName) it.next(); + if (!hasPropertyName(propName)) { + addPropertyEntry(PropertyReference.create(this, propName, isf, idFactory)); + } + } for (Iterator it = properties.keySet().iterator(); it.hasNext();) { - ChildPropertyEntry pe = (ChildPropertyEntry) properties.get((QName) it.next()); - if (pe.isAvailable()) { - try { - PropertyState ps = getPropertyState(pe.getName()); - if (ps.getStatus() == Status.REMOVED || ps.getStatus() == Status.STALE_DESTROYED) { - it.remove(); - } - } catch (ItemStateException e) { - log.error("Internal error while merging item node states.", e); - } - } else if (!wspProps.contains(pe.getName())) { - // not available and not present in wsp-layer any more. + // remove all prop-entries in the session state that are + // not present in the wsp-state. + if (!wspPropNames.contains(it.next())) { it.remove(); } } - // add missing property entries - for (Iterator it = wspProps.iterator(); it.hasNext();) { - QName propName = (QName) it.next(); - if (!hasPropertyName(propName)) { - properties.put(propName, PropertyReference.create(this, propName, isf, idFactory)); - } // else property is already listed - } - Collection wspEntries = wspState.getChildNodeEntries(); - // remove child entries, that are 'REMOVED' in the wsp layer - Set toRemove = new HashSet(); + // merge child node entries + for (Iterator it = wspState.getChildNodeEntries().iterator(); it.hasNext();) { + ChildNodeEntry cne = (ChildNodeEntry) it.next(); + int index = cne.getIndex(); + if (!childNodeEntries.contains(cne.getName(), index, cne.getUUID())) { + childNodeEntries.add(cne.getName(), cne.getUUID(), index); + } + } + List toRemove = new ArrayList(); for (Iterator it = getChildNodeEntries().iterator(); it.hasNext();) { ChildNodeEntry cne = (ChildNodeEntry) it.next(); - if (cne.isAvailable()) { - try { - NodeState ns = cne.getNodeState(); - if (ns.getStatus() == Status.REMOVED) { - toRemove.add(cne); - } - } catch (ItemStateException e) { - // should not occur - log.error("Internal error while merging item node states.", e); - } - } else if (wspState.getChildNodeEntries(cne.getName()).isEmpty()) { + if (!wspState.childNodeEntries.contains(cne.getName(), cne.getIndex(), cne.getUUID())) { toRemove.add(cne); - } // TODO: clean up same-named siblings + } } for (Iterator it = toRemove.iterator(); it.hasNext();) { ChildNodeEntry cne = (ChildNodeEntry) it.next(); childNodeEntries.remove(cne.getName(), cne.getIndex()); } - - // add missing child entries - for (Iterator it = wspEntries.iterator(); it.hasNext();) { - ChildNodeEntry wspEntry = (ChildNodeEntry) it.next(); - List namedEntries = getChildNodeEntries(wspEntry.getName()); - if (namedEntries.isEmpty()) { - // simple case: no cne with the given name - childNodeEntries.add(wspEntry.getName(), wspEntry.getUUID()); - } else { - List wspCnes = wspState.getChildNodeEntries(wspEntry.getName()); - // TODO: compare sn-siblings an add missing ones - } - } + // set the node references + references = wspState.references; } } } @@ -839,7 +920,7 @@ setStatus(Status.REMOVED); } // now inform parent - parent.childNodeStateRemoved(this); + getParent().childNodeStateRemoved(this); } /** @@ -919,18 +1000,18 @@ // set removed setStatus(Status.REMOVED); // remove from parent - parent.childNodeStateRemoved(this); + getParent().childNodeStateRemoved(this); affectedItemStates.add(this); break; case Status.REMOVED: // shouldn't happen actually, because a 'removed' state is not // accessible anymore log.warn("trying to revert an already removed node state"); - parent.childNodeStateRemoved(this); + getParent().childNodeStateRemoved(this); break; case Status.STALE_DESTROYED: // overlayed state does not exist anymore - parent.childNodeStateRemoved(this); + getParent().childNodeStateRemoved(this); affectedItemStates.add(this); break; } @@ -938,9 +1019,9 @@ /** * @inheritDoc - * @see ItemState#collectTransientStates(Set) + * @see ItemState#collectTransientStates(Collection) */ - void collectTransientStates(Set transientStates) { + void collectTransientStates(Collection transientStates) { checkIsSessionState(); switch (getStatus()) { @@ -978,38 +1059,20 @@ } /** - * Sets the names of this node's mixin types. - * - * @param mixinTypeNames set of names of mixin types - */ - synchronized void setMixinTypeNames(QName[] mixinTypeNames) { - checkIsSessionState(); - - if (mixinTypeNames != null) { - this.mixinTypeNames = mixinTypeNames; - } else { - this.mixinTypeNames = new QName[0]; - } - markModified(); - } - - /** * Adds a child node state to this node state. * * @param child the node state to add. - * @param uuid the uuid of the child node state or null if - * child cannot be identified with a uuid. * @throws IllegalArgumentException if this is not the parent * of child. */ - synchronized void addChildNodeState(NodeState child, String uuid) { + synchronized void addChildNodeState(NodeState child) { checkIsSessionState(); - if (child.getParent() != this) { throw new IllegalArgumentException("This NodeState is not the parent of child"); } ChildNodeEntry cne = ChildNodeReference.create(child, isf, idFactory); childNodeEntries.add(cne); + markModified(); } @@ -1069,7 +1132,7 @@ existingState = ref.getPropertyState(); } catch (ItemStateException e) { // probably does not exist anymore, remove from properties map - properties.remove(propertyName); + removePropertyEntry(propertyName); } if (existingState != null) { if (existingState.getStatus() == Status.EXISTING_REMOVED) { @@ -1080,7 +1143,7 @@ } } } - properties.put(propertyName, PropertyReference.create(propState, isf, idFactory)); + addPropertyEntry(PropertyReference.create(propState, isf, idFactory)); markModified(); } @@ -1099,11 +1162,10 @@ // remove property state from map of properties if it does not exist // anymore, otherwise leave the property state in the map if (propState.getStatus() == Status.REMOVED) { - properties.remove(propState.getQName()); + removePropertyEntry(propState.getQName()); } markModified(); } - /** * Reorders the child node insertNode before the child node * beforeNode. @@ -1167,8 +1229,7 @@ */ private synchronized void rename(QName newName) { checkIsSessionState(); - - if (parent == null) { + if (getParent() == null) { throw new IllegalStateException("root node cannot be renamed"); } name = newName; @@ -1302,6 +1363,80 @@ return false; } + /** + * + * @param ps + * @return + * @throws RepositoryException + */ + private static QName[] getMixinNames(PropertyState ps) throws RepositoryException { + assert QName.JCR_MIXINTYPES.equals(ps.getQName()); + + QValue[] values = ps.getValues(); + QName[] newMixins = new QName[values.length]; + for (int i = 0; i < values.length; i++) { + newMixins[i] = QName.valueOf(values[i].getString()); + } + return newMixins; + } + + private static boolean isUuidOrMixin(QName propName) { + return QName.JCR_UUID.equals(propName) || QName.JCR_MIXINTYPES.equals(propName); + } + + private static void modifiedParent(NodeState parent, ItemState child, Map modParents) { + List l; + if (modParents.containsKey(parent)) { + l = (List) modParents.get(parent); + } else { + l = new ArrayList(2); + modParents.put(parent, l); + } + if (child != null && !child.isNode() && isUuidOrMixin(child.getQName())) { + l.add(child); + } + } + + /** + * + * @param parent + * @param props + */ + private static void adjustNodeState(NodeState parent, PropertyState[] props) { + NodeState overlayed = (parent.isWorkspaceState()) ? parent : (NodeState) parent.overlayedState; + NodeState sState = (parent.isWorkspaceState()) ? (NodeState) overlayed.getSessionState() : parent; + + if (overlayed != null) { + for (int i = 0; i < props.length; i++) { + try { + if (QName.JCR_UUID.equals(props[i].getQName())) { + String uuid = (props[i].getStatus() == Status.REMOVED) ? null : props[i].getValue().getString(); + sState.setUUID(uuid); + overlayed.setUUID(uuid); + } else if (QName.JCR_MIXINTYPES.equals(props[i].getQName())) { + QName[] mixins = (props[i].getStatus() == Status.REMOVED) ? QName.EMPTY_ARRAY : getMixinNames(props[i]); + + sState.mixinTypeNames = mixins; + overlayed.mixinTypeNames = mixins; + } // else: ignore. + } catch (RepositoryException e) { + // should never occur. + log.error("Internal error while updating node state.", e); + } + } + + // make sure all other modifications on the overlayed state are + // reflected on the session-state. + sState.reset(); + // make sure, the session-state gets its status reset to Existing. + if (sState.getStatus() == Status.EXISTING_MODIFIED) { + sState.setStatus(Status.EXISTING); + } + } else { + // should never occur. + log.warn("Error while adjusting nodestate: Overlayed state is missing."); + } + } //------------------------------------------------------< inner classes >--- /** * ChildNodeEntries represents an insertion-ordered @@ -1326,6 +1461,61 @@ private final Map nameMap = new HashMap(); /** + * + * @param name + * @param index + * @param uuid + * @return + */ + private boolean contains(QName name, int index, String uuid) { + if (!nameMap.containsKey(name)) { + // no matching child node entry + return false; + } + Object o = nameMap.get(name); + if (o instanceof List) { + // SNS + for (Iterator it = ((List) o).iterator(); it.hasNext(); ) { + LinkedEntries.LinkNode n = (LinkedEntries.LinkNode) it.next(); + ChildNodeEntry cne = n.getChildNodeEntry(); + if (uuid == null) { + if (cne.getIndex() == index) { + return true; + } + } else if (uuid.equals(cne.getUUID())) { + return true; + } + } + // no matching entry found + return false; + } else { + // single child node with this name + ChildNodeEntry cne = ((LinkedEntries.LinkNode) o).getChildNodeEntry(); + if (uuid == null) { + return cne.getUUID() == null; + } else { + return uuid.equals(cne.getUUID()); + } + } + } + + /** + * + * @param uuid + * @return + */ + private ChildNodeEntry get(String uuid) { + for (Iterator it = entries.iterator(); it.hasNext();) { + LinkedEntries.LinkNode ln = (LinkedEntries.LinkNode) it.next(); + ChildNodeEntry cne = ln.getChildNodeEntry(); + if (cne.getUUID() == uuid) { + return cne; + } + } + return null; + } + + /** * Returns the ChildNodeEntry for the given * nodeState. * @@ -1346,7 +1536,7 @@ ChildNodeEntry cne = n.getChildNodeEntry(); // only check available child node entries try { - if (cne.isAvailable() && cne.getNodeState() == nodeState) { + if (cne.getNodeState() == nodeState) { return cne; } } catch (ItemStateException e) { @@ -1357,7 +1547,7 @@ // single child node with this name ChildNodeEntry cne = ((LinkedEntries.LinkNode) o).getChildNodeEntry(); try { - if (cne.isAvailable() && cne.getNodeState() == nodeState) { + if (cne.getNodeState() == nodeState) { return cne; } } catch (ItemStateException e) { @@ -1415,8 +1605,7 @@ }); } else { // map entry is a single child node entry - return Collections.singletonList( - ((LinkedEntries.LinkNode) obj).getChildNodeEntry()); + return Collections.singletonList(((LinkedEntries.LinkNode) obj).getChildNodeEntry()); } } @@ -1483,31 +1672,35 @@ * @return the created ChildNodeEntry. */ ChildNodeEntry add(QName nodeName, String uuid) { - List siblings = null; - Object obj = nameMap.get(nodeName); - if (obj != null) { - if (obj instanceof List) { - // map entry is a list of siblings - siblings = (List) obj; - } else { - // map entry is a single child node entry, - // convert to siblings list - siblings = new ArrayList(); - siblings.add(obj); - nameMap.put(nodeName, siblings); - } - } - - ChildNodeEntry entry = ChildNodeReference.create(NodeState.this, nodeName, uuid, isf, idFactory); - LinkedEntries.LinkNode ln = entries.add(entry); + ChildNodeEntry cne = ChildNodeReference.create(NodeState.this, nodeName, uuid, isf, idFactory); + add(cne); + return cne; + } - if (siblings != null) { - siblings.add(ln); - } else { - nameMap.put(nodeName, ln); - } + /** + * Insert a new childnode entry at the position indicated by index. + * @param nodeName + * @param uuid + * @param index + * @return + */ + ChildNodeEntry add(QName nodeName, String uuid, int index) { + ChildNodeEntry cne = add(nodeName, uuid); + // TODO: in case of SNS, move new cne to the right position. + return cne; + } - return entry; + /** + * Adds a childNode to the end of the list. + * + * @param childState the NodeState to add. + * @return the ChildNodeEntry which was created for + * childNode. + */ + ChildNodeEntry add(NodeState childState) { + ChildNodeEntry cne = ChildNodeReference.create(childState, isf, idFactory); + add(cne); + return cne; } /** @@ -1542,19 +1735,6 @@ } /** - * Adds a childNode to the end of the list. - * - * @param childNode the NodeState to add. - * @return the ChildNodeEntry which was created for - * childNode. - */ - ChildNodeEntry add(NodeState childNode) { - ChildNodeEntry cne = ChildNodeReference.create(childNode, isf, idFactory); - add(cne); - return cne; - } - - /** * Appends a list of ChildNodeEntrys to this list. * * @param entriesList the list of ChildNodeEntrys to add. @@ -1667,11 +1847,10 @@ * * @param insertNode the node state to move. * @param beforeNode the node state where insertNode is - * reordered to. + * reordered to. * @throws NoSuchItemStateException if insertNode or - * beforeNode does not - * have a ChildNodeEntry - * in this ChildNodeEntries. + * beforeNode does not have a ChildNodeEntry + * in this ChildNodeEntries. */ public void reorder(NodeState insertNode, NodeState beforeNode) throws NoSuchItemStateException { @@ -1731,6 +1910,24 @@ } /** + * If the given child state got a (new) uuid assigned or its removed, + * its childEntry must be adjusted. + * + * @param childState + */ + private void replaceEntry(NodeState childState) { + // NOTE: test if child-state needs to get a new entry not checked here. + try { + Object replaceObj = nameMap.get(childState.getQName()); + LinkedEntries.LinkNode ln = getLinkNode(replaceObj, childState); + ChildNodeEntry newCne = ChildNodeReference.create(childState, isf, idFactory); + entries.replaceNode(ln, newCne); + } catch (NoSuchItemStateException e) { + log.error("Internal error.", e); + } + } + + /** * Returns the matching LinkNode from a list or a single * LinkNode. * @@ -1893,6 +2090,17 @@ } else { addNode(insert, before); } + } + + /** + * Replace the value of the given LinkNode with a new childNodeEntry + * value. + * + * @param node + * @param value + */ + void replaceNode(LinkNode node, ChildNodeEntry value) { + updateNode(node, value); } /** Modified: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/PropertyState.java URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/PropertyState.java?view=diff&rev=467956&r1=467955&r2=467956 ============================================================================== --- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/PropertyState.java (original) +++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/PropertyState.java Thu Oct 26 04:02:02 2006 @@ -33,6 +33,8 @@ import org.slf4j.LoggerFactory; import java.util.Set; +import java.util.Collection; +import java.util.Iterator; /** * PropertyState represents the state of a Property. @@ -44,7 +46,12 @@ /** * The name of this property state. */ - private QName name; + private final QName name; + + /** + * Property definition + */ + private final QPropertyDefinition def; /** * The internal value(s) @@ -57,11 +64,6 @@ private int type; /** - * Property definition - */ - private QPropertyDefinition def; - - /** * Constructs a new property state that is initially connected to an * overlayed state. * @@ -71,8 +73,11 @@ * @param idFactory */ protected PropertyState(PropertyState overlayedState, NodeState parent, - int initialStatus, IdFactory idFactory) { - super(overlayedState, parent, initialStatus, idFactory); + int initialStatus, ItemStateFactory isf, IdFactory idFactory) { + super(overlayedState, parent, initialStatus, isf, idFactory); + this.name = overlayedState.name; + this.def = overlayedState.def; + reset(); } @@ -86,13 +91,12 @@ * @param idFactory */ protected PropertyState(QName name, NodeState parent, QPropertyDefinition definition, - int initialStatus, IdFactory idFactory, boolean isWorkspaceState) { - super(parent, initialStatus, idFactory, isWorkspaceState); + int initialStatus, ItemStateFactory isf, IdFactory idFactory, + boolean isWorkspaceState) { + super(parent, initialStatus, isf, idFactory, isWorkspaceState); this.name = name; this.def = definition; - - type = PropertyType.UNDEFINED; - values = QValue.EMPTY_ARRAY; + init(PropertyType.UNDEFINED, QValue.EMPTY_ARRAY); } /** @@ -101,6 +105,18 @@ * @param values */ void init(int type, QValue[] values) { + // free old values as necessary + QValue[] oldValues = this.values; + if (oldValues != null) { + for (int i = 0; i < oldValues.length; i++) { + QValue old = oldValues[i]; + if (old != null) { + // make sure temporarily allocated data is discarded + // before overwriting it (see QValue#discard()) + old.discard(); + } + } + } this.type = type; this.values = (values == null) ? QValue.EMPTY_ARRAY : values; } @@ -141,7 +157,7 @@ * @return the id of this property. */ public PropertyId getPropertyId() { - return idFactory.createPropertyId(parent.getNodeId(), getQName()); + return idFactory.createPropertyId(getParent().getNodeId(), getQName()); } /** @@ -205,27 +221,30 @@ //----------------------------------------------------< Workspace State >--- /** - * @see ItemState#refresh(Event, ChangeLog) + * @see ItemState#refresh(Event) */ - synchronized void refresh(Event event, ChangeLog changeLog) { + synchronized void refresh(Event event) { checkIsWorkspaceState(); switch (event.getType()) { case Event.PROPERTY_REMOVED: - if (event.getItemId().equals(getId())) { - setStatus(Status.REMOVED); - } else { - // ILLEGAL - throw new IllegalArgumentException("EventId " + event.getItemId() + " does not match id of this property state."); - } + setStatus(Status.REMOVED); break; case Event.PROPERTY_CHANGED: - if (event.getItemId().equals(getId())) { + // TODO: improve. + /* retrieve property value and type from server even if + changes were issued from this session (changelog). + this is currently the only way to update the workspace + state, which is not connected to its overlaying session-state. + */ + try { + PropertyState tmp = isf.createPropertyState(getPropertyId(), parent); + init(tmp.getType(), tmp.getValues()); setStatus(Status.MODIFIED); - } else { - // ILLEGAL - throw new IllegalArgumentException("EventId " + event.getItemId() + " does not match id of this property state."); + } catch (ItemStateException e) { + // TODO: rather throw? + log.error("Internal Error", e); } break; @@ -240,6 +259,29 @@ //----------------------------------------------------< Session - State >--- /** * {@inheritDoc} + * @see ItemState#refresh(Collection,ChangeLog) + */ + void refresh(Collection events, ChangeLog changeLog) throws IllegalStateException { + for (Iterator it = changeLog.modifiedStates(); it.hasNext();) { + ItemState modState = (ItemState) it.next(); + if (modState == this) { + /* + NOTE: overlayedState must be existing, otherwise save was not + possible on prop. Similarly a property can only be the changelog + target, if it was modified. removal, add must be persisted on parent. + */ + // push changes to overlayed state and reset status + ((PropertyState) overlayedState).init(getType(), getValues()); + setStatus(Status.EXISTING); + // parent must not be informed, since all properties that + // affect the parent state (uuid, mixins) are protected. + removeEvent(events, modState); + } + } + } + + /** + * {@inheritDoc} * @see ItemState#reset() */ synchronized void reset() { @@ -247,31 +289,23 @@ if (overlayedState != null) { synchronized (overlayedState) { PropertyState wspState = (PropertyState) overlayedState; - name = wspState.name; - type = wspState.type; - def = wspState.def; - values = wspState.values; + init(wspState.type, wspState.values); } } } - synchronized void merge() { - reset(); - } - /** * @inheritDoc * @see ItemState#remove() */ void remove() { checkIsSessionState(); - if (getStatus() == Status.NEW) { setStatus(Status.REMOVED); } else { setStatus(Status.EXISTING_REMOVED); } - parent.propertyStateRemoved(this); + getParent().propertyStateRemoved(this); } /** @@ -297,18 +331,18 @@ // set removed setStatus(Status.REMOVED); // and remove from parent - parent.propertyStateRemoved(this); + getParent().propertyStateRemoved(this); affectedItemStates.add(this); break; case Status.REMOVED: // shouldn't happen actually, because a 'removed' state is not // accessible anymore log.warn("trying to revert an already removed property state"); - parent.propertyStateRemoved(this); + getParent().propertyStateRemoved(this); break; case Status.STALE_DESTROYED: // overlayed does not exist anymore - parent.propertyStateRemoved(this); + getParent().propertyStateRemoved(this); affectedItemStates.add(this); break; } @@ -316,9 +350,9 @@ /** * @inheritDoc - * @see ItemState#collectTransientStates(Set) + * @see ItemState#collectTransientStates(Collection) */ - void collectTransientStates(Set transientStates) { + void collectTransientStates(Collection transientStates) { checkIsSessionState(); switch (getStatus()) { @@ -346,12 +380,11 @@ */ void setValues(QValue[] values, int type) throws RepositoryException { checkIsSessionState(); - // make sure the arguements are consistent and do not violate the // given property definition. validate(values, type, this.def); - this.values = values; - this.type = type; + init(type, values); + markModified(); } Modified: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/SessionItemStateManager.java URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/SessionItemStateManager.java?view=diff&rev=467956&r1=467955&r2=467956 ============================================================================== --- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/SessionItemStateManager.java (original) +++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/SessionItemStateManager.java Thu Oct 26 04:02:02 2006 @@ -18,8 +18,6 @@ import org.apache.jackrabbit.jcr2spi.HierarchyManager; import org.apache.jackrabbit.jcr2spi.HierarchyManagerImpl; -import org.apache.jackrabbit.jcr2spi.state.entry.ChildPropertyEntry; -import org.apache.jackrabbit.jcr2spi.state.entry.ChildNodeEntry; import org.apache.jackrabbit.jcr2spi.util.ReferenceChangeTracker; import org.apache.jackrabbit.jcr2spi.util.LogUtil; import org.apache.jackrabbit.jcr2spi.nodetype.EffectiveNodeType; @@ -56,6 +54,7 @@ import org.apache.jackrabbit.spi.ItemId; import org.apache.jackrabbit.spi.IdFactory; import org.apache.jackrabbit.value.QValue; +import org.apache.jackrabbit.uuid.UUID; import javax.jcr.InvalidItemStateException; import javax.jcr.ItemNotFoundException; @@ -74,14 +73,13 @@ import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.NoSuchNodeTypeException; import javax.jcr.lock.LockException; -import java.util.ArrayList; import java.util.Iterator; -import java.util.List; import java.util.Set; import java.util.HashSet; -import java.util.Arrays; import java.util.Calendar; import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; import java.io.InputStream; import java.io.IOException; @@ -126,7 +124,6 @@ // create hierarchy manager hierMgr = new HierarchyManagerImpl(this, nsResolver); - } /** @@ -193,8 +190,33 @@ * @param nodeState */ public Collection getReferingStates(NodeState nodeState) throws ItemStateException { - // TODO: not correct. ItemManager later on expectes overlaying state - return workspaceItemStateMgr.getReferingStates(nodeState); + NodeState wspState = (NodeState) nodeState.getWorkspaceState(); + if (wspState == null) { + // new state => unable to determine references + return Collections.EMPTY_SET; + } + + Collection rs = workspaceItemStateMgr.getReferingStates(wspState); + if (rs.isEmpty()) { + return rs; + } else { + // retrieve session-propertystates + Set refStates = new HashSet(); + for (Iterator it = rs.iterator(); it.hasNext();) { + PropertyState wState = (PropertyState) it.next(); + ItemState sState = wState.getSessionState(); + if (sState == null) { + // overlaying state has not been build up to now + sState = getItemState(wState.getPropertyId()); + } + // add property state to list of refering states unless it has + // be removed in the transient layer. + if (sState.isValid()) { + refStates.add(sState); + } + } + return Collections.unmodifiableCollection(refStates); + } } /** @@ -206,7 +228,12 @@ * @param nodeState */ public boolean hasReferingStates(NodeState nodeState) { - return workspaceItemStateMgr.hasReferingStates(nodeState); + try { + return !getReferingStates(nodeState).isEmpty(); + } catch (ItemStateException e) { + log.warn("Internal error", e); + return false; + } } //------------------------------------------< UpdatableItemStateManager >--- @@ -367,33 +394,20 @@ private void collectTransientStates(ItemState state, ChangeLog changeLog, boolean throwOnStale) throws StaleItemStateException, ItemStateException { // fail-fast test: check status of this item's state - switch (state.getStatus()) { - case Status.NEW: - { - String msg = LogUtil.safeGetJCRPath(state, nsResolver) + ": cannot save a new item."; - log.debug(msg); - throw new ItemStateException(msg); - } + if (state.getStatus() == Status.NEW) { + String msg = LogUtil.safeGetJCRPath(state, nsResolver) + ": cannot save a new item."; + log.debug(msg); + throw new ItemStateException(msg); } - if (throwOnStale) { - switch (state.getStatus()) { - case Status.STALE_MODIFIED: - { - String msg = LogUtil.safeGetJCRPath(state, nsResolver) + ": the item cannot be saved because it has been modified externally."; - log.debug(msg); - throw new StaleItemStateException(msg); - } - case Status.STALE_DESTROYED: - { - String msg = LogUtil.safeGetJCRPath(state, nsResolver) + ": the item cannot be saved because it has been deleted externally."; - log.debug(msg); - throw new StaleItemStateException(msg); - } - } + + if (throwOnStale && Status.isStale(state.getStatus())) { + String msg = LogUtil.safeGetJCRPath(state, nsResolver) + ": the item cannot be saved because it has been modified/removed externally."; + log.debug(msg); + throw new StaleItemStateException(msg); } // Set of transient states that should be persisted - Set transientStates = new HashSet(); + Set transientStates = new LinkedHashSet(); state.collectTransientStates(transientStates); for (Iterator it = transientStates.iterator(); it.hasNext();) { @@ -522,23 +536,12 @@ * @inheritDoc */ public void visit(SetMixin operation) throws ConstraintViolationException, AccessDeniedException, NoSuchNodeTypeException, UnsupportedRepositoryOperationException, VersionException, RepositoryException { - // remember if an existing mixin is being removed. - boolean anyRemoved; - + // NOTE: nodestate is only modified upon save of the changes! QName[] mixinNames = operation.getMixinNames(); NodeState nState = operation.getNodeState(); - // mixin-names to be execute on the nodestate (and corresponding property state) + // new array of mixinNames to be set on the nodestate (and corresponding property state) if (mixinNames != null && mixinNames.length > 0) { - // find out if any of the existing mixins is removed - List originalMixins = new ArrayList(); - originalMixins.addAll(Arrays.asList(nState.getMixinTypeNames())); - originalMixins.removeAll(Arrays.asList(mixinNames)); - anyRemoved = originalMixins.size() > 0; - - // update nodestate - nState.setMixinTypeNames(mixinNames); - // update/create corresponding property state if (nState.hasPropertyName(QName.JCR_MIXINTYPES)) { // execute value of existing property @@ -559,10 +562,6 @@ addPropertyState(nState, pd.getQName(), pd.getRequiredType(), mixinValue, pd, options); } } else { - anyRemoved = nState.getMixinTypeNames().length > 0; - // remove all mixins - nState.setMixinTypeNames(null); - // remove the jcr:mixinTypes property state if already present if (nState.hasPropertyName(QName.JCR_MIXINTYPES)) { try { @@ -576,53 +575,7 @@ } } - // make sure, the modification of the mixin set did not left child-item - // states defined by the removed mixin type(s) - // TODO: the following block should be delegated to 'server' - side. - if (anyRemoved) { - EffectiveNodeType ent = validator.getEffectiveNodeType(nState); - // use temp set to avoid ConcurrentModificationException - Iterator childProps = new HashSet(nState.getPropertyEntries()).iterator(); - while (childProps.hasNext()) { - try { - ChildPropertyEntry entry = (ChildPropertyEntry) childProps.next(); - PropertyState childState = entry.getPropertyState(); - QName declNtName = childState.getDefinition().getDeclaringNodeType(); - // check if property has been defined by mixin type (or one of its supertypes) - if (!ent.includesNodeType(declNtName)) { - // the remaining effective node type doesn't include the - // node type that declared this property, it is thus safe - // to remove it - int options = 0; // no checks required - removeItemState(childState, options); - } - } catch (ItemStateException e) { - // ignore. cleanup will occure upon save anyway - log.error("Error while removing child node defined by removed mixin: {0}", e.getMessage()); - } - } - // use temp array to avoid ConcurrentModificationException - Iterator childNodes = new ArrayList(nState.getChildNodeEntries()).iterator(); - while (childNodes.hasNext()) { - try { - ChildNodeEntry entry = (ChildNodeEntry) childNodes.next(); - NodeState childState = entry.getNodeState(); - // check if node has been defined by mixin type (or one of its supertypes) - QName declNtName = childState.getDefinition().getDeclaringNodeType(); - if (!ent.includesNodeType(declNtName)) { - // the remaining effective node type doesn't include the - // node type that declared this child node, it is thus safe - // to remove it. - int options = 0; // NOTE: referencial intergrity checked upon save. - removeItemState(childState, options); - } - } catch (ItemStateException e) { - // ignore. cleanup will occure upon save anyway - log.error("Error while removing child property defined by removed mixin: {0}", e.getMessage()); - } - } - } - + nState.markModified(); transientStateMgr.addOperation(operation); } @@ -734,13 +687,14 @@ validator.checkAddProperty(parent, propertyName, pDef, options); // create property state - PropertyState propState = transientStateMgr.createNewPropertyState(propertyName, parent, pDef); - - // NOTE: callers must make sure, the property type is not 'undefined' - propState.setValues(values, propertyType); + PropertyState propState = transientStateMgr.createNewPropertyState(propertyName, parent, pDef, values, propertyType); } - private void addNodeState(NodeState parent, QName nodeName, QName nodeTypeName, String uuid, QNodeDefinition definition, int options) throws RepositoryException, ConstraintViolationException, AccessDeniedException, UnsupportedRepositoryOperationException, NoSuchNodeTypeException, ItemExistsException, VersionException { + private void addNodeState(NodeState parent, QName nodeName, QName nodeTypeName, + String uuid, QNodeDefinition definition, int options) + throws RepositoryException, ConstraintViolationException, AccessDeniedException, + UnsupportedRepositoryOperationException, NoSuchNodeTypeException, + ItemExistsException, VersionException { // TODO: improve... // check if add node is possible. note, that the options differ if @@ -773,7 +727,7 @@ QPropertyDefinition pd = pda[i]; QValue[] autoValue = computeSystemGeneratedPropertyValues(nodeState, pd); if (autoValue != null) { - int propOptions = 0; // nothing to check + int propOptions = ItemStateValidator.CHECK_NONE; // execute 'addProperty' without adding operation. addPropertyState(nodeState, pd.getQName(), pd.getRequiredType(), autoValue, pd, propOptions); } @@ -824,19 +778,6 @@ throws ValueFormatException, LockException, ConstraintViolationException, AccessDeniedException, ItemExistsException, UnsupportedRepositoryOperationException, VersionException, RepositoryException { // assert that the property can be modified. validator.checkSetProperty(propState, options); - - // free old values as necessary - QValue[] oldValues = propState.getValues(); - if (oldValues != null) { - for (int i = 0; i < oldValues.length; i++) { - QValue old = oldValues[i]; - if (old != null) { - // make sure temporarily allocated data is discarded - // before overwriting it (see QValue#discard()) - old.discard(); - } - } - } propState.setValues(iva, valueType); } @@ -874,7 +815,11 @@ QName name = def.getQName(); if (QName.MIX_REFERENCEABLE.equals(declaringNT) && QName.JCR_UUID.equals(name)) { // mix:referenceable node type defines jcr:uuid - genValues = new QValue[]{QValue.create(parent.getNodeId().getUUID().toString())}; + String uuid = parent.getUUID(); + if (uuid == null) { + uuid = UUID.randomUUID().toString(); + } + genValues = new QValue[]{QValue.create(uuid)}; } else if (QName.NT_BASE.equals(declaringNT)) { // nt:base node type if (QName.JCR_PRIMARYTYPE.equals(name)) { Modified: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/Status.java URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/Status.java?view=diff&rev=467956&r1=467955&r2=467956 ============================================================================== --- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/Status.java (original) +++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/Status.java Thu Oct 26 04:02:02 2006 @@ -62,11 +62,41 @@ */ public static final int REMOVED = 8; - - public static boolean isTerminalStatus(int status) { + /** + * Returns true if the given status is a terminal status, i.e. + * the given status one of: + *
    + *
  • {@link #REMOVED}
  • + *
  • {@link #STALE_DESTROYED}
  • + *
+ * + * @param status + * @return + */ + public static boolean isTerminal(int status) { return status == REMOVED || status == STALE_DESTROYED; } + /** + * Returns true if this item state is valid, that is its status + * is one of: + *
    + *
  • {@link #EXISTING}
  • + *
  • {@link #EXISTING_MODIFIED}
  • + *
  • {@link #NEW}
  • + *
+ * + * @param status + * @return + */ + public static boolean isValid(int status) { + return status == EXISTING || status == EXISTING_MODIFIED || status == NEW; + } + + public static boolean isStale(int status) { + return status == STALE_DESTROYED || status == STALE_MODIFIED; + } + public static boolean isValidStatusChange(int oldStatus, int newStatus, boolean isWorkspaceState) { if (oldStatus == newStatus) { @@ -112,10 +142,9 @@ case REMOVED: isValid = (oldStatus == NEW || oldStatus == EXISTING || oldStatus == EXISTING_REMOVED); break; - /* default: - NEW cannot change state to NEW -> false - MODIFIED never applicable to session state -> false */ - + /* default: + NEW cannot change state to NEW -> false + MODIFIED never applicable to session state -> false */ } } return isValid; Modified: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/TransientISFactory.java URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/TransientISFactory.java?view=diff&rev=467956&r1=467955&r2=467956 ============================================================================== --- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/TransientISFactory.java (original) +++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/TransientISFactory.java Thu Oct 26 04:02:02 2006 @@ -35,16 +35,14 @@ private final IdFactory idFactory; private final ItemStateManager parent; - private ItemStateLifeCycleListener listener; + private ItemStateCache cache; + private ItemStateCreationListener listener; TransientISFactory(IdFactory idFactory, ItemStateManager parent) { this.idFactory = idFactory; this.parent = parent; } - void setListener(ItemStateLifeCycleListener listener) { - this.listener = listener; - } //------------------------------------------< TransientItemStateFactory >--- /** * @inheritDoc @@ -55,10 +53,15 @@ QNodeDefinition definition) { NodeState nodeState = new NodeState(name, uuid, parent, nodetypeName, definition, Status.NEW, this, idFactory, false); - // get a notification when this item state is saved or invalidated + + // notify listeners when this item state is saved or invalidated + nodeState.addListener(cache); nodeState.addListener(listener); - // notify listener that a node state has been created - listener.statusChanged(nodeState, Status.NEW); + + // notify listeners that a node state has been created + cache.created(nodeState); + listener.created(nodeState); + return nodeState; } @@ -68,14 +71,27 @@ */ public PropertyState createNewPropertyState(QName name, NodeState parent, QPropertyDefinition definition) { PropertyState propState = new PropertyState(name, parent, - definition, Status.NEW, idFactory, false); + definition, Status.NEW, this, idFactory, false); + // get a notification when this item state is saved or invalidated + propState.addListener(cache); propState.addListener(listener); - // notify listener that a property state has been created - listener.statusChanged(propState, Status.NEW); + + // notify listeners that a property state has been created + cache.created(propState); + listener.created(propState); + return propState; } + /** + * @inheritDoc + * @see TransientItemStateFactory#setListener(ItemStateCreationListener) + */ + public void setListener(ItemStateCreationListener listener) { + this.listener = listener; + } + //---------------------------------------------------< ItemStateFactory >--- /** * @inheritDoc @@ -84,9 +100,10 @@ public NodeState createRootState(ItemStateManager ism) throws ItemStateException { // retrieve state to overlay NodeState overlayedState = (NodeState) parent.getRootState(); - NodeState nodeState = new NodeState(overlayedState, null, - Status.EXISTING, this, idFactory); - nodeState.addListener(listener); + NodeState nodeState = new NodeState(overlayedState, null, Status.EXISTING, this, idFactory); + + nodeState.addListener(cache); + cache.created(nodeState); return nodeState; } @@ -96,16 +113,22 @@ */ public NodeState createNodeState(NodeId nodeId, ItemStateManager ism) throws NoSuchItemStateException, ItemStateException { - // retrieve state to overlay - NodeState overlayedState = (NodeState) parent.getItemState(nodeId); - NodeState overlayedParent = overlayedState.getParent(); - NodeState parentState = null; - if (overlayedParent != null) { - parentState = (NodeState) ism.getItemState(overlayedParent.getId()); + + NodeState nodeState = cache.getNodeState(nodeId); + if (nodeState == null) { + // retrieve state to overlay + NodeState overlayedState = (NodeState) parent.getItemState(nodeId); + NodeState overlayedParent = overlayedState.getParent(); + + NodeState parentState = null; + if (overlayedParent != null) { + parentState = (NodeState) ism.getItemState(overlayedParent.getId()); + } + + nodeState = new NodeState(overlayedState, parentState, Status.EXISTING, this, idFactory); + nodeState.addListener(cache); + cache.created(nodeState); } - NodeState nodeState = new NodeState(overlayedState, parentState, - Status.EXISTING, this, idFactory); - nodeState.addListener(listener); return nodeState; } @@ -115,11 +138,16 @@ */ public NodeState createNodeState(NodeId nodeId, NodeState parentState) throws NoSuchItemStateException, ItemStateException { - // retrieve state to overlay - NodeState overlayedState = (NodeState) parent.getItemState(nodeId); - NodeState nodeState = new NodeState(overlayedState, parentState, - Status.EXISTING, this, idFactory); - nodeState.addListener(listener); + + NodeState nodeState = cache.getNodeState(nodeId); + if (nodeState == null) { + // retrieve state to overlay + NodeState overlayedState = (NodeState) parent.getItemState(nodeId); + nodeState = new NodeState(overlayedState, parentState, Status.EXISTING, this, idFactory); + + nodeState.addListener(cache); + cache.created(nodeState); + } return nodeState; } @@ -130,11 +158,24 @@ public PropertyState createPropertyState(PropertyId propertyId, NodeState parentState) throws NoSuchItemStateException, ItemStateException { - // retrieve state to overlay - PropertyState overlayedState = (PropertyState) parent.getItemState(propertyId); - PropertyState propState = new PropertyState(overlayedState, parentState, - Status.EXISTING, idFactory); - propState.addListener(listener); + + PropertyState propState = cache.getPropertyState(propertyId); + if (propState == null) { + // retrieve state to overlay + PropertyState overlayedState = (PropertyState) parent.getItemState(propertyId); + propState = new PropertyState(overlayedState, parentState, Status.EXISTING, this, idFactory); + + propState.addListener(cache); + cache.created(propState); + } return propState; + } + + /** + * @inheritDoc + * @see ItemStateFactory#setCache(ItemStateCache) + */ + public void setCache(ItemStateCache cache) { + this.cache = cache; } } Modified: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/TransientItemStateFactory.java URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/TransientItemStateFactory.java?view=diff&rev=467956&r1=467955&r2=467956 ============================================================================== --- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/TransientItemStateFactory.java (original) +++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/TransientItemStateFactory.java Thu Oct 26 04:02:02 2006 @@ -55,4 +55,11 @@ public PropertyState createNewPropertyState(QName name, NodeState parent, QPropertyDefinition definition); + + /** + * Set the listener that gets informed about NEW states. + * + * @param listener + */ + public void setListener(ItemStateCreationListener listener); } Modified: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/TransientItemStateManager.java URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/TransientItemStateManager.java?view=diff&rev=467956&r1=467955&r2=467956 ============================================================================== --- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/TransientItemStateManager.java (original) +++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/TransientItemStateManager.java Thu Oct 26 04:02:02 2006 @@ -22,10 +22,13 @@ import org.apache.jackrabbit.spi.IdFactory; import org.apache.jackrabbit.spi.QNodeDefinition; import org.apache.jackrabbit.spi.QPropertyDefinition; +import org.apache.jackrabbit.value.QValue; import org.slf4j.LoggerFactory; import org.slf4j.Logger; import javax.jcr.ItemExistsException; +import javax.jcr.RepositoryException; +import javax.jcr.nodetype.ConstraintViolationException; import java.util.Iterator; import java.util.Collection; @@ -38,10 +41,10 @@ * item states. While all other modifications can be invoked on the item state * instances itself, creating a new node state is done using * {@link #createNewNodeState(QName, String, QName, QNodeDefinition, NodeState)} - * and {@link #createNewPropertyState(QName, NodeState, QPropertyDefinition)}. + * and {@link #createNewPropertyState(QName, NodeState, QPropertyDefinition, QValue[], int)}. */ public class TransientItemStateManager extends CachingItemStateManager - implements ItemStateLifeCycleListener { + implements ItemStateCreationListener { /** * Logger instance for this class. @@ -55,21 +58,19 @@ private final ChangeLog changeLog; /** - * The parent item state manager, which return item states that are then - * overlayed by transient item states created by this TransientItemStateManager. - */ - private final ItemStateManager parent; - - /** * The root node state or null if it hasn't been retrieved yet. */ private NodeState rootNodeState; + /** + * + * @param idFactory + * @param parent + */ TransientItemStateManager(IdFactory idFactory, ItemStateManager parent) { super(new TransientISFactory(idFactory, parent), idFactory); - this.parent = parent; this.changeLog = new ChangeLog(null); - ((TransientISFactory) getTransientFactory()).setListener(this); + getTransientFactory().setListener(this); } @@ -128,9 +129,8 @@ QNodeDefinition definition, NodeState parent) { NodeState nodeState = getTransientFactory().createNewNodeState(nodeName, uuid, parent, nodeTypeName, definition); - parent.addChildNodeState(nodeState, uuid); + parent.addChildNodeState(nodeState); changeLog.added(nodeState); - nodeState.addListener(this); return nodeState; } @@ -143,15 +143,20 @@ * @param definition * @return the created property state. * @throws ItemExistsException if parent already has a property - * with the given name. - */ - PropertyState createNewPropertyState(QName propName, NodeState parent, QPropertyDefinition definition) - throws ItemExistsException { + * with the given name. + * @throws ConstraintViolationException + * @throws RepositoryException + */ + PropertyState createNewPropertyState(QName propName, NodeState parent, + QPropertyDefinition definition, + QValue[] values, int propertyType) + throws ItemExistsException, ConstraintViolationException, RepositoryException { PropertyState propState = getTransientFactory().createNewPropertyState(propName, parent, definition); + // NOTE: callers must make sure, the property type is not 'undefined' + propState.init(propertyType, values); parent.addPropertyState(propState); changeLog.added(propState); - propState.addListener(this); return propState; } @@ -186,7 +191,6 @@ public NodeState getRootState() throws ItemStateException { if (rootNodeState == null) { rootNodeState = getItemStateFactory().createRootState(this); - rootNodeState.addListener(this); } return rootNodeState; } @@ -246,6 +250,11 @@ * @see ItemStateLifeCycleListener#statusChanged(ItemState, int) */ public void statusChanged(ItemState state, int previousStatus) { + if (!Status.isValidStatusChange(previousStatus, state.getStatus(), false)) { + log.error("ItemState has invalid status: " + state.getStatus()); + return; + } + // TODO: depending on status of state adapt change log // e.g. a revert on states will reset the status from // 'existing modified' to 'existing'. @@ -301,12 +310,20 @@ case Status.STALE_MODIFIED: // state is now stale. keep in modified. wait until refreshed break; - case Status.NEW: - // new state has been created - changeLog.added(state); - break; default: log.error("ItemState has invalid status: " + state.getStatus()); + } + } + + //-----------------------------------------< ItemStateCreationListener >--- + + /** + * @see ItemStateCreationListener#created(ItemState) + */ + public void created(ItemState state) { + // new state has been created + if (state.getStatus() == Status.NEW) { + changeLog.added(state); } } }