/* * 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.persistence.util; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import javax.jcr.PropertyType; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.id.PropertyId; import org.apache.jackrabbit.core.persistence.PersistenceManager; import org.apache.jackrabbit.core.value.InternalValue; import org.apache.jackrabbit.core.state.PropertyState; import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This Class provides a simple structure to hold the nodestate and related * propertystate data. */ public class NodePropBundle { /** * default logger */ private static Logger log = LoggerFactory.getLogger(NodePropBundle.class); /** * the node id */ private final NodeId id; /** * the parent node id */ private NodeId parentId; /** * the nodetype name */ private Name nodeTypeName; /** * the mixintype names */ private Set<Name> mixinTypeNames; /** * the child node entries */ private LinkedList<NodePropBundle.ChildNodeEntry> childNodeEntries = new LinkedList<NodePropBundle.ChildNodeEntry>(); /** * the properties */ private HashMap<Name, PropertyEntry> properties = new HashMap<Name, PropertyEntry>(); /** * flag that indicates if this bundle is new */ private boolean isNew = true; /** * flag that indicates if this bundle is referenceable */ private boolean isReferenceable; /** * the mod count */ private short modCount; /** * the size */ private long size; /** * Shared set, consisting of the parent ids of this shareable node. This * entry is <code>null</code> if this node is not shareable. */ private Set<NodeId> sharedSet; /** * Creates a "new" bundle with the given id * * @param id the node id */ public NodePropBundle(NodeId id) { this.id = id; } /** * Creates a bundle from the given state * * @param state the node state */ public NodePropBundle(NodeState state) { this(state.getNodeId()); update(state); } /** * Updates this bundle with values from the given state. * @param state the node state */ public void update(NodeState state) { if (!id.equals(state.getNodeId())) { // sanity check throw new IllegalArgumentException("Not allowed to update foreign state."); } parentId = state.getParentId(); nodeTypeName = state.getNodeTypeName(); mixinTypeNames = state.getMixinTypeNames(); isReferenceable = state.hasPropertyName(NameConstants.JCR_UUID); modCount = state.getModCount(); List<org.apache.jackrabbit.core.state.ChildNodeEntry> list = state.getChildNodeEntries(); childNodeEntries.clear(); for (org.apache.jackrabbit.core.state.ChildNodeEntry cne : list) { addChildNodeEntry(cne.getName(), cne.getId()); } sharedSet = state.getSharedSet(); } /** * Creates a node state from the values of this bundle * @param pMgr the persistence manager * @return the new nodestate */ public NodeState createNodeState(PersistenceManager pMgr) { NodeState state = pMgr.createNew(id); state.setParentId(parentId); state.setNodeTypeName(nodeTypeName); state.setMixinTypeNames(mixinTypeNames); state.setModCount(modCount); for (ChildNodeEntry e : childNodeEntries) { state.addChildNodeEntry(e.getName(), e.getId()); } state.setPropertyNames(properties.keySet()); // add fake property entries state.addPropertyName(NameConstants.JCR_PRIMARYTYPE); if (mixinTypeNames.size() > 0) { state.addPropertyName(NameConstants.JCR_MIXINTYPES); } // uuid is special...only if 'referenceable' if (isReferenceable) { state.addPropertyName(NameConstants.JCR_UUID); } for (NodeId nodeId : sharedSet) { state.addShare(nodeId); } return state; } /** * Checks if this bundle is new. * @return <code>true</code> if this bundle is new; * <code>false</code> otherwise. */ public boolean isNew() { return isNew; } /** * Marks this bundle as 'not new'. */ public void markOld() { isNew = false; } /** * Returns the node id of this bundle * @return the node id of this bundle */ public NodeId getId() { return id; } /** * Returns the parent id of this bundle * @return the parent id of this bundle */ public NodeId getParentId() { return parentId; } /** * Sets the parent id * @param parentId the parent id */ public void setParentId(NodeId parentId) { this.parentId = parentId; } /** * Returns the nodetype name of this bundle * @return the nodetype name of this bundle */ public Name getNodeTypeName() { return nodeTypeName; } /** * Sets the node type name * @param nodeTypeName the nodetype name */ public void setNodeTypeName(Name nodeTypeName) { this.nodeTypeName = nodeTypeName; } /** * Returns the mixin type names of this bundle. * @return the mixin type names of this bundle. */ public Set<Name> getMixinTypeNames() { return mixinTypeNames; } /** * Sets the mixin type names * @param mixinTypeNames the mixin type names */ public void setMixinTypeNames(Set<Name> mixinTypeNames) { this.mixinTypeNames = mixinTypeNames; } /** * Checks if this bundle is referenceable. * @return <code>true</code> if this bundle is referenceable; * <code>false</code> otherwise. */ public boolean isReferenceable() { return isReferenceable; } /** * Sets the is referenceable flag on this bundle * @param referenceable the ref. flag */ public void setReferenceable(boolean referenceable) { isReferenceable = referenceable; } /** * Returns the mod count. * * @return the mod count. */ public short getModCount() { return modCount; } /** * Sets the mod count * * @param modCount the mod count */ public void setModCount(short modCount) { this.modCount = modCount; } /** * Returns the list of the child node entries. * @return the list of the child node entries. */ public List<NodePropBundle.ChildNodeEntry> getChildNodeEntries() { return childNodeEntries; } /** * Adds a child node entry. * @param name the name of the entry. * @param id the id of the entry */ public void addChildNodeEntry(Name name, NodeId id) { childNodeEntries.add(new ChildNodeEntry(name, id)); } /** * Adds a new property entry * @param entry the enrty to add */ public void addProperty(PropertyEntry entry) { assert !NameConstants.JCR_PRIMARYTYPE.equals(entry.getName()); assert !NameConstants.JCR_UUID.equals(entry.getName()); properties.put(entry.getName(), entry); } /** * Creates a property entry from the given state and adds it. * * @param state the property state * @param blobStore BLOB store from where to delete previous property value */ public void addProperty(PropertyState state, BLOBStore blobStore) { PropertyEntry old = properties.put(state.getName(), new PropertyEntry(state)); if (old != null) { old.destroy(blobStore); } } /** * Checks if this bundle has a property * @param name the name of the property * @return <code>true</code> if the property exists; * <code>false</code> otherwise. */ public boolean hasProperty(Name name) { return properties.containsKey(name); } /** * Returns a set of the property names. * @return a set of the property names. */ public Set<Name> getPropertyNames() { return properties.keySet(); } /** * Returns a collection of property entries. * @return a collection of property entries. */ public Collection<PropertyEntry> getPropertyEntries() { return properties.values(); } /** * Returns the property entry with the given name. * @param name the name of the property entry * @return the desired property entry or <code>null</code> */ public PropertyEntry getPropertyEntry(Name name) { return properties.get(name); } /** * Removes all property entries * * @param blobStore BLOB store from where to delete property values */ public void removeAllProperties(BLOBStore blobStore) { for (Name name : new HashSet<Name>(properties.keySet())) { removeProperty(name, blobStore); } } /** * Removes the proprty with the given name from this bundle. * * @param name the name of the property * @param blobStore BLOB store from where to delete the property value */ public void removeProperty(Name name, BLOBStore blobStore) { PropertyEntry pe = properties.remove(name); if (pe != null) { pe.destroy(blobStore); } } /** * Sets the shared set of this bundle. * @return the shared set of this bundle. */ public Set<NodeId> getSharedSet() { return sharedSet; } /** * Sets the shared set. * @param sharedSet shared set */ public void setSharedSet(Set<NodeId> sharedSet) { this.sharedSet = sharedSet; } /** * Returns the approx. size of this bundle. * @return the approx. size of this bundle. */ public long getSize() { // add some internal memory // + shallow size: 64 // + properties // + shallow size: 40 // + N * property entry: 218 + values + blobids // + childnodes // + shallow size: 24 // + N * 24 + 160 + 44 + name.length // + mixintypes names // + shallow size: 16 // + N * QNames // + nodetype name: // + shallow size: 24 // + string: 20 + length // + parentId: 160 // + id: 160 return 500 + size + 300 * (childNodeEntries.size() + properties.size() + 3); } /** * Sets the data size of this bundle * @param size the data size */ public void setSize(long size) { this.size = size; } //--------------------------------------------------------------< Object > @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append(id); builder.append("("); builder.append(parentId); builder.append(","); builder.append(nodeTypeName); for (Name mixin : mixinTypeNames) { builder.append(","); builder.append(mixin); } if (isReferenceable) { builder.append(",referenceable"); } builder.append(") = "); if (!sharedSet.isEmpty()) { builder.append(sharedSet); builder.append(" "); } builder.append(properties.values()); builder.append(" "); builder.append(childNodeEntries); return builder.toString(); } @Override public boolean equals(Object object) { if (object instanceof NodePropBundle) { NodePropBundle that = (NodePropBundle) object; return equalNullSafe(id, that.id) && equalNullSafe(parentId, that.parentId) && equalNullSafe(nodeTypeName, that.nodeTypeName) && equalNullSafe(mixinTypeNames, that.mixinTypeNames) && isReferenceable == that.isReferenceable && equalNullSafe(sharedSet, that.sharedSet) && equalNullSafe(properties, that.properties) && equalNullSafe(childNodeEntries, that.childNodeEntries); } return false; } private static boolean equalNullSafe(Object a, Object b) { if (a == null || b == null) { return a == b; } return a.equals(b); } //-----------------------------------------------------< ChildNodeEntry >--- /** * Helper class for a child node entry */ public static class ChildNodeEntry { /** * the name of the entry */ private final Name name; /** * the id of the entry */ private final NodeId id; /** * Creates a new entry with the given name and id * @param name the name * @param id the id */ public ChildNodeEntry(Name name, NodeId id) { this.name = name; this.id = id; } /** * Returns the name. * @return the name. */ public Name getName() { return name; } /** * Returns the id. * @return the id. */ public NodeId getId() { return id; } //----------------------------------------------------------< Object > public String toString() { return name + " => " + id; } public boolean equals(Object object) { if (object instanceof ChildNodeEntry) { ChildNodeEntry that = (ChildNodeEntry) object; return name.equals(that.name) && id.equals(that.id); } else { return false; } } } //------------------------------------------------------< PropertyEntry >--- /** * Helper class for a property enrty */ public static class PropertyEntry { /** * The property id */ private final PropertyId id; /** * the internal value */ private InternalValue[] values; /** * the property type */ private int type; /** * the multivalued flag */ private boolean multiValued; /** * the blob ids */ private String[] blobIds; /** * the mod count */ private short modCount; /** * Creates a new property entry with the given id. * @param id the id */ public PropertyEntry(PropertyId id) { this.id = id; } /** * Creates a new property entry and initialized it with values from * the given property state. * @param state the source property state. */ public PropertyEntry(PropertyState state) { this((PropertyId) state.getId()); values = state.getValues(); type = state.getType(); multiValued = state.isMultiValued(); if (!multiValued && values.length != 1) { throw new IllegalArgumentException("Non-multi-valued property with values.length " + values.length); } modCount = state.getModCount(); if (type == PropertyType.BINARY) { blobIds = new String[values.length]; } } /** * Returns the property id. * @return the property id. */ public PropertyId getId() { return id; } /** * Returns the property name * @return the property name */ public Name getName() { return id.getName(); } /** * Retruns the internal values * @return the internal values */ public InternalValue[] getValues() { return values; } /** * Sets the internal values. * @param values the internal values. */ public void setValues(InternalValue[] values) { this.values = values; } /** * Returns the type. * @return the type. */ public int getType() { return type; } /** * Sets the type * @param type the type */ public void setType(int type) { this.type = type; } /** * Returns the multivalued flag. * @return the multivalued flag. */ public boolean isMultiValued() { return multiValued; } /** * Sets the multivalued flag. * @param multiValued the multivalued flag */ public void setMultiValued(boolean multiValued) { this.multiValued = multiValued; } /** * Returns the n<sup>th</sup> blob id. * @param n the index of the blob id * @return the blob id */ public String getBlobId(int n) { return blobIds[n]; } /** * Sets the blob ids * @param blobIds the blobids */ public void setBlobIds(String[] blobIds) { this.blobIds = blobIds; } /** * Sets the n<sup>th</sup> blobid * @param blobId the blob id * @param n the index of the blob id */ public void setBlobId(String blobId, int n) { blobIds[n] = blobId; } /** * Returns the mod count. * @return the mod count. */ public short getModCount() { return modCount; } /** * Sets the mod count * @param modCount the mod count */ public void setModCount(short modCount) { this.modCount = modCount; } /** * Destroys this property state and deletes temporary blob file values. * @param blobStore the blobstore that will destroy the blobs */ private void destroy(BLOBStore blobStore) { // delete blobs if needed for (int i = 0; blobIds != null && i < blobIds.length; i++) { if (blobIds[i] != null) { try { blobStore.remove(blobIds[i]); log.debug("removed blob {}", blobIds[i]); } catch (Exception e) { log.error("Ignoring error while removing blob " + blobIds[i], e); } } } } //----------------------------------------------------------< Object > public String toString() { StringBuilder builder = new StringBuilder(); builder.append(id); builder.append("("); builder.append(PropertyType.nameFromValue(type)); if (multiValued) { builder.append(",multiple"); } builder.append(") = "); builder.append(Arrays.toString(values)); return builder.toString(); } public boolean equals(Object object) { if (object instanceof PropertyEntry) { PropertyEntry that = (PropertyEntry) object; return id.equals(that.id) && type == that.type && multiValued == that.multiValued && Arrays.equals(values, that.values); } else { return false; } } } }