/* * 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.version; import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import java.util.Arrays; import java.util.Calendar; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.ArrayList; /** * Implements a <code>InternalVersion</code> */ class InternalVersionImpl extends InternalVersionItemImpl implements InternalVersion { /** Logger instance */ private static final Logger log = LoggerFactory.getLogger(InternalVersionImpl.class); /** * the date when this version was created */ private Calendar created; /** * the set of version labes of this history (values == String) */ private HashSet<Name> labelCache = null; /** * specifies if this is the root version */ private final boolean isRoot; /** * the version name */ private final Name name; /** * the version history */ private final InternalVersionHistory versionHistory; /** * Creates a new internal version with the given version history and * persistence node. please note, that versions must be created by the * version history. * * @param vh containing version history * @param node node state of this version * @param name name of this version */ public InternalVersionImpl(InternalVersionHistoryImpl vh, NodeStateEx node, Name name) throws RepositoryException{ super(vh.getVersionManager(), node); this.versionHistory = vh; this.name = name; // init internal values InternalValue[] values = node.getPropertyValues(NameConstants.JCR_CREATED); if (values != null) { created = values[0].getDate(); } isRoot = name.equals(NameConstants.JCR_ROOTVERSION); } /** * {@inheritDoc} */ @Override public NodeId getId() { return node.getNodeId(); } /** * {@inheritDoc} */ @Override public InternalVersionItem getParent() { return versionHistory; } /** * {@inheritDoc} */ public Name getName() { return name; } /** * {@inheritDoc} */ public InternalFrozenNode getFrozenNode() { // get frozen node try { return (InternalFrozenNode) vMgr.getItem(getFrozenNodeId()); } catch (RepositoryException e) { throw new InconsistentVersioningState("unable to retrieve frozen node: " + e, null, e); } } /** * {@inheritDoc} */ public NodeId getFrozenNodeId() { ChildNodeEntry entry = node.getState().getChildNodeEntry(NameConstants.JCR_FROZENNODE, 1); if (entry == null) { throw new InconsistentVersioningState("version has no frozen node: " + getId()); } return entry.getId(); } /** * {@inheritDoc} */ public Calendar getCreated() { return created; } /** * {@inheritDoc} */ public List<InternalVersion> getSuccessors() { VersioningLock.ReadLock lock = vMgr.acquireReadLock(); try { InternalValue[] values = node.getPropertyValues(NameConstants.JCR_SUCCESSORS); if (values != null) { List<InternalVersion> versions = new ArrayList<InternalVersion>(values.length); for (InternalValue value : values) { InternalVersion version = versionHistory.getVersion(value.getNodeId()); if (version != null) { versions.add(version); } else { // Can happen with a corrupted repository (JCR-2655) log.warn("Missing successor {}", value.getNodeId()); } } return versions; } else { return Collections.emptyList(); } } finally { lock.release(); } } /** * {@inheritDoc} */ public InternalVersion getLinearSuccessor(InternalVersion baseVersion) { // walk up all predecessors of the base version until 'this' version // is found. InternalVersion pred = baseVersion.getLinearPredecessor(); while (pred != null && !pred.getId().equals(getId())) { baseVersion = pred; pred = baseVersion.getLinearPredecessor(); } return pred == null ? null : baseVersion; } /** * {@inheritDoc} */ public InternalVersion[] getPredecessors() { InternalValue[] values = node.getPropertyValues(NameConstants.JCR_PREDECESSORS); if (values != null) { InternalVersion[] versions = new InternalVersion[values.length]; for (int i = 0; i < values.length; i++) { versions[i] = versionHistory.getVersion(values[i].getNodeId()); } return versions; } else { return new InternalVersion[0]; } } /** * {@inheritDoc} * * Always return the left most predecessor */ public InternalVersion getLinearPredecessor() { InternalValue[] values = node.getPropertyValues(NameConstants.JCR_PREDECESSORS); if (values != null && values.length > 0) { return versionHistory.getVersion(values[0].getNodeId()); } else { return null; } } /** * {@inheritDoc} */ public boolean isMoreRecent(InternalVersion v) { InternalVersion[] preds = getPredecessors(); for (InternalVersion pred : preds) { if (pred.equals(v) || pred.isMoreRecent(v)) { return true; } } return false; } /** * {@inheritDoc} */ public InternalVersionHistory getVersionHistory() { return versionHistory; } /** * {@inheritDoc} */ public boolean hasLabel(Name label) { return internalHasLabel(label); } /** * {@inheritDoc} */ public Name[] getLabels() { return internalGetLabels(); } /** * {@inheritDoc} */ public boolean isRootVersion() { return isRoot; } /** * Clear the list of predecessors/successors and the label cache. */ synchronized void clear() { labelCache = null; } /** * stores the given successors or predecessors to the persistence node * * @param cessors list of versions to store * @param propname property name to store * @param store if <code>true</code> the node is stored * @throws RepositoryException if a repository error occurs */ private void storeXCessors(List<InternalVersion> cessors, Name propname, boolean store) throws RepositoryException { InternalValue[] values = new InternalValue[cessors.size()]; for (int i = 0; i < values.length; i++) { values[i] = InternalValue.create((cessors.get(i)).getId()); } node.setPropertyValues(propname, PropertyType.REFERENCE, values); if (store) { node.store(); } } /** * Detaches itself from the version graph. * * @throws RepositoryException if a repository error occurs */ void internalDetach() throws RepositoryException { // detach this from all successors for (InternalVersion aSucc : getSuccessors()) { ((InternalVersionImpl) aSucc).internalDetachPredecessor(this, true); } // detach cached successors from predecessors InternalVersion[] preds = getPredecessors(); for (InternalVersion pred : preds) { ((InternalVersionImpl) pred).internalDetachSuccessor(this, true); } // clear properties clear(); } /** * Attaches this version as successor to all predecessors. assuming that the * predecessors are already set. * * @throws RepositoryException if a repository error occurs */ void internalAttach() throws RepositoryException { InternalVersion[] preds = getPredecessors(); for (InternalVersion pred : preds) { ((InternalVersionImpl) pred).internalAddSuccessor(this, true); } } /** * Adds a version to the set of successors. * * @param succ successor * @param store <code>true</code> if node is stored * @throws RepositoryException if a repository error occurs */ private void internalAddSuccessor(InternalVersionImpl succ, boolean store) throws RepositoryException { List<InternalVersion> l = new ArrayList<InternalVersion>(getSuccessors()); if (!l.contains(succ)) { l.add(succ); storeXCessors(l, NameConstants.JCR_SUCCESSORS, store); } } /** * Removes the predecessor V of this predecessors list and adds all of Vs * predecessors to it. * <p> * please note, that this operation might corrupt the version graph * * @param v the successor to detach * @param store <code>true</code> if node is stored immediately * @throws RepositoryException if a repository error occurs */ private void internalDetachPredecessor(InternalVersionImpl v, boolean store) throws RepositoryException { // remove 'v' from predecessor list List<InternalVersion> l = new ArrayList<InternalVersion>(Arrays.asList(getPredecessors())); l.remove(v); // attach V's predecessors l.addAll(Arrays.asList(v.getPredecessors())); storeXCessors(l, NameConstants.JCR_PREDECESSORS, store); } /** * Removes the successor V of this successors list and adds all of Vs * successors to it. * <p> * please note, that this operation might corrupt the version graph * * @param v the successor to detach * @param store <code>true</code> if node is stored immediately * @throws RepositoryException if a repository error occurs */ private void internalDetachSuccessor(InternalVersionImpl v, boolean store) throws RepositoryException { // remove 'v' from successors list List<InternalVersion> l = new ArrayList<InternalVersion>(getSuccessors()); l.remove(v); // attach V's successors l.addAll(v.getSuccessors()); storeXCessors(l, NameConstants.JCR_SUCCESSORS, store); } /** * adds a label to the label cache. does not affect storage * * @param label label to add * @return <code>true</code> if the label was added */ synchronized boolean internalAddLabel(Name label) { if (labelCache == null) { labelCache = new HashSet<Name>(); } return labelCache.add(label); } /** * removes a label from the label cache. does not affect storage * * @param label label to remove * @return <code>true</code> if the label was removed */ synchronized boolean internalRemoveLabel(Name label) { return labelCache != null && labelCache.remove(label); } /** * checks, if a label is in the label cache * * @param label label to check * @return <code>true</code> if the label exists */ synchronized boolean internalHasLabel(Name label) { return labelCache != null && labelCache.contains(label); } /** * returns the array of the cached labels * * @return the internal labels */ synchronized Name[] internalGetLabels() { if (labelCache == null) { return Name.EMPTY_ARRAY; } else { return labelCache.toArray(new Name[labelCache.size()]); } } /** * Invalidate this item. */ void invalidate() { node.getState().discard(); } /** * Resolves jcr:successor properties that are missing. * * @throws RepositoryException if a repository error occurs */ void legacyResolveSuccessors() throws RepositoryException { InternalValue[] values = node.getPropertyValues(NameConstants.JCR_PREDECESSORS); if (values != null) { for (InternalValue value : values) { InternalVersionImpl v = (InternalVersionImpl) versionHistory.getVersion(value.getNodeId()); // version may be null if history is broken if (v != null) { v.internalAddSuccessor(this, false); } } } } /** * {@inheritDoc} */ public boolean equals(Object obj) { if (this == obj) { return true; } if (obj instanceof InternalVersionImpl) { InternalVersionImpl v = (InternalVersionImpl) obj; return v.getId().equals(getId()); } return false; } /** * {@inheritDoc} */ public int hashCode() { return getId().hashCode(); } /** * Returns the activity of this version or <code>null</code> if not defined * @return the activity or <code>null</code> * @throws RepositoryException if an error occurs */ public InternalActivityImpl getActivity() throws RepositoryException { if (node.hasProperty(NameConstants.JCR_ACTIVITY)) { InternalValue value = node.getPropertyValue(NameConstants.JCR_ACTIVITY); return (InternalActivityImpl) vMgr.getItem(value.getNodeId()); } else { return null; } } }