All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.modeshape.jcr.SystemContent Maven / Gradle / Ivy

There is a newer version: 5.4.1.Final
Show newest version
/*
 * ModeShape (http://www.modeshape.org)
 *
 * Licensed 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.modeshape.jcr;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Value;
import javax.jcr.ValueFormatException;
import javax.jcr.lock.LockException;
import javax.jcr.nodetype.ConstraintViolationException;
import javax.jcr.nodetype.NodeDefinition;
import javax.jcr.nodetype.NodeDefinitionTemplate;
import javax.jcr.nodetype.NodeType;
import javax.jcr.nodetype.NodeTypeDefinition;
import javax.jcr.nodetype.NodeTypeTemplate;
import javax.jcr.nodetype.PropertyDefinition;
import javax.jcr.nodetype.PropertyDefinitionTemplate;
import javax.jcr.version.OnParentVersionAction;
import org.modeshape.common.SystemFailureException;
import org.modeshape.common.collection.Collections;
import org.modeshape.common.logging.Logger;
import org.modeshape.jcr.RepositoryLockManager.ModeShapeLock;
import org.modeshape.jcr.api.index.IndexColumnDefinition;
import org.modeshape.jcr.api.index.IndexDefinition;
import org.modeshape.jcr.api.index.IndexDefinition.IndexKind;
import org.modeshape.jcr.api.index.IndexDefinition.WorkspaceMatchRule;
import org.modeshape.jcr.api.value.DateTime;
import org.modeshape.jcr.cache.CachedNode;
import org.modeshape.jcr.cache.ChildReference;
import org.modeshape.jcr.cache.ChildReferences;
import org.modeshape.jcr.cache.MutableCachedNode;
import org.modeshape.jcr.cache.NodeKey;
import org.modeshape.jcr.cache.PropertyTypeUtil;
import org.modeshape.jcr.cache.SessionCache;
import org.modeshape.jcr.value.Name;
import org.modeshape.jcr.value.NameFactory;
import org.modeshape.jcr.value.NamespaceRegistry.Namespace;
import org.modeshape.jcr.value.Path;
import org.modeshape.jcr.value.Path.Segment;
import org.modeshape.jcr.value.Property;
import org.modeshape.jcr.value.PropertyFactory;
import org.modeshape.jcr.value.PropertyType;
import org.modeshape.jcr.value.Reference;
import org.modeshape.jcr.value.ReferenceFactory;
import org.modeshape.jcr.value.ValueFactories;
import org.modeshape.jcr.value.ValueFactory;
import org.modeshape.jcr.value.basic.BasicName;
import org.modeshape.jcr.value.basic.BasicNamespace;
import org.modeshape.jcr.value.basic.NodeKeyReference;

/**
 *
 */
public class SystemContent {

    public static final String GENERATED_PREFIX = "ns";
    private static final Name GENERATED_NAMESPACE_NODE_NAME = new BasicName("", GENERATED_PREFIX);
    protected static final Pattern GENERATED_PREFIX_PATTERN = Pattern.compile("ns(\\d{3})");

    private static final Set NON_EXTENDED_PROPERTIES = Collections.unmodifiableSet(JcrLexicon.PRIMARY_TYPE,
                                                                                         JcrLexicon.MIXIN_TYPES,
                                                                                         ModeShapeLexicon.KIND,
                                                                                         ModeShapeLexicon.WORKSPACES,
                                                                                         JcrLexicon.DESCRIPTION);

    private final SessionCache system;
    private NodeKey systemKey;
    private NodeKey nodeTypesKey;
    private NodeKey namespacesKey;
    private NodeKey locksKey;
    private NodeKey versionStorageKey;
    private NodeKey indexesKey;
    private final PropertyFactory propertyFactory;
    private final ValueFactory booleans;
    private final ValueFactory strings;
    private final NameFactory names;
    private final ReferenceFactory referenceFactory;
    private final javax.jcr.ValueFactory jcrValues;

    SystemContent( SessionCache systemCache ) {
        this.system = systemCache;
        ExecutionContext context = systemCache.getContext();
        this.propertyFactory = context.getPropertyFactory();
        ValueFactories factories = context.getValueFactories();
        this.booleans = factories.getBooleanFactory();
        this.strings = factories.getStringFactory();
        this.names = factories.getNameFactory();
        this.referenceFactory = factories.getReferenceFactory();
        this.jcrValues = new JcrValueFactory(context);
    }

    public void save() {
        system.save();
    }

    private final ExecutionContext context() {
        return system.getContext();
    }

    public final SessionCache cache() {
        return system;
    }

    public NodeKey systemKey() {
        if (systemKey == null) {
            // This is idempotent, so no need to lock
            CachedNode rootNode = system.getNode(system.getRootKey());
            ChildReference systemRef = rootNode.getChildReferences(system).getChild(JcrLexicon.SYSTEM);
            systemKey = systemRef.getKey();
        }
        return systemKey;
    }

    public NodeKey nodeTypesKey() {
        if (nodeTypesKey == null) {
            // This is idempotent, so no need to lock
            CachedNode systemNode = systemNode();
            ChildReference nodeTypesRef = systemNode.getChildReferences(system).getChild(JcrLexicon.NODE_TYPES);
            nodeTypesKey = nodeTypesRef.getKey();
        }
        return nodeTypesKey;
    }

    public NodeKey indexesKey() {
        if (indexesKey == null) {
            // This is idempotent, so no need to lock
            CachedNode systemNode = systemNode();
            ChildReference nodeTypesRef = systemNode.getChildReferences(system).getChild(ModeShapeLexicon.INDEXES);
            indexesKey = nodeTypesRef.getKey();
        }
        return indexesKey;
    }

    public NodeKey namespacesKey() {
        if (namespacesKey == null) {
            // This is idempotent, so no need to lock
            CachedNode systemNode = systemNode();
            ChildReference namespacesRef = systemNode.getChildReferences(system).getChild(ModeShapeLexicon.NAMESPACES);
            namespacesKey = namespacesRef.getKey();
        }
        return namespacesKey;
    }

    public NodeKey locksKey() {
        if (locksKey == null) {
            // This is idempotent, so no need to lock
            CachedNode systemNode = systemNode();
            ChildReference locksRef = systemNode.getChildReferences(system).getChild(ModeShapeLexicon.LOCKS);
            locksKey = locksRef.getKey();
        }
        return locksKey;
    }

    public NodeKey versionStorageKey() {
        if (versionStorageKey == null) {
            // This is idempotent, so no need to lock
            CachedNode systemNode = systemNode();
            ChildReference locksRef = systemNode.getChildReferences(system).getChild(JcrLexicon.VERSION_STORAGE);
            versionStorageKey = locksRef.getKey();
        }
        return versionStorageKey;
    }

    public CachedNode systemNode() {
        return system.getNode(systemKey());
    }

    public CachedNode nodeTypesNode() {
        return system.getNode(nodeTypesKey());
    }

    public CachedNode namespacesNode() {
        return system.getNode(namespacesKey());
    }

    public CachedNode indexesNode() {
        return system.getNode(indexesKey());
    }

    public CachedNode locksNode() {
        return system.getNode(locksKey());
    }

    public CachedNode versionStorageNode() {
        return system.getNode(versionStorageKey());
    }

    public MutableCachedNode mutableNodeTypesNode() {
        return system.mutable(nodeTypesKey());
    }

    public MutableCachedNode mutableNamespacesNode() {
        return system.mutable(namespacesKey());
    }

    public MutableCachedNode mutableIndexesNode() {
        return system.mutable(indexesKey());
    }

    public MutableCachedNode mutableLocksNode() {
        return system.mutable(locksKey());
    }

    public MutableCachedNode mutableVersionStorageNode() {
        return system.mutable(versionStorageKey());
    }

    public MutableCachedNode mutableSystemNode() {
        return system.mutable(systemKey());
    }

    /**
     * Stores the node types in the system area under /jcr:system/jcr:nodeTypes.
     * 

* For each node type, a node is created with primary type of nt:nodeType and a name that corresponds to the node * type's name. All other properties and child nodes for the newly created node are added in a manner consistent with the * guidance provided in section 6.7.22 of the JCR 1.0 specification and section 4.7.24 of the JCR 2.0 specification where * possible. *

* * @param nodeTypes the node types to write out; may not be null * @param updateExisting a boolean flag denoting whether the new node type definition should be overwrite an existing node * type definition */ public void store( Iterable nodeTypes, boolean updateExisting ) { MutableCachedNode nodeTypesNode = mutableNodeTypesNode(); Set names = new HashSet(); Set keys = new HashSet(); for (JcrNodeType nodeType : nodeTypes) { if (!names.add(nodeType.getInternalName())) { Logger.getLogger(getClass()).debug("Found duplicate node type: " + nodeType); } if (!keys.add(nodeType.key())) { Logger.getLogger(getClass()).debug("Found duplicate key: " + nodeType); } } for (JcrNodeType nodeType : nodeTypes) { store(nodeType, nodeTypesNode, updateExisting); } } /** * Stores the node type in the system area under /jcr:system/jcr:nodeTypes. *

* The stored content will contain a node with a primary type of nt:nodeType and a name that corresponds to the * node type's name. All other properties and child nodes for the newly created node are added in a manner consistent with the * guidance provided in section 6.7.22 of the JCR 1.0 specification and section 4.7.24 of the JCR 2.0 specification where * possible. *

* * @param nodeType the node type to write; may not be null * @param updateExisting a boolean flag denoting whether the new node type definition should be overwrite an existing node * type definition */ public void store( JcrNodeType nodeType, boolean updateExisting ) { MutableCachedNode nodeTypesNode = mutableNodeTypesNode(); store(nodeType, nodeTypesNode, updateExisting); } /** * Projects the node types onto the provided graph under the location of parentOfTypeNodes. The operations needed * to create the node (and any child nodes or properties) will be added to the batch specified in batch. * * @param nodeType the node type to be projected * @param nodeTypes the parent node under which each node type should be saved; may not be null * @param updateExisting a boolean flag denoting whether the new node type definition should be overwrite an existing node * type definition */ private void store( JcrNodeType nodeType, MutableCachedNode nodeTypes, boolean updateExisting ) { assert nodeType != null; assert system != null; assert nodeTypes != null; Name name = nodeType.getInternalName(); final NodeKey key = nodeType.key(); MutableCachedNode nodeTypeNode = null; Set existingChildKeys = null; if (nodeTypes.getChildReferences(system).hasChild(key)) { // The node already exists ... if (!updateExisting) return; nodeTypeNode = system.mutable(key); // We'll need to delete any existing child that isn't there anymore ... existingChildKeys = new HashSet(); for (ChildReference childRef : nodeTypeNode.getChildReferences(system)) { existingChildKeys.add(childRef.getKey()); } } // Define the properties for this node type ... NodeType[] supertypes = nodeType.getDeclaredSupertypes(); List supertypeNames = new ArrayList(supertypes.length); for (int i = 0; i < supertypes.length; i++) { supertypeNames.add(((JcrNodeType)supertypes[i]).getInternalName()); } List properties = new ArrayList(); properties.add(propertyFactory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.NODE_TYPE)); properties.add(propertyFactory.create(JcrLexicon.IS_MIXIN, nodeType.isMixin())); properties.add(propertyFactory.create(JcrLexicon.IS_ABSTRACT, nodeType.isAbstract())); properties.add(propertyFactory.create(JcrLexicon.IS_QUERYABLE, nodeType.isQueryable())); if (nodeType.getPrimaryItemName() != null) { properties.add(propertyFactory.create(JcrLexicon.PRIMARY_ITEM_NAME, nodeType.getPrimaryItemName())); } properties.add(propertyFactory.create(JcrLexicon.NODE_TYPE_NAME, nodeType.getName())); properties.add(propertyFactory.create(JcrLexicon.HAS_ORDERABLE_CHILD_NODES, nodeType.hasOrderableChildNodes())); properties.add(propertyFactory.create(JcrLexicon.SUPERTYPES, supertypeNames)); // Now make or adjust the node for the node type ... if (nodeTypeNode != null) { // Update the properties ... nodeTypeNode.setProperties(system, properties); // make sure each new supertype of the existing node is present *before* the existing node in the parent nodeTypes // this because node type validation is a top-down process, expecting the parents before the children for (NodeType superType : supertypes) { CachedNode superTypeNode = system.getNode(((JcrNodeType)superType).key()); if (superTypeNode instanceof MutableCachedNode && ((MutableCachedNode)superTypeNode).isNew()) { nodeTypes.reorderChild(system, superTypeNode.getKey(), nodeTypeNode.getKey()); } } } else { // We have to create the node type node ... nodeTypeNode = nodeTypes.createChild(system, key, name, properties); } // And the property definitions ... for (JcrPropertyDefinition defn : nodeType.getDeclaredPropertyDefinitions()) { store(nodeTypeNode, defn); if (existingChildKeys != null) existingChildKeys.remove(defn.key()); } // And the child node definitions ... for (JcrNodeDefinition defn : nodeType.getDeclaredChildNodeDefinitions()) { store(nodeTypeNode, defn); if (existingChildKeys != null) existingChildKeys.remove(defn.key()); } // Remove any children that weren't represented by a property definition or child node definition ... if (existingChildKeys != null && !existingChildKeys.isEmpty()) { for (NodeKey childKey : existingChildKeys) { // Remove the child from the parent, then destrot it ... nodeTypeNode.removeChild(system, childKey); system.destroy(childKey); } } } /** * Projects a single property definition onto the provided graph under the location of nodeTypePath. The * operations needed to create the property definition and any of its properties will be added to the batch specified in * batch. *

* All node creation is performed through the graph layer. If the primary type of the node at nodeTypePath does * not contain a residual definition that allows child nodes of type nt:propertyDefinition, this method creates * nodes for which the JCR layer cannot determine the corresponding node definition. This WILL corrupt the graph from a JCR * standpoint and make it unusable through the ModeShape JCR layer. *

* * @param nodeTypeNode the parent node under which each property definition should be saved; may not be null * @param propertyDef the property definition to be projected */ private void store( MutableCachedNode nodeTypeNode, JcrPropertyDefinition propertyDef ) { // Find an existing node for this property definition ... final NodeKey key = propertyDef.key(); final Name name = propertyDef.getInternalName(); MutableCachedNode propDefnNode = null; if (!nodeTypeNode.isNew()) { if (nodeTypeNode.getChildReferences(system).hasChild(key)) { // The node already exists ... propDefnNode = system.mutable(key); } } List properties = new ArrayList(); properties.add(propertyFactory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.PROPERTY_DEFINITION)); if (!JcrNodeType.RESIDUAL_ITEM_NAME.equals(propertyDef.getName())) { properties.add(propertyFactory.create(JcrLexicon.NAME, name)); } properties.add(propertyFactory.create(JcrLexicon.AUTO_CREATED, propertyDef.isAutoCreated())); properties.add(propertyFactory.create(JcrLexicon.MANDATORY, propertyDef.isMandatory())); properties.add(propertyFactory.create(JcrLexicon.MULTIPLE, propertyDef.isMultiple())); properties.add(propertyFactory.create(JcrLexicon.PROTECTED, propertyDef.isProtected())); properties.add(propertyFactory.create(JcrLexicon.ON_PARENT_VERSION, OnParentVersionAction.nameFromValue(propertyDef.getOnParentVersion()))); properties.add(propertyFactory.create(JcrLexicon.REQUIRED_TYPE, org.modeshape.jcr.api.PropertyType.nameFromValue(propertyDef.getRequiredType()) .toUpperCase())); List symbols = new ArrayList(); for (String value : propertyDef.getAvailableQueryOperators()) { if (value != null) symbols.add(value); } properties.add(propertyFactory.create(JcrLexicon.AVAILABLE_QUERY_OPERATORS, symbols)); Value[] defaultValues = propertyDef.getDefaultValues(); if (defaultValues != null && defaultValues.length > 0) { String[] defaultsAsString = new String[defaultValues.length]; for (int i = 0; i < defaultValues.length; i++) { try { defaultsAsString[i] = defaultValues[i].getString(); } catch (RepositoryException re) { // Really shouldn't get here as all values are convertible to string throw new IllegalStateException(re); } } properties.add(propertyFactory.create(JcrLexicon.DEFAULT_VALUES, defaultsAsString)); } String[] valueConstraints = propertyDef.getValueConstraints(); if (valueConstraints.length > 0) { properties.add(propertyFactory.create(JcrLexicon.VALUE_CONSTRAINTS, valueConstraints)); } // Now either update the existing node or create a new node .. if (propDefnNode != null) { // Update the properties ... propDefnNode.setProperties(system, properties); } else { // We have to create the node type node ... propDefnNode = nodeTypeNode.createChild(system, key, name, properties); } } /** * Projects a single child node definition onto the provided graph under the location of nodeTypePath. The * operations needed to create the child node definition and any of its properties will be added to the batch specified in * batch. *

* All node creation is performed through the graph layer. If the primary type of the node at nodeTypePath does * not contain a residual definition that allows child nodes of type nt:childNodeDefinition, this method creates * nodes for which the JCR layer cannot determine the corresponding node definition. This WILL corrupt the graph from a JCR * standpoint and make it unusable through the ModeShape JCR layer. *

* * @param nodeTypeNode the parent node under which each property definition should be saved; may not be null * @param childNodeDef the child node definition to be projected */ private void store( MutableCachedNode nodeTypeNode, JcrNodeDefinition childNodeDef ) { // Find an existing node for this property definition ... final NodeKey key = childNodeDef.key(); final Name name = childNodeDef.getInternalName(); MutableCachedNode nodeDefnNode = null; if (!nodeTypeNode.isNew()) { if (nodeTypeNode.getChildReferences(system).hasChild(key)) { // The node already exists ... nodeDefnNode = system.mutable(key); } } List props = new ArrayList(); props.add(propertyFactory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.CHILD_NODE_DEFINITION)); if (!JcrNodeType.RESIDUAL_ITEM_NAME.equals(childNodeDef.getName())) { props.add(propertyFactory.create(JcrLexicon.NAME, name)); } if (childNodeDef.defaultPrimaryTypeName() != null) { props.add(propertyFactory.create(JcrLexicon.DEFAULT_PRIMARY_TYPE, childNodeDef.defaultPrimaryTypeName())); } props.add(propertyFactory.create(JcrLexicon.REQUIRED_PRIMARY_TYPES, childNodeDef.requiredPrimaryTypeNames())); props.add(propertyFactory.create(JcrLexicon.SAME_NAME_SIBLINGS, childNodeDef.allowsSameNameSiblings())); props.add(propertyFactory.create(JcrLexicon.ON_PARENT_VERSION, OnParentVersionAction.nameFromValue(childNodeDef.getOnParentVersion()))); props.add(propertyFactory.create(JcrLexicon.AUTO_CREATED, childNodeDef.isAutoCreated())); props.add(propertyFactory.create(JcrLexicon.MANDATORY, childNodeDef.isMandatory())); props.add(propertyFactory.create(JcrLexicon.PROTECTED, childNodeDef.isProtected())); // Now either update the existing node or create a new node .. if (nodeDefnNode != null) { // Update the properties ... nodeDefnNode.setProperties(system, props); } else { // We have to create the node type node ... nodeDefnNode = nodeTypeNode.createChild(system, key, name, props); } } public IndexDefinition readIndexDefinition( CachedNode indexDefn, Name providerName ) { String name = strings.create(indexDefn.getName(system)); String desc = strings.create(first(indexDefn, JcrLexicon.DESCRIPTION)); String providerNameStr = strings.create(providerName); String kindStr = strings.create(first(indexDefn, ModeShapeLexicon.KIND)); String workspacesRule = strings.create(first(indexDefn, ModeShapeLexicon.WORKSPACES)); boolean synchronous = booleans.create(first(indexDefn, ModeShapeLexicon.SYNCHRONOUS)); IndexKind kind = IndexKind.valueOf(kindStr); String nodeTypeName = strings.create(names.create(first(indexDefn, ModeShapeLexicon.NODE_TYPE_NAME))); Map extendedProps = new HashMap<>(); for (Iterator props = indexDefn.getProperties(system); props.hasNext();) { Property prop = props.next(); if (NON_EXTENDED_PROPERTIES.contains(prop.getName())) continue; if (prop.isSingle()) { extendedProps.put(strings.create(prop.getName()), prop); } else if (prop.isMultiple()) { extendedProps.put(strings.create(prop.getName()), prop); } } Collection columnDefns = new LinkedList<>(); for (ChildReference ref : indexDefn.getChildReferences(system)) { CachedNode indexColumnDefn = system.getNode(ref); IndexColumnDefinition defn = readIndexColumnDefinition(indexColumnDefn); columnDefns.add(defn); } WorkspaceMatchRule rule = RepositoryIndexDefinition.workspaceMatchRule(workspacesRule); return new RepositoryIndexDefinition(name, providerNameStr, kind, nodeTypeName, columnDefns, extendedProps, desc, synchronous, true, rule); } public IndexColumnDefinition readIndexColumnDefinition( CachedNode indexColumnDefn ) { String propertyName = strings.create(names.create(first(indexColumnDefn, ModeShapeLexicon.PROPERTY_NAME))); String columnTypeName = strings.create(first(indexColumnDefn, ModeShapeLexicon.COLUMN_TYPE_NAME)); PropertyType columnType = PropertyType.valueFor(columnTypeName); return new RepositoryIndexColumnDefinition(propertyName, columnType.jcrType()); } /** * Read from system storage the index definitions. If the names of the providers are providers, then the resulting index * definitions will each be {@link IndexDefinition#isEnabled() enabled} only if the definition's named provider is in the * supplied set; otherwise, the definition will be marked as disabled. * * @param providerNames the names of the providers that should be used to determine which index definitions are * {@link IndexDefinition#isEnabled() enabled}; may be null if not known and all index definitions will be * {@link IndexDefinition#isEnabled() enabled} * @return the index definitions as read from the system storage */ public List readAllIndexDefinitions( Set providerNames ) { CachedNode indexes = indexesNode(); List defns = new ArrayList<>(); for (ChildReference ref : indexes.getChildReferences(system)) { CachedNode provider = system.getNode(ref); Name providerName = provider.getName(system); for (ChildReference indexRef : provider.getChildReferences(system)) { CachedNode indexDefn = system.getNode(indexRef); IndexDefinition defn = readIndexDefinition(indexDefn, providerName); if (providerNames.contains(defn.getProviderName())) { defns.add(defn); } else { // There is no provider by this name, so mark it as not enabled ... defn = RepositoryIndexDefinition.createFrom(defn, false); } } } return defns; } private final NodeKey nodeKey( NodeKey prototype, IndexDefinition defn ) { return prototype.withId("/jcr:system/mode:indexes/" + defn.getProviderName() + "/" + defn.getName()); } private final NodeKey nodeKey( NodeKey indexDefnKey, IndexColumnDefinition defn ) { String id = strings.create(strings.create(defn.getPropertyName())); return indexDefnKey.withId(indexDefnKey.getIdentifier() + id); } private final NodeKey nodeKey( NodeKey prototype, String providerName ) { return prototype.withId("/jcr:system/mode:indexes/" + providerName); } public void remove( IndexDefinition indexDefn ) { assert indexDefn != null; assert system != null; MutableCachedNode indexes = mutableIndexesNode(); final NodeKey providerKey = nodeKey(indexes.getKey(), indexDefn.getProviderName()); if (indexes.getChildReferences(system).hasChild(providerKey)) { // Find the provider node ... MutableCachedNode providerNode = system.mutable(providerKey); // And remove the index defn from the provider ... final NodeKey key = nodeKey(providerNode.getKey(), indexDefn); providerNode.removeChild(system, key); system.destroy(key); // If there are no more children under the provider, remove it, too... if (providerNode.getChildReferences(system).isEmpty()) { indexes.removeChild(system, providerKey); system.destroy(providerKey); } } } public void store( IndexDefinition indexDefn, boolean updateExisting ) { MutableCachedNode indexesNode = mutableIndexesNode(); store(indexDefn, indexesNode, updateExisting); } private void store( IndexDefinition indexDefn, MutableCachedNode indexes, boolean updateExisting ) { assert indexDefn != null; assert system != null; assert indexes != null; assert indexDefn.getName() != null; // First find or create the provider node ... MutableCachedNode providerNode = null; final NodeKey providerKey = nodeKey(indexes.getKey(), indexDefn.getProviderName()); if (indexes.getChildReferences(system).hasChild(providerKey)) { // The node already exists ... providerNode = system.mutable(providerKey); } else { // Create the new provider node ... Property primaryType = propertyFactory.create(JcrLexicon.PRIMARY_TYPE, ModeShapeLexicon.INDEX_PROVIDER); Name providerName = names.create(indexDefn.getProviderName()); providerNode = indexes.createChild(system, providerKey, providerName, primaryType); } assert providerNode != null; Name name = names.create(indexDefn.getName()); final NodeKey key = nodeKey(indexes.getKey(), indexDefn); MutableCachedNode indexNode = null; Set existingChildKeys = null; if (providerNode.getChildReferences(system).hasChild(key)) { // The node already exists ... if (!updateExisting) return; indexNode = system.mutable(key); // We'll need to delete any existing column that isn't there anymore ... existingChildKeys = new HashSet(); for (ChildReference childRef : indexNode.getChildReferences(system)) { existingChildKeys.add(childRef.getKey()); } } // Define the properties for this index definition ... List properties = new ArrayList(); // Add the extended properties first, in case the standard ones overwrite them ... for (Map.Entry entry : indexDefn.getIndexProperties().entrySet()) { Property prop = createProperty(entry.getKey(), entry.getValue()); if (prop != null) properties.add(prop); } // Now do the standard properties ... properties.add(propertyFactory.create(JcrLexicon.PRIMARY_TYPE, ModeShapeLexicon.INDEX)); properties.add(propertyFactory.create(JcrLexicon.DESCRIPTION, indexDefn.getDescription())); properties.add(propertyFactory.create(ModeShapeLexicon.KIND, indexDefn.getKind().name())); properties.add(propertyFactory.create(ModeShapeLexicon.NODE_TYPE_NAME, indexDefn.getNodeTypeName())); properties.add(propertyFactory.create(ModeShapeLexicon.SYNCHRONOUS, indexDefn.isSynchronous())); // Now make or adjust the node for the index definition ... if (indexNode != null) { // Update the properties ... indexNode.setProperties(system, properties); } else { // We have to create the index definition node ... indexNode = providerNode.createChild(system, key, name, properties); } // And the column definitions ... for (IndexColumnDefinition columnDefn : indexDefn) { NodeKey columnDefnKey = store(indexNode, columnDefn); if (existingChildKeys != null) existingChildKeys.remove(columnDefnKey); } // Remove any column defns that weren't represented in the index definition ... if (existingChildKeys != null && !existingChildKeys.isEmpty()) { for (NodeKey childKey : existingChildKeys) { // Remove the child from the parent, then destroy it ... indexNode.removeChild(system, childKey); system.destroy(childKey); } } } private Property createProperty( String name, Object valueOrValues ) { Name propName = names.create(name); Property prop = null; if (valueOrValues instanceof Object[]) { Object[] values = (Object[])valueOrValues; PropertyType type = propertyTypeOf(values); prop = propertyFactory.create(propName, type, values); } else if (valueOrValues == null) { prop = propertyFactory.create(propName, new Object[] {}); } else { PropertyType type = PropertyType.discoverType(valueOrValues); prop = propertyFactory.create(propName, type, valueOrValues); } return prop; } private PropertyType propertyTypeOf( Object[] values ) { if (values != null) { for (Object value : values) { if (value != null) { PropertyType type = PropertyType.discoverType(value); if (type != PropertyType.OBJECT) return type; } } } return PropertyType.OBJECT; } private NodeKey store( MutableCachedNode indexDefn, IndexColumnDefinition columnDefn ) { // Find an existing node for this column definition ... final NodeKey key = nodeKey(indexDefn.getKey(), columnDefn); final Name name = ModeShapeLexicon.INDEX_COLUMN; MutableCachedNode columnDefnNode = null; if (!indexDefn.isNew()) { if (indexDefn.getChildReferences(system).hasChild(key)) { // The node already exists ... columnDefnNode = system.mutable(key); } } String propTypeName = org.modeshape.jcr.api.PropertyType.nameFromValue(columnDefn.getColumnType()).toUpperCase(); List props = new ArrayList(); props.add(propertyFactory.create(JcrLexicon.PRIMARY_TYPE, ModeShapeLexicon.INDEX_COLUMN)); props.add(propertyFactory.create(ModeShapeLexicon.PROPERTY_NAME, columnDefn.getPropertyName())); props.add(propertyFactory.create(ModeShapeLexicon.COLUMN_TYPE_NAME, propTypeName)); // Now either update the existing node or create a new node .. if (columnDefnNode != null) { // Update the properties ... columnDefnNode.setProperties(system, props); } else { // We have to create the node type node ... columnDefnNode = indexDefn.createChild(system, key, name, props); } return key; } /** * Read from system storage the node type definitions with the supplied names. * * @param nodeTypesToRefresh * @return the node types as read from the system storage */ public List readNodeTypes( Set nodeTypesToRefresh ) { return readAllNodeTypes(); } /** * Read from system storage all of the node type definitions. * * @return the node types as read from the system storage */ public List readAllNodeTypes() { CachedNode nodeTypes = nodeTypesNode(); List defns = new ArrayList(); for (ChildReference ref : nodeTypes.getChildReferences(system)) { CachedNode nodeType = system.getNode(ref); defns.add(readNodeTypeDefinition(nodeType)); } return defns; } @SuppressWarnings( "unchecked" ) public NodeTypeDefinition readNodeTypeDefinition( CachedNode nodeType ) { try { NodeTypeTemplate defn = new JcrNodeTypeTemplate(context()); defn.setMixin(booleans.create(first(nodeType, JcrLexicon.IS_MIXIN))); defn.setAbstract(booleans.create(first(nodeType, JcrLexicon.IS_ABSTRACT))); defn.setQueryable(booleans.create(first(nodeType, JcrLexicon.IS_QUERYABLE))); defn.setOrderableChildNodes(booleans.create(first(nodeType, JcrLexicon.HAS_ORDERABLE_CHILD_NODES))); defn.setName(strings.create(first(nodeType, JcrLexicon.NODE_TYPE_NAME))); defn.setPrimaryItemName(strings.create(first(nodeType, JcrLexicon.PRIMARY_ITEM_NAME))); Property supertypes = nodeType.getProperty(JcrLexicon.SUPERTYPES, system); if (supertypes != null && !supertypes.isEmpty()) { String[] supertypeNames = new String[supertypes.size()]; int i = 0; for (Object name : supertypes) { supertypeNames[i++] = strings.create(name); } defn.setDeclaredSuperTypeNames(supertypeNames); } // Read the children ... for (ChildReference ref : nodeType.getChildReferences(system)) { CachedNode itemDefn = system.getNode(ref); Name primaryType = names.create(first(itemDefn, JcrLexicon.PRIMARY_TYPE)); if (JcrNtLexicon.PROPERTY_DEFINITION.equals(primaryType)) { PropertyDefinition propDefn = readPropertyDefinition(itemDefn); assert propDefn != null; defn.getPropertyDefinitionTemplates().add(propDefn); } else if (JcrNtLexicon.CHILD_NODE_DEFINITION.equals(primaryType)) { NodeDefinition childDefn = readChildNodeDefinition(itemDefn); assert childDefn != null; defn.getNodeDefinitionTemplates().add(childDefn); } } return defn; } catch (ConstraintViolationException e) { // this is never expected throw new SystemFailureException(e); } } protected PropertyDefinition readPropertyDefinition( CachedNode propDefn ) throws ConstraintViolationException { PropertyDefinitionTemplate defn = new JcrPropertyDefinitionTemplate(context()); defn.setName(strings.create(first(propDefn, JcrLexicon.NAME, JcrNodeType.RESIDUAL_ITEM_NAME))); defn.setAutoCreated(booleans.create(first(propDefn, JcrLexicon.AUTO_CREATED))); defn.setMandatory(booleans.create(first(propDefn, JcrLexicon.MANDATORY))); defn.setMultiple(booleans.create(first(propDefn, JcrLexicon.MULTIPLE))); defn.setProtected(booleans.create(first(propDefn, JcrLexicon.PROTECTED))); defn.setOnParentVersion(OnParentVersionAction.valueFromName(strings.create(first(propDefn, JcrLexicon.ON_PARENT_VERSION)))); defn.setRequiredType(propertyType(first(propDefn, JcrLexicon.REQUIRED_TYPE))); Property queryOps = propDefn.getProperty(JcrLexicon.AVAILABLE_QUERY_OPERATORS, system); if (queryOps != null && !queryOps.isEmpty()) { String[] queryOperators = new String[queryOps.size()]; int i = 0; for (Object op : queryOps) { queryOperators[i++] = strings.create(op); } defn.setAvailableQueryOperators(queryOperators); } Property defaultValues = propDefn.getProperty(JcrLexicon.DEFAULT_VALUES, system); if (defaultValues != null && !defaultValues.isEmpty()) { Value[] values = new Value[defaultValues.size()]; int i = 0; for (Object value : defaultValues) { org.modeshape.jcr.value.PropertyType modeType = org.modeshape.jcr.value.PropertyType.discoverType(value); int jcrType = PropertyTypeUtil.jcrPropertyTypeFor(modeType); String strValue = strings.create(value); try { values[i++] = jcrValues.createValue(strValue, jcrType); } catch (ValueFormatException err) { values[i++] = jcrValues.createValue(strValue); } i++; } defn.setDefaultValues(values); } Property constraints = propDefn.getProperty(JcrLexicon.VALUE_CONSTRAINTS, system); if (constraints != null && !constraints.isEmpty()) { String[] values = new String[constraints.size()]; int i = 0; for (Object value : constraints) { values[i++] = strings.create(value); } defn.setValueConstraints(values); } return defn; } protected NodeDefinition readChildNodeDefinition( CachedNode childDefn ) throws ConstraintViolationException { NodeDefinitionTemplate defn = new JcrNodeDefinitionTemplate(context()); defn.setName(strings.create(first(childDefn, JcrLexicon.NAME, JcrNodeType.RESIDUAL_ITEM_NAME))); defn.setAutoCreated(booleans.create(first(childDefn, JcrLexicon.AUTO_CREATED))); defn.setMandatory(booleans.create(first(childDefn, JcrLexicon.MANDATORY))); defn.setSameNameSiblings(booleans.create(first(childDefn, JcrLexicon.SAME_NAME_SIBLINGS))); defn.setProtected(booleans.create(first(childDefn, JcrLexicon.PROTECTED))); defn.setOnParentVersion(OnParentVersionAction.valueFromName(strings.create(first(childDefn, JcrLexicon.ON_PARENT_VERSION)))); String defaultPrimaryType = strings.create(first(childDefn, JcrLexicon.DEFAULT_PRIMARY_TYPE)); if (defaultPrimaryType != null) defn.setDefaultPrimaryTypeName(defaultPrimaryType); Property requiredPrimaryTypes = childDefn.getProperty(JcrLexicon.REQUIRED_PRIMARY_TYPES, system); if (requiredPrimaryTypes != null && !requiredPrimaryTypes.isEmpty()) { String[] values = new String[requiredPrimaryTypes.size()]; int i = 0; for (Object op : requiredPrimaryTypes) { values[i++] = strings.create(op); } defn.setRequiredPrimaryTypeNames(values); } return defn; } protected final int propertyType( Object value ) { org.modeshape.jcr.value.PropertyType type = org.modeshape.jcr.value.PropertyType.valueFor(strings.create(value) .toLowerCase()); return PropertyTypeUtil.jcrPropertyTypeFor(type); } protected final Iterable all( CachedNode node, Name propertyName ) { return node.getProperty(propertyName, system); } protected final Object first( CachedNode node, Name propertyName ) { return first(node, propertyName, null); } protected final Object first( CachedNode node, Name propertyName, Object defaultValue ) { Property property = node.getProperty(propertyName, system); return property != null ? property.getFirstValue() : defaultValue; } public Collection readAllNamespaces() { CachedNode namespaces = namespacesNode(); List results = new ArrayList(); // Add in the empty namespace ... results.add(new BasicNamespace("", "")); // Now read in the persisted namespaces ... for (ChildReference ref : namespaces.getChildReferences(system)) { CachedNode namespace = system.getNode(ref); String prefix = prefixFor(ref.getSegment()); String uri = strings.create(first(namespace, ModeShapeLexicon.URI)); results.add(new BasicNamespace(prefix, uri)); } return results; } private String prefixFor( Segment segment ) { Name name = segment.getName(); if (ModeShapeLexicon.NAMESPACE.equals(name)) { // This is the empty prefix ... return ""; } String localName = name.getLocalName(); int index = segment.getIndex(); return prefixFor(localName, index); } private String prefixFor( String name, int counter ) { if (counter == 1 && !GENERATED_PREFIX.equals(name)) return name; if (counter < 10) { return name + "00" + counter; } if (counter < 100) { return name + "0" + counter; } assert counter < 1000; return name + counter; } private Name nameForPrefix( String prefix ) { if (prefix.length() == 0) return ModeShapeLexicon.NAMESPACE; Matcher matcher = GENERATED_PREFIX_PATTERN.matcher(prefix); if (matcher.matches()) { prefix = GENERATED_PREFIX; } return names.create(prefix); } public Set registerNamespaces( Map newUrisByPrefix ) { Set removedPrefixes = new HashSet(); MutableCachedNode namespaces = mutableNamespacesNode(); ChildReferences childRefs = namespaces.getChildReferences(system); // Find any existing namespace nodes for the new namespace URIs ... for (Map.Entry newNamespaceEntry : newUrisByPrefix.entrySet()) { String newPrefix = newNamespaceEntry.getKey().trim(); String newUri = newNamespaceEntry.getValue().trim(); // Verify that the prefix is not already used ... Name newPrefixName = nameForPrefix(newPrefix); ChildReference ref = childRefs.getChild(newPrefixName); if (ref != null) { // There's an existing node with the same prefix/name ... CachedNode existingNode = system.getNode(ref); String existingUri = strings.create(existingNode.getProperty(ModeShapeLexicon.URI, system).getFirstValue()); if (newUri.equals(existingUri)) { // The URI also matches, so nothing to do ... continue; } // Otherwise, the prefix was bound to another URI, so this means we're taking an existing prefix already bound // to one URI and assigning it to another URI. Per the JavaDoc for javax.jcr.Namespace#register(String,String) // the old URI is to be unregistered -- meaning we should delete it ... namespaces.removeChild(system, ref.getKey()); system.destroy(ref.getKey()); continue; } // Look for an existing namespace node that uses the same URI ... NodeKey key = keyForNamespaceUri(newUri); CachedNode existingNode = system.getNode(key); if (existingNode != null) { // Get the prefix for the existing namespace node ... Segment segment = existingNode.getSegment(system); String existingPrefix = prefixFor(segment); if (GENERATED_NAMESPACE_NODE_NAME.equals(segment.getName()) || !existingPrefix.equals(newPrefix)) { // The prefix but was not used elsewhere, so we know we can just change it ... namespaces.renameChild(system, key, names.create(newPrefix)); removedPrefixes.add(existingPrefix); } } else if (newUri.length() > 0) { // register the new prefix Name name = names.create(newPrefix); List props = new ArrayList(3); props.add(propertyFactory.create(ModeShapeLexicon.URI, newUri)); props.add(propertyFactory.create(JcrLexicon.PRIMARY_TYPE, ModeShapeLexicon.NAMESPACE)); props.add(propertyFactory.create(ModeShapeLexicon.GENERATED, booleans.create(false))); namespaces.createChild(system, key, name, props); } } return removedPrefixes; } public String readNamespacePrefix( String namespaceUri, boolean generateIfMissing ) { NodeKey key = keyForNamespaceUri(namespaceUri); CachedNode nsNode = system.getNode(key); if (nsNode != null) { // There's an existing node, so just read the prefix (e.g., the name) ... Segment segment = nsNode.getSegment(system); return prefixFor(segment); } if (!generateIfMissing) return null; // Create a new namespace node that uses this URI ... MutableCachedNode mutableNamespaces = mutableNamespacesNode(); List props = new ArrayList(3); props.add(propertyFactory.create(ModeShapeLexicon.URI, namespaceUri)); props.add(propertyFactory.create(ModeShapeLexicon.GENERATED, booleans.create(true))); props.add(propertyFactory.create(JcrLexicon.PRIMARY_TYPE, ModeShapeLexicon.NAMESPACE)); MutableCachedNode newNsNode = mutableNamespaces.createChild(system, key, GENERATED_NAMESPACE_NODE_NAME, props); return prefixFor(newNsNode.getSegment(system)); } protected boolean unregisterNamespace( String namespaceUri ) { MutableCachedNode namespaces = mutableNamespacesNode(); NodeKey key = keyForNamespaceUri(namespaceUri); CachedNode nsNode = system.getNode(key); if (nsNode != null) { namespaces.removeChild(system, key); system.destroy(key); return true; } return false; } protected void unregisterNodeTypes( JcrNodeType... nodeTypes ) { MutableCachedNode nodeTypesNode = mutableNodeTypesNode(); for (JcrNodeType nodeType : nodeTypes) { NodeKey nodeTypeKey = nodeType.key(); nodeTypesNode.removeChild(system, nodeTypeKey); system.destroy(nodeTypeKey); } } protected final NodeKey keyForNamespaceUri( String namespaceUri ) { return namespacesKey().withId("mode:namespaces-" + namespaceUri); } void storeLock( ModeShapeLock lock ) { MutableCachedNode locksNode = mutableLocksNode(); Name name = names.create(lock.getLockToken()); List properties = new ArrayList(); properties.add(propertyFactory.create(JcrLexicon.PRIMARY_TYPE, ModeShapeLexicon.LOCK)); properties.add(propertyFactory.create(JcrLexicon.LOCK_OWNER, lock.getLockOwner())); properties.add(propertyFactory.create(JcrLexicon.LOCK_IS_DEEP, lock.isDeep())); properties.add(propertyFactory.create(ModeShapeLexicon.WORKSPACE, lock.getWorkspaceName())); properties.add(propertyFactory.create(ModeShapeLexicon.LOCK_TOKEN, lock.getLockToken())); properties.add(propertyFactory.create(ModeShapeLexicon.IS_SESSION_SCOPED, lock.isSessionScoped())); // Locks are always created by sessions and then held by them unless explicitly removed later ... properties.add(propertyFactory.create(ModeShapeLexicon.IS_HELD_BY_SESSION, true)); properties.add(propertyFactory.create(ModeShapeLexicon.LOCKING_SESSION, lock.getLockingSessionId())); properties.add(propertyFactory.create(ModeShapeLexicon.EXPIRATION_DATE, lock.getExpiryTime())); locksNode.createChild(system, lock.getLockKey(), name, properties); } void removeLock( ModeShapeLock lock ) { MutableCachedNode locksNode = mutableLocksNode(); NodeKey lockKey = lock.getLockKey(); locksNode.removeChild(system, lockKey); system.destroy(lockKey); } /** * Updates the underlying repository directly (i.e., outside the scope of the {@link Session}) to mark the token for the given * lock as being held (or not held) by some {@link Session}. Note that this method does not identify which (if any) * session holds the token for the lock, just that some session holds the token for the lock. * * @param lockToken the lock token for which the "held" status should be modified; may not be null * @param value the new value * @return true if the lock "held" status was successfully changed to the desired value, or false otherwise * @throws LockException if there is no such lock with the supplied token */ boolean changeLockHeldBySession( String lockToken, boolean value ) throws LockException { CachedNode locksNode = locksNode(); ChildReferences childRefs = locksNode.getChildReferences(system); Name name = names.create(lockToken); ChildReference ref = childRefs.getChild(name); if (ref == null) { throw new LockException(JcrI18n.invalidLockToken.text(lockToken)); } MutableCachedNode lockNode = system.mutable(ref.getKey()); boolean isHeld = booleans.create(first(lockNode, ModeShapeLexicon.IS_HELD_BY_SESSION, false)); if (isHeld && value) { // The lock is already held by a session ... return false; } lockNode.setProperty(system, propertyFactory.create(ModeShapeLexicon.IS_HELD_BY_SESSION, value)); return true; } public NodeKey versionHistoryNodeKeyFor( NodeKey versionableNodeKey ) { return systemKey().withId(versionableNodeKey.getIdentifier()); } public boolean hasVersionHistory( NodeKey versionableNodeKey ) { return cache().getNode(versionHistoryNodeKeyFor(versionableNodeKey)) != null; } /** * Create and initialize the version history structure for a versionable node with the supplied UUID. This method assumes that * the version history node does not exist. *

* Given a NodeKey for a node that has an identifier part of "fae2b929-c5ef-4ce5-9fa1-514779ca0ae3", the SHA-1 hash of this * identifier part is "b46dde8905f76361779339fa3ccacc4f47664255". The path to the version history for this node is as follows: * *

     *  + jcr:system
     *    + jcr:versionStorage   {jcr:primaryType = mode:versionStorage}
     *      + b4   {jcr:primaryType = mode:versionHistoryFolder}
     *        + 6d   {jcr:primaryType = mode:versionHistoryFolder}
     *          + de   {jcr:primaryType = mode:versionHistoryFolder}
     *            + 298905f76361779339fa3ccacc4f47664255   {jcr:primaryType = nt:versionHistory}
     *              + jcr:versionLabels  {jcr:primaryType = nt:versionLabels}
     *              + jcr:rootVersion  {jcr:primaryType = nt:version}
     *                - jcr:uuid = ...
     *                - jcr:created = ...
     *                + jcr:frozenNode  {jcr:primaryType = nt:frozenNode}
     *                  - jcr:frozenUuid
     *                  - jcr:frozenPrimaryType
     *                  - jcr:frozenMixinTypes
     * 
* * Note that the path between "/jcr:system/jcr:versionStorage" and the "nt:versionHistory" node is shown as being * {@link JcrVersionManager.HiearchicalPathAlgorithm hiearchical}. * * @param versionableNodeKey the identifier of the versionable node for which the history is to be created; may not be null * @param versionHistoryKey the key to the version history node; may not be null * @param versionKey the key to be used for the initial version; may be null if the key should be generated * @param primaryTypeName the name of the primary type of the versionable node; may not be null * @param mixinTypeNames the names of the mixin types for the versionable node; may be null or empty * @param versionHistoryPath the path of the version history node; may not be null * @param originalVersionKey the key of the original node from which the new versionable node was copied; may be null * @param now the current date time; may not be null * @return the history node; never null */ protected MutableCachedNode initializeVersionStorage( NodeKey versionableNodeKey, NodeKey versionHistoryKey, NodeKey versionKey, Name primaryTypeName, Set mixinTypeNames, Path versionHistoryPath, NodeKey originalVersionKey, DateTime now ) { assert versionHistoryPath != null; assert versionHistoryPath.size() == 6; CachedNode node = versionStorageNode(); MutableCachedNode mutable = null; // Find the parent of the version history node by walking the path and creating any missing intermediate folders ... Path parentPathInStorage = versionHistoryPath.getParent().subpath(2); Property primaryType = null; for (Segment segment : parentPathInStorage) { ChildReferences childRefs = node.getChildReferences(system); ChildReference ref = childRefs.getChild(segment); if (ref != null) { // Look up the child node ... node = system.getNode(ref); } else { // Create the intermediate node ... MutableCachedNode mutableNode = system.mutable(node.getKey()); NodeKey key = systemKey().withRandomId(); if (primaryType == null) { primaryType = propertyFactory.create(JcrLexicon.PRIMARY_TYPE, ModeShapeLexicon.VERSION_HISTORY_FOLDER); } mutable = mutableNode.createChild(system, key, segment.getName(), primaryType); node = mutable; } } // See if the version history exists ... MutableCachedNode historyParent = mutable != null ? mutable : system.mutable(node.getKey()); // Now create the version history node itself ... List historyProps = new ArrayList(); historyProps.add(propertyFactory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.VERSION_HISTORY)); historyProps.add(propertyFactory.create(JcrLexicon.VERSIONABLE_UUID, versionableNodeKey.getIdentifier())); historyProps.add(propertyFactory.create(JcrLexicon.UUID, versionHistoryKey.getIdentifier())); if (originalVersionKey != null) { // the tck expects this to be a reference, so that getNode works on it historyProps.add(propertyFactory.create(JcrLexicon.COPIED_FROM, org.modeshape.jcr.value.PropertyType.WEAKREFERENCE, referenceFactory.create(originalVersionKey, true))); } Name historyName = versionHistoryPath.getLastSegment().getName(); MutableCachedNode history = historyParent.createChild(system, versionHistoryKey, historyName, historyProps); // Now create the 'nt:versionLabels' child node ... Property labelProp = propertyFactory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.VERSION_LABELS); MutableCachedNode labels = history.createChild(system, null, JcrLexicon.VERSION_LABELS, labelProp); assert labels != null; // And create the 'nt:rootVersion' child node ... NodeKey rootVersionKey = versionKey != null ? versionKey : systemKey().withRandomId(); List rootProps = new ArrayList(); rootProps.add(propertyFactory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.VERSION)); rootProps.add(propertyFactory.create(JcrLexicon.CREATED, now)); rootProps.add(propertyFactory.create(JcrLexicon.UUID, rootVersionKey.getIdentifier())); MutableCachedNode rootVersion = history.createChild(system, rootVersionKey, JcrLexicon.ROOT_VERSION, rootProps); // And create the 'nt:rootVersion/nt:frozenNode' child node ... NodeKey frozenNodeKey = rootVersion.getKey().withRandomId(); List frozenProps = new ArrayList(); frozenProps.add(propertyFactory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.FROZEN_NODE)); frozenProps.add(propertyFactory.create(JcrLexicon.FROZEN_UUID, versionableNodeKey.getIdentifier())); frozenProps.add(propertyFactory.create(JcrLexicon.FROZEN_PRIMARY_TYPE, primaryTypeName)); frozenProps.add(propertyFactory.create(JcrLexicon.UUID, frozenNodeKey)); if (mixinTypeNames != null && !mixinTypeNames.isEmpty()) { frozenProps.add(propertyFactory.create(JcrLexicon.FROZEN_MIXIN_TYPES, mixinTypeNames)); } MutableCachedNode frozenNode = rootVersion.createChild(system, frozenNodeKey, JcrLexicon.FROZEN_NODE, frozenProps); assert frozenNode != null; return history; } /** * The method efficiently updates the JCR version history and storage with a new version of a node being checked in. However, * it does not update the versionable node with the "mix:versionable" properties. *

* Note that this method will initialize the version history for the node if the version history does not already exist. *

*

* The names of the different versions has changed since 2.x, and now follows the same convention and algorithm as used in the * reference implementation. See * {@link #nextNameForVersionNode(org.modeshape.jcr.value.Property, org.modeshape.jcr.cache.ChildReferences)} for details. *

* * @param versionableNode the versionable node for which a new version is to be created in the node's version history; may not * be null * @param cacheForVersionableNode the cache used to access the versionable node and any descendants; may not be null * @param versionHistoryPath the path of the version history node; may not be null * @param originalVersionKey the key of the original node from which the new versionable node was copied; may be null * @param versionableProperties the versionable node's properties that should be record in the version history (on the frozen * node); may be null or empty * @param now the current date time; may not be null * @param frozenNodeOutput the reference that should be set upon successful completion to the frozen node created under the * new version; may not be null * @return the version node in the version history; never null */ public MutableCachedNode recordNewVersion( CachedNode versionableNode, SessionCache cacheForVersionableNode, Path versionHistoryPath, NodeKey originalVersionKey, Collection versionableProperties, DateTime now, AtomicReference frozenNodeOutput ) { assert versionHistoryPath != null; assert versionHistoryPath.size() == 6; // Get the information from this node ... NodeKey versionableNodeKey = versionableNode.getKey(); Name primaryTypeName = versionableNode.getPrimaryType(cacheForVersionableNode); Set mixinTypeNames = versionableNode.getMixinTypes(cacheForVersionableNode); NodeKey versionHistoryKey = versionHistoryNodeKeyFor(versionableNodeKey); // Find the existing version history for this node (if it exists) ... Name versionName = null; MutableCachedNode historyNode = system.mutable(versionHistoryKey); Property predecessors = null; NodeKey versionKey = versionHistoryKey.withRandomId(); if (historyNode == null) { // Initialize the version history ... historyNode = initializeVersionStorage(versionableNodeKey, versionHistoryKey, null, primaryTypeName, mixinTypeNames, versionHistoryPath, originalVersionKey, now); // Overwrite the predecessor's property ... NodeKey rootVersionKey = historyNode.getChildReferences(system).getChild(JcrLexicon.ROOT_VERSION).getKey(); Reference rootVersionRef = referenceFactory.create(rootVersionKey, true); predecessors = propertyFactory.create(JcrLexicon.PREDECESSORS, new Object[] {rootVersionRef}); versionName = names.create("1.0"); } else { ChildReferences historyChildren = historyNode.getChildReferences(system); predecessors = versionableNode.getProperty(JcrLexicon.PREDECESSORS, cacheForVersionableNode); versionName = nextNameForVersionNode(predecessors, historyChildren); } // Create a 'nt:version' node under the version history node ... List props = new ArrayList(); props.add(propertyFactory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.VERSION)); props.add(predecessors); props.add(propertyFactory.create(JcrLexicon.CREATED, now)); props.add(propertyFactory.create(JcrLexicon.UUID, versionKey.getIdentifier())); MutableCachedNode versionNode = historyNode.createChild(system, versionKey, versionName, props); // Create a 'nt:frozenNode' node under the 'nt:version' node ... NodeKey frozenNodeKey = systemKey().withRandomId(); props = new ArrayList(); props.add(propertyFactory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.FROZEN_NODE)); props.add(propertyFactory.create(JcrLexicon.FROZEN_UUID, versionableNodeKey.getIdentifier())); props.add(propertyFactory.create(JcrLexicon.FROZEN_PRIMARY_TYPE, primaryTypeName)); props.add(propertyFactory.create(JcrLexicon.FROZEN_MIXIN_TYPES, mixinTypeNames)); props.add(propertyFactory.create(JcrLexicon.UUID, frozenNodeKey)); if (versionableProperties != null) props.addAll(versionableProperties); MutableCachedNode frozenNode = versionNode.createChild(system, frozenNodeKey, JcrLexicon.FROZEN_NODE, props); assert frozenNode != null; frozenNodeOutput.set(frozenNode); // Now update the predecessor nodes to have the new version node be included as one of their successors ... Property successors = null; final Set successorReferences = new HashSet(); for (Object value : predecessors) { NodeKey predecessorKey = ((NodeKeyReference)value).getNodeKey(); CachedNode predecessor = system.getNode(predecessorKey); // Look up the 'jcr:successors' property on the predecessor ... successors = predecessor.getProperty(JcrLexicon.SUCCESSORS, system); if (successors != null) { // There were already successors, so we need to add our new version node the list ... successorReferences.clear(); for (Object successorValue : successors) { NodeKey successorKey = ((NodeKeyReference)successorValue).getNodeKey(); successorReferences.add(referenceFactory.create(successorKey, true)); } } // Now add the uuid of the versionable node ... successorReferences.add(referenceFactory.create(versionKey, true)); successors = propertyFactory.create(JcrLexicon.SUCCESSORS, org.modeshape.jcr.value.PropertyType.REFERENCE, successorReferences); system.mutable(predecessorKey).setProperty(system, successors); } // Return the newly-created version node ... return versionNode; } /** * Compute the name for the next version node in the given history. Note that the naming convention has changed since 2.x, and * now follows the same convention and algorithm as used in the reference implementation. See * org.apache.jackrabbit.core.version.InternalVersionManagerBase.calculateCheckinVersionName(...) for the original. *

* The basic rules are as follows: *

    *
  • first the predecessor version with the shortest name is searched. *
  • if that predecessor version is the root version, the new version gets the name "{number of successors}+1" + ".0" *
  • if that predecessor version has no successor, the last digit of it's version number is incremented. *
  • if that predecessor version has successors but the incremented name does not exist, that name is used. *
  • otherwise a ".0" is added to the name until a non conflicting name is found. *
      * Example Graph: * *
           * jcr:rootVersion
           *  |     |
           * 1.0   2.0
           *  |
           * 1.1
           *  |
           * 1.2 ---\  ------\
           *  |      \        \
           * 1.3   1.2.0   1.2.0.0
           *  |      |
           * 1.4   1.2.1 ----\
           *  |      |        \
           * 1.5   1.2.2   1.2.1.0
           *  |      |        |
           * 1.6     |     1.2.1.1
           *  |------/
           * 1.7
           * 
      * *

      * * @param predecessors the 'jcr:predecessors' property; may not be null * @param historyChildren the child references under the version history for the node * @return the next name */ protected Name nextNameForVersionNode( Property predecessors, ChildReferences historyChildren ) { String proposedName = null; CachedNode versionNode = null; // Try to find the versions in the history that are considered predecessors ... if (predecessors != null) { for (Object predecessor : predecessors) { if (predecessor == null) continue; NodeKey key = ((NodeKeyReference)predecessor).getNodeKey(); CachedNode predecessorNode = system.getNode(key); Name predecessorName = predecessorNode.getName(system); if (proposedName == null || predecessorName.getLocalName().length() < proposedName.length()) { proposedName = predecessorName.getLocalName(); versionNode = predecessorNode; } } } if (proposedName == null) { proposedName = "1.0"; Name versionName = names.create(proposedName); if (historyChildren.getChild(versionName) == null) return versionName; // Otherwise use the root version ... versionNode = system.getNode(historyChildren.getChild(JcrLexicon.ROOT_VERSION)); } assert versionNode != null; // Now make sure the name is not used ... int index = proposedName.lastIndexOf('.'); if (index > 0) { proposedName = proposedName.substring(0, index + 1) + (Integer.parseInt(proposedName.substring(index + 1)) + 1); Name versionName = names.create(proposedName); // excludes the trailing '.' while (historyChildren.getChild(versionName) != null) { proposedName = proposedName + ".0"; versionName = names.create(proposedName); } return versionName; } // Get the number of successors of the version Property successors = versionNode.getProperty(JcrLexicon.SUCCESSORS, system); String baseName = successors != null ? Integer.toString(successors.size() + 1) : "1"; return names.create(baseName + ".0"); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy