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(),