public void process( MutableCachedNode node,
                             SaveContext context ) throws Exception {
            // Most nodes do not need any extra processing, so the first thing to do is figure out whether this
            // node has a primary type or mixin types that need extra processing. Unfortunately, this means we always have
            // to get the primary type and mixin types.
            final Name primaryType = node.getPrimaryType(cache);
            final Set<Name> mixinTypes = node.getMixinTypes(cache);
            if (nodeTypeCapabilities.isFullyDefinedType(primaryType, mixinTypes)) {
                // There is nothing to do for this node ...
                return;
            }
            if (!initialized) {
                // We're gonna need a few more objects, so create them now ...
                initialized = true;
                versionManager = versionManager();
                propertyFactory = propertyFactory();
                referenceFactory = referenceFactory();
            }
            AbstractJcrNode jcrNode = null;
            // -----------
            // mix:created
            // -----------
            boolean initializeVersionHistory = false;
            if (node.isNew()) {
                if (nodeTypeCapabilities.isCreated(primaryType, mixinTypes)) {
                    // Set the created by and time information if not changed explicitly
                    node.setPropertyIfUnchanged(cache, propertyFactory.create(JcrLexicon.CREATED, context.getTime()));
                    node.setPropertyIfUnchanged(cache, propertyFactory.create(JcrLexicon.CREATED_BY, context.getUserId()));
                }
                initializeVersionHistory = nodeTypeCapabilities.isVersionable(primaryType, mixinTypes);
            } else {
                // Changed nodes can only be made versionable if the primary type or mixins changed ...
                if (node.hasChangedPrimaryType() || !node.getAddedMixins(cache).isEmpty()) {
                    initializeVersionHistory = nodeTypeCapabilities.isVersionable(primaryType, mixinTypes);
                }
            }
            // ----------------
            // mix:lastModified
            // ----------------
            if (nodeTypeCapabilities.isLastModified(primaryType, mixinTypes)) {
                // Set the last modified by and time information if it has not been changed explicitly
                node.setPropertyIfUnchanged(cache, propertyFactory.create(JcrLexicon.LAST_MODIFIED, context.getTime()));
                node.setPropertyIfUnchanged(cache, propertyFactory.create(JcrLexicon.LAST_MODIFIED_BY, context.getUserId()));
            }
            // ---------------
            // mix:versionable
            // ---------------
            if (initializeVersionHistory) {
                // See if there is a version history for the node ...
                NodeKey versionableKey = node.getKey();
                if (!systemContent.hasVersionHistory(versionableKey)) {
                    // Initialize the version history ...
                    NodeKey historyKey = systemContent.versionHistoryNodeKeyFor(versionableKey);
                    NodeKey baseVersionKey = baseVersionKeys == null ? null : baseVersionKeys.get(versionableKey);
                    // it may happen during an import, that a node with version history & base version is assigned a new key and
                    // therefore
                    // the base version points to an existing version while no version history is found initially
                    boolean shouldCreateNewVersionHistory = true;
                    if (baseVersionKey != null) {
                        CachedNode baseVersionNode = systemCache.getNode(baseVersionKey);
                        if (baseVersionNode != null) {
                            historyKey = baseVersionNode.getParentKey(systemCache);
                            shouldCreateNewVersionHistory = (historyKey == null);
                        }
                    }
                    if (shouldCreateNewVersionHistory) {
                        // a new version history should be initialized
                        assert historyKey != null;
                        if (baseVersionKey == null) baseVersionKey = historyKey.withRandomId();
                        NodeKey originalVersionKey = originalVersionKeys != null ? originalVersionKeys.get(versionableKey) : null;
                        Path versionHistoryPath = versionManager.versionHistoryPathFor(versionableKey);
                        systemContent.initializeVersionStorage(versionableKey, historyKey, baseVersionKey, primaryType,
                                                               mixinTypes, versionHistoryPath, originalVersionKey,
                                                               context.getTime());
                    }
                    // Now update the node as if it's checked in (with the exception of the predecessors...)
                    Reference historyRef = referenceFactory.create(historyKey, true);
                    Reference baseVersionRef = referenceFactory.create(baseVersionKey, true);
                    node.setProperty(cache, propertyFactory.create(JcrLexicon.IS_CHECKED_OUT, Boolean.TRUE));
                    node.setReference(cache, propertyFactory.create(JcrLexicon.VERSION_HISTORY, historyRef), systemCache);
                    node.setReference(cache, propertyFactory.create(JcrLexicon.BASE_VERSION, baseVersionRef), systemCache);
                    // JSR 283 - 15.1
                    node.setReference(cache, propertyFactory.create(JcrLexicon.PREDECESSORS, new Object[] {baseVersionRef}),
                                      systemCache);
                } else {
                    // we're dealing with node which has a version history, check if there any versionable properties present
                    boolean hasVersioningProperties = node.hasProperty(JcrLexicon.IS_CHECKED_OUT, cache)
                                                      || node.hasProperty(JcrLexicon.VERSION_HISTORY, cache)
                                                      || node.hasProperty(JcrLexicon.BASE_VERSION, cache)
                                                      || node.hasProperty(JcrLexicon.PREDECESSORS, cache);
                    if (!hasVersioningProperties) {
                        // the node doesn't have any versionable properties, so this is a case of mix:versionable removed at some
                        // point and then re-added. If it had any versioning properties, we might've been dealing with something
                        // else
                        // e.g. a restore
                        // Re-link the versionable properties, based on the existing version history
                        node.setProperty(cache, propertyFactory.create(JcrLexicon.IS_CHECKED_OUT, Boolean.TRUE));
                        JcrVersionHistoryNode versionHistoryNode = versionManager().getVersionHistory(node(node.getKey(), null));
                        Reference historyRef = referenceFactory.create(versionHistoryNode.key(), true);
                        node.setReference(cache, propertyFactory.create(JcrLexicon.VERSION_HISTORY, historyRef), systemCache);
                        // set the base version to the last existing version
                        JcrVersionNode baseVersion = null;
                        for (VersionIterator versionIterator = versionHistoryNode.getAllVersions(); versionIterator.hasNext();) {
                            JcrVersionNode version = (JcrVersionNode)versionIterator.nextVersion();
                            if (baseVersion == null || version.isLinearSuccessorOf(baseVersion)) {
                                baseVersion = version;
                            }
                        }
                        assert baseVersion != null;
                        Reference baseVersionRef = referenceFactory.create(baseVersion.key(), true);
                        node.setReference(cache, propertyFactory.create(JcrLexicon.BASE_VERSION, baseVersionRef), systemCache);
                        // set the predecessors to the same list as the base version's predecessors
                        Version[] baseVersionPredecessors = baseVersion.getPredecessors();
                        Reference[] predecessors = new Reference[baseVersionPredecessors.length];
                        for (int i = 0; i < baseVersionPredecessors.length; i++) {
                            predecessors[i] = referenceFactory.create(((JcrVersionNode)baseVersionPredecessors[i]).key(), true);
                        }
                        node.setReference(cache, propertyFactory.create(JcrLexicon.PREDECESSORS, predecessors), systemCache);
                    }
                }
            }
            // -----------
            // nt:resource
            // -----------
            if (nodeTypeCapabilities.isNtResource(primaryType)) {
                // If there is no "jcr:mimeType" property ...
                if (!node.hasProperty(JcrLexicon.MIMETYPE, cache)) {
                    // Try to get the MIME type for the binary value ...
                    org.modeshape.jcr.value.Property dataProp = node.getProperty(JcrLexicon.DATA, cache);
                    if (dataProp != null) {
                        Object dataValue = dataProp.getFirstValue();
                        if (dataValue instanceof Binary) {
                            Binary binaryValue = (Binary)dataValue;
                            // Get the name of this node's parent ...
                            String fileName = null;
                            NodeKey parentKey = node.getParentKey(cache);
                            if (parentKey != null) {
                                CachedNode parent = cache.getNode(parentKey);
                                Name parentName = parent.getName(cache);
                                fileName = stringFactory().create(parentName);
                            }
                            String mimeType = binaryValue.getMimeType(fileName);
                            if (mimeType != null) {
                                node.setProperty(cache, propertyFactory.create(JcrLexicon.MIMETYPE, mimeType));
                            }
                        }
                    }
                }
            }
            // --------------------
            // Mandatory properties
            // --------------------
            // Some of the version history properties are mandatory, so we need to initialize the version history first ...
            Collection<JcrPropertyDefinition> mandatoryPropDefns = null;
            mandatoryPropDefns = nodeTypeCapabilities.getMandatoryPropertyDefinitions(primaryType, mixinTypes);
            if (!mandatoryPropDefns.isEmpty()) {
                // There is at least one mandatory property on this node, so go through all of the mandatory property
                // definitions and see if any do not correspond to existing properties ...
                for (JcrPropertyDefinition defn : mandatoryPropDefns) {
                    Name propName = defn.getInternalName();
                    if (!node.hasProperty(propName, cache)) {
                        // There is no mandatory property ...
                        if (defn.hasDefaultValues()) {
                            // This may or may not be auto-created; we don't care ...
                            if (jcrNode == null) jcrNode = node(node, (Type)null, null);
                            JcrValue[] defaultValues = defn.getDefaultValues();
                            if (defn.isMultiple()) {
                                jcrNode.setProperty(propName, defaultValues, defn.getRequiredType(), false);
                            } else {
                                // don't skip constraint checks or protected checks
                                jcrNode.setProperty(propName, defaultValues[0], false, false, false, false);
                            }
                        } else {
                            // There is no default for this mandatory property, so this is a constraint violation ...
                            String pName = defn.getName();
                            String typeName = defn.getDeclaringNodeType().getName();
                            String loc = readableLocation(node);
                            throw new ConstraintViolationException(JcrI18n.missingMandatoryProperty.text(pName, typeName, loc));
                        }
                    } else {
                        // There is a property with the same name as the mandatory property, so verify that the
                        // existing property does indeed use this property definition. Use the JCR property
                        // since it may already cache the property definition ID or will know how to find it ...
                        if (jcrNode == null) jcrNode = node(node, (Type)null, null);
                        AbstractJcrProperty jcrProperty = jcrNode.getProperty(propName);
                        PropertyDefinitionId defnId = jcrProperty.propertyDefinitionId();
                        if (defn.getId().equals(defnId)) {
                            // This existing property does use the auto-created definition ...
                            continue;
                        }
                        // The existing property does not use the property definition, but we can't auto-create the property
                        // because there is already an existing one with the same name. First see if we can forcibly
                        // recompute the property definition ...
                        jcrProperty.releasePropertyDefinitionId();
                        defnId = jcrProperty.propertyDefinitionId();
                        if (defn.getId().equals(defnId)) {
                            // This existing property does use the auto-created definition ...
                            continue;
                        }
                        // Still didn't match, so this is a constraint violation of the existing property ...
                        String pName = defn.getName();
                        String typeName = defn.getDeclaringNodeType().getName();
                        String loc = readableLocation(node);
                        I18n msg = JcrI18n.propertyNoLongerSatisfiesConstraints;
                        throw new ConstraintViolationException(msg.text(pName, loc, defn.getName(), typeName));
                    }
                }
            }
            // ---------------------
            // Mandatory child nodes
            // ---------------------
            Collection<JcrNodeDefinition> mandatoryChildDefns = null;
            mandatoryChildDefns = nodeTypeCapabilities.getMandatoryChildNodeDefinitions(primaryType, mixinTypes);
            if (!mandatoryChildDefns.isEmpty()) {
                Set<Name> childrenNames = new HashSet<Name>();
                for (ChildReference childRef : node.getChildReferences(cache())) {
                    childrenNames.add(childRef.getName());
                }
                for (JcrNodeDefinition defn : mandatoryChildDefns) {
                    Name childName = defn.getInternalName();
                    if (!childrenNames.contains(childName)) {
                        throw new ConstraintViolationException(
                                                               JcrI18n.propertyNoLongerSatisfiesConstraints.text(childName,
                                                                                                                 readableLocation(node),
                                                                                                                 defn.getName(),