Index: modeshape-graph/src/main/java/org/modeshape/graph/session/GraphSession.java
===================================================================
--- modeshape-graph/src/main/java/org/modeshape/graph/session/GraphSession.java (revision 2528)
+++ modeshape-graph/src/main/java/org/modeshape/graph/session/GraphSession.java (working copy)
@@ -187,6 +187,10 @@ public class GraphSession {
return context;
}
+ protected Graph.Batch operations() {
+ return operations;
+ }
+
final String readable( Name name ) {
return name.getString(context.getNamespaceRegistry());
}
Index: modeshape-jcr/src/main/java/org/modeshape/jcr/JcrContentHandler.java
===================================================================
--- modeshape-jcr/src/main/java/org/modeshape/jcr/JcrContentHandler.java (revision 2528)
+++ modeshape-jcr/src/main/java/org/modeshape/jcr/JcrContentHandler.java (working copy)
@@ -57,6 +57,7 @@ import org.modeshape.graph.property.NameFactory;
import org.modeshape.graph.property.NamespaceRegistry;
import org.modeshape.graph.property.Path;
import org.modeshape.graph.property.PathFactory;
+import org.modeshape.jcr.SessionCache.NodeEditor;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
@@ -94,6 +95,8 @@ class JcrContentHandler extends DefaultHandler {
private final javax.jcr.NamespaceRegistry jcrNamespaceRegistry;
private final SaveMode saveMode;
protected final int uuidBehavior;
+ protected final boolean retentionInfoRetained;
+ protected final boolean lifecycleInfoRetained;
protected final String primaryTypeName;
protected final String mixinTypesName;
@@ -102,6 +105,7 @@ class JcrContentHandler extends DefaultHandler {
private AbstractJcrNode currentNode;
private ContentHandler delegate;
protected final List refPropsRequiringConstraintValidation = new LinkedList();
+ protected final List nodesForPostProcessing = new LinkedList();
private SessionCache cache;
@@ -113,7 +117,9 @@ class JcrContentHandler extends DefaultHandler {
JcrContentHandler( JcrSession session,
Path parentPath,
int uuidBehavior,
- SaveMode saveMode ) throws PathNotFoundException, RepositoryException {
+ SaveMode saveMode,
+ boolean retentionInfoRetained,
+ boolean lifecycleInfoRetained ) throws PathNotFoundException, RepositoryException {
assert session != null;
assert parentPath != null;
assert uuidBehavior == ImportUUIDBehavior.IMPORT_UUID_CREATE_NEW
@@ -127,6 +133,8 @@ class JcrContentHandler extends DefaultHandler {
this.pathFactory = context.getValueFactories().getPathFactory();
this.stringFactory = context.getValueFactories().getStringFactory();
this.uuidBehavior = uuidBehavior;
+ this.retentionInfoRetained = retentionInfoRetained;
+ this.lifecycleInfoRetained = lifecycleInfoRetained;
this.saveMode = saveMode;
switch (this.saveMode) {
@@ -200,24 +208,128 @@ class JcrContentHandler extends DefaultHandler {
return cache;
}
+ protected void postProcessNodes() throws SAXException {
+ JcrVersionManager versions = null;
+ try {
+ for (AbstractJcrNode node : nodesForPostProcessing) {
+ NodeEditor editor = null;
+ // ---------------
+ // mix:versionable
+ // ---------------
+ if (node.isNodeType(JcrMixLexicon.VERSIONABLE)) {
+
+ // At this point, we're not recovering version history information nor properly updating
+ // existing versionable nodes. Instead, we're just checking whether all of the versionable
+ // properties are valid. If they are, then the history must have been imported (or the import
+ // replaced a versionable node that already had history).
+ boolean validHistory = isValidReference(node, JcrLexicon.VERSION_HISTORY, false);
+ boolean validBaseVersion = isValidReference(node, JcrLexicon.BASE_VERSION, false);
+ boolean validPredecessors = isValidReference(node, JcrLexicon.PREDECESSORS, false);
+ if (validHistory) {
+ // There is a valid version history already ...
+ if (!validBaseVersion || !validPredecessors) {
+ // The imported base version is not valid anymore, so set it to the base version from the history ...
+ if (versions == null) versions = node.versionManager();
+ JcrVersionHistoryNode history = versions.getVersionHistory(node);
+ UUID baseVersion = history.getRootVersion().uuid();
+ JcrValue newValue = node.valueFrom(PropertyType.REFERENCE, baseVersion.toString());
+ JcrValue[] newValues = new JcrValue[] {newValue};
+ editor = node.editor();
+ editor.setProperty(JcrLexicon.BASE_VERSION, newValue, true, false);
+ editor.setProperty(JcrLexicon.PREDECESSORS, newValues, PropertyType.REFERENCE, false);
+ } // otherwise the base version and predecessors are valid, too
+ } else {
+ // The version history reference is not valid, so remove all mix:versionable properties
+ // (they'll be re-added during save by SessionCache) ...
+ editor = node.editor();
+ editor.removeProperty(JcrLexicon.IS_CHECKED_OUT);
+ editor.removeProperty(JcrLexicon.VERSION_HISTORY);
+ editor.removeProperty(JcrLexicon.BASE_VERSION);
+ editor.removeProperty(JcrLexicon.PREDECESSORS);
+ editor.removeProperty(JcrLexicon.MERGE_FAILED);
+ editor.removeProperty(JcrLexicon.ACTIVITY);
+ editor.removeProperty(JcrLexicon.CONFIGURATION);
+ }
+ }
+
+ // ---------------
+ // mix:lockable
+ // ---------------
+ if (node.isNodeType(JcrMixLexicon.LOCKABLE) && node.isLocked()) {
+ // Nodes should not be locked upon import ...
+ node.unlock();
+ }
+
+ // ---------------
+ // mix:lifecycle
+ // ---------------
+ if (node.isNodeType(JcrMixLexicon.LIFECYCLE)) {
+ if (lifecycleInfoRetained && !isValidReference(node, JcrLexicon.LIFECYCLE_POLICY, false)) {
+ // The 'jcr:lifecyclePolicy' REFERENCE values is not valid or does not reference an existing node,
+ // so the 'jcr:lifecyclePolicy' and 'jcr:currentLifecycleState' properties should be removed...
+ if (editor == null) editor = node.editor();
+ assert editor != null;
+ editor.removeProperty(JcrLexicon.LIFECYCLE_POLICY);
+ editor.removeProperty(JcrLexicon.CURRENT_LIFECYCLE_STATE);
+ }
+ }
+
+ // --------------------
+ // mix:managedRetention
+ // --------------------
+ if (node.isNodeType(JcrMixLexicon.MANAGED_RETENTION)) {
+ if (retentionInfoRetained && !isValidReference(node, JcrLexicon.RETENTION_POLICY, false)) {
+ // The 'jcr:retentionPolicy' REFERENCE values is not valid or does not reference an existing node,
+ // so the 'jcr:retentionPolicy', 'jcr:hold' and 'jcr:isDeep' properties should be removed ...
+ if (editor == null) editor = node.editor();
+ assert editor != null;
+ editor.removeProperty(JcrLexicon.HOLD);
+ editor.removeProperty(JcrLexicon.IS_DEEP);
+ editor.removeProperty(JcrLexicon.RETENTION_POLICY);
+ }
+
+ }
+ }
+ } catch (RepositoryException e) {
+ throw new EnclosingSAXException(e);
+ }
+ }
+
+ protected boolean isValidReference( AbstractJcrNode node,
+ Name propertyName,
+ boolean returnValueIfNoProperty ) throws RepositoryException {
+ AbstractJcrProperty property = node.getProperty(propertyName);
+ return property == null ? returnValueIfNoProperty : isValidReference(property);
+ }
+
+ protected boolean isValidReference( AbstractJcrProperty property ) throws RepositoryException {
+ JcrPropertyDefinition defn = property.getDefinition();
+ if (defn == null) return false;
+ if (property.isMultiple()) {
+ for (Value value : property.getValues()) {
+ if (!defn.canCastToTypeAndSatisfyConstraints(value)) {
+ // We know it's not valid, so return ...
+ return false;
+ }
+ }
+ // All values appeared to be valid ...
+ return true;
+ }
+ // Just a single value ...
+ return defn.canCastToTypeAndSatisfyConstraints(property.getValue());
+ }
+
protected void validateReferenceConstraints() throws SAXException {
if (refPropsRequiringConstraintValidation.isEmpty()) return;
try {
for (AbstractJcrProperty refProp : refPropsRequiringConstraintValidation) {
- JcrPropertyDefinition defn = refProp.getDefinition();
- if (refProp.isMultiple()) {
- for (Value value : refProp.getValues()) {
- if (!defn.canCastToTypeAndSatisfyConstraints(value)) {
- String name = stringFor(refProp.name());
- throw new ConstraintViolationException(JcrI18n.constraintViolatedOnReference.text(name, defn));
- }
- }
- } else {
- Value value = refProp.getValue();
- if (!defn.canCastToTypeAndSatisfyConstraints(value)) {
- String name = stringFor(refProp.name());
- throw new ConstraintViolationException(JcrI18n.constraintViolatedOnReference.text(name, defn));
- }
+ // Make sure the reference is still there ...
+ if (refProp.propertyInfo() == null) continue;
+ // It is still there, so validate it ...
+ if (!isValidReference(refProp)) {
+ JcrPropertyDefinition defn = refProp.getDefinition();
+ String name = stringFor(refProp.name());
+ throw new ConstraintViolationException(JcrI18n.constraintViolatedOnReference.text(name, defn));
}
}
} catch (RepositoryException e) {
@@ -245,6 +357,7 @@ class JcrContentHandler extends DefaultHandler {
*/
@Override
public void endDocument() throws SAXException {
+ postProcessNodes();
validateReferenceConstraints();
if (saveMode == SaveMode.WORKSPACE) {
try {
@@ -397,18 +510,47 @@ class JcrContentHandler extends DefaultHandler {
}
/**
- * The set of properties that should be skipped on import. Currently, this list includes all properties of "mix:lockable",
- * since upon import no node should be locked.
+ * Some nodes need additional post-processing upon import, and this set of property names is used to come up with the nodes
+ * that may need to be post-processed.
+ *
+ * Really, the nodes that need to be post-processed are best found using the node types of each node. However, that is more
+ * expensive to compute. Thus, we'll collect the candidate nodes that are candidates for post-processing, then in the
+ * post-processing we can more effectively and efficiently use the node types.
+ *
+ *
+ * Currently, we want to post-process nodes that contain repository-level semantics. In other words, nodes that are of the
+ * following node types:
+ *
+ * mix:versionable
+ * mix:lockable
+ * mix:lifecycle
+ * mix:managedRetention
+ *
+ * The mix:simpleVersionable
would normally also be included here, except that the jcr:isCheckedOut
+ * property is a boolean value that doesn't need any particular post-processing.
+ *
+ *
+ * Some of these node types has a mandatory property, so the names of these mandatory properties are used to quickly determine
+ * candidates for post-processing. In cases where there is no mandatory property, then the set of all properties for that node
+ * type are included:
+ *
+ * mix:versionable
--> jcr:baseVersion
(mandatory)
+ * mix:lockable
--> jcr:lockOwner
and jcr:lockIsDeep
+ * mix:lifecycle
--> jcr:lifecyclePolicy
and jcr:currentLifecycleState
+ * mix:managedRetention
--> jcr:hold
, jcr:isDeep
, and
+ * jcr:retentionPolicy
+ *
+ *
*/
- protected static final Set PROPERTIES_TO_SKIP_ON_IMPORT = Collections.unmodifiableSet(JcrLexicon.LOCK_IS_DEEP,
- JcrLexicon.LOCK_OWNER);
-
- // JcrLexicon.VERSION_HISTORY,
- // JcrLexicon.PREDECESSORS,
- // JcrLexicon.MERGE_FAILED,
- // JcrLexicon.BASE_VERSION,
- // JcrLexicon.IS_CHECKED_OUT,
- // );
+ protected static final Set PROPERTIES_FOR_POST_PROCESSING = Collections.unmodifiableSet(
+ /* 'mix:lockable' has two optional properties */
+ JcrLexicon.LOCK_IS_DEEP, JcrLexicon.LOCK_OWNER,
+ /* 'mix:versionable' has several mandatory properties, but we only need to check one */
+ JcrLexicon.BASE_VERSION,
+ /* 'mix:lifecycle' has two optional properties */
+ JcrLexicon.LIFECYCLE_POLICY, JcrLexicon.CURRENT_LIFECYCLE_STATE,
+ /* 'mix:managedRetention' has three optional properties */
+ JcrLexicon.HOLD, JcrLexicon.IS_DEEP, JcrLexicon.RETENTION_POLICY);
protected class BasicNodeHandler extends NodeHandler {
private final Map> properties;
@@ -416,6 +558,7 @@ class JcrContentHandler extends DefaultHandler {
private NodeHandler parentHandler;
private AbstractJcrNode node;
private final int uuidBehavior;
+ private boolean postProcessed = false;
protected BasicNodeHandler( Name name,
NodeHandler parentHandler,
@@ -453,11 +596,6 @@ class JcrContentHandler extends DefaultHandler {
return parentHandler;
}
- protected boolean shouldNotImportProperty( Name propertyName ) {
- return false;
- // return PROPERTIES_TO_SKIP_ON_IMPORT.contains(propertyName);
- }
-
@Override
public void addPropertyValue( Name name,
String value,
@@ -468,7 +606,6 @@ class JcrContentHandler extends DefaultHandler {
if (JcrLexicon.PRIMARY_TYPE.equals(name)) return;
if (JcrLexicon.MIXIN_TYPES.equals(name)) return;
if (JcrLexicon.UUID.equals(name)) return;
- if (shouldNotImportProperty(name)) return; // ignore some properties
// The node was already created, so set the property using the editor ...
node.editor().setProperty(name, (JcrValue)valueFor(value, propertyType));
@@ -499,6 +636,9 @@ class JcrContentHandler extends DefaultHandler {
}
}
}
+ if (!postProcessed && PROPERTIES_FOR_POST_PROCESSING.contains(name)) {
+ postProcessed = true;
+ }
} catch (IOException ioe) {
throw new EnclosingSAXException(ioe);
} catch (RepositoryException re) {
@@ -604,11 +744,6 @@ class JcrContentHandler extends DefaultHandler {
continue;
}
- // Should we ignore this property?
- if (shouldNotImportProperty(propertyName)) {
- continue;
- }
-
List values = entry.getValue();
if (values.size() == 1) {
@@ -629,6 +764,12 @@ class JcrContentHandler extends DefaultHandler {
}
node = child;
+
+ if (postProcessed) {
+ // This node needs to be post-processed ...
+ nodesForPostProcessing.add(node);
+ }
+
} catch (RepositoryException re) {
throw new EnclosingSAXException(re);
}
Index: modeshape-jcr/src/main/java/org/modeshape/jcr/JcrLexicon.java
===================================================================
--- modeshape-jcr/src/main/java/org/modeshape/jcr/JcrLexicon.java (revision 2528)
+++ modeshape-jcr/src/main/java/org/modeshape/jcr/JcrLexicon.java (working copy)
@@ -33,10 +33,13 @@ import org.modeshape.graph.property.basic.BasicName;
@Immutable
public class JcrLexicon extends org.modeshape.graph.JcrLexicon {
+ public static final Name ACTIVITY = new BasicName(Namespace.URI, "activity");
public static final Name BASE_VERSION = new BasicName(Namespace.URI, "baseVersion");
public static final Name CHILD_VERSION_HISTORY = new BasicName(Namespace.URI, "childVersionHistory");
+ public static final Name CONFIGURATION = new BasicName(Namespace.URI, "configuration");
public static final Name CONTENT = new BasicName(Namespace.URI, "content");
public static final Name COPIED_FROM = new BasicName(Namespace.URI, "copiedFrom");
+ public static final Name CURRENT_LIFECYCLE_STATE = new BasicName(Namespace.URI, "currentLifecycleState");
public static final Name DATA = new BasicName(Namespace.URI, "data");
public static final Name ENCODING = new BasicName(Namespace.URI, "encoding");
public static final Name ETAG = new BasicName(Namespace.URI, "etag");
@@ -44,8 +47,11 @@ public class JcrLexicon extends org.modeshape.graph.JcrLexicon {
public static final Name FROZEN_NODE = new BasicName(Namespace.URI, "frozenNode");
public static final Name FROZEN_PRIMARY_TYPE = new BasicName(Namespace.URI, "frozenPrimaryType");
public static final Name FROZEN_UUID = new BasicName(Namespace.URI, "frozenUuid");
+ public static final Name HOLD = new BasicName(Namespace.URI, "hold");
public static final Name IS_CHECKED_OUT = new BasicName(Namespace.URI, "isCheckedOut");
+ public static final Name IS_DEEP = new BasicName(Namespace.URI, "isDeep");
public static final Name LANGUAGE = new BasicName(Namespace.URI, "language");
+ public static final Name LIFECYCLE_POLICY = new BasicName(Namespace.URI, "lifecyclePolicy");
public static final Name LOCK_IS_DEEP = new BasicName(Namespace.URI, "lockIsDeep");
public static final Name LOCK_OWNER = new BasicName(Namespace.URI, "lockOwner");
public static final Name MERGE_FAILED = new BasicName(Namespace.URI, "mergeFailed");
@@ -53,6 +59,7 @@ public class JcrLexicon extends org.modeshape.graph.JcrLexicon {
/** The "jcr:path" pseudo-column used in queries */
public static final Name PATH = new BasicName(Namespace.URI, "path");
public static final Name PREDECESSORS = new BasicName(Namespace.URI, "predecessors");
+ public static final Name RETENTION_POLICY = new BasicName(Namespace.URI, "retentionPolicy");
public static final Name ROOT = new BasicName(Namespace.URI, "root");
public static final Name ROOT_VERSION = new BasicName(Namespace.URI, "rootVersion");
/** The "jcr:score" pseudo-column used in queries */
Index: modeshape-jcr/src/main/java/org/modeshape/jcr/JcrMixLexicon.java
===================================================================
--- modeshape-jcr/src/main/java/org/modeshape/jcr/JcrMixLexicon.java (revision 2528)
+++ modeshape-jcr/src/main/java/org/modeshape/jcr/JcrMixLexicon.java (working copy)
@@ -41,5 +41,13 @@ public class JcrMixLexicon extends org.modeshape.graph.JcrMixLexicon {
* The name for the "mix:shareable" mixin.
*/
public static final Name SHAREABLE = new BasicName(Namespace.URI, "shareable");
+ /**
+ * The name for the "mix:lifecycle" mixin.
+ */
+ public static final Name LIFECYCLE = new BasicName(Namespace.URI, "lifecycle");
+ /**
+ * The name for the "mix:managedRetention" mixin.
+ */
+ public static final Name MANAGED_RETENTION = new BasicName(Namespace.URI, "managedRetention");
}
Index: modeshape-jcr/src/main/java/org/modeshape/jcr/JcrSession.java
===================================================================
--- modeshape-jcr/src/main/java/org/modeshape/jcr/JcrSession.java (revision 2528)
+++ modeshape-jcr/src/main/java/org/modeshape/jcr/JcrSession.java (working copy)
@@ -603,7 +603,9 @@ class JcrSession implements Session {
public ContentHandler getImportContentHandler( String parentAbsPath,
int uuidBehavior ) throws PathNotFoundException, RepositoryException {
Path parentPath = this.executionContext.getValueFactories().getPathFactory().create(parentAbsPath);
- return new JcrContentHandler(this, parentPath, uuidBehavior, SaveMode.SESSION);
+ boolean retainLifecycleInfo = getRepository().getDescriptorValue(Repository.OPTION_LIFECYCLE_SUPPORTED).getBoolean();
+ boolean retainRetentionInfo = getRepository().getDescriptorValue(Repository.OPTION_RETENTION_SUPPORTED).getBoolean();
+ return new JcrContentHandler(this, parentPath, uuidBehavior, SaveMode.SESSION, retainRetentionInfo, retainLifecycleInfo);
}
/**
Index: modeshape-jcr/src/main/java/org/modeshape/jcr/JcrVersionHistoryNode.java
===================================================================
--- modeshape-jcr/src/main/java/org/modeshape/jcr/JcrVersionHistoryNode.java (revision 2528)
+++ modeshape-jcr/src/main/java/org/modeshape/jcr/JcrVersionHistoryNode.java (working copy)
@@ -84,7 +84,7 @@ class JcrVersionHistoryNode extends JcrNode implements VersionHistory {
* @{inheritDoc
*/
@Override
- public Version getRootVersion() throws RepositoryException {
+ public JcrVersionNode getRootVersion() throws RepositoryException {
// Copied from AbstractJcrNode.getNode(String) to avoid double conversion. Needs to be refactored.
Segment segment = context().getValueFactories().getPathFactory().createSegment(JcrLexicon.ROOT_VERSION);
try {
Index: modeshape-jcr/src/main/java/org/modeshape/jcr/JcrWorkspace.java
===================================================================
--- modeshape-jcr/src/main/java/org/modeshape/jcr/JcrWorkspace.java (revision 2528)
+++ modeshape-jcr/src/main/java/org/modeshape/jcr/JcrWorkspace.java (working copy)
@@ -41,6 +41,7 @@ import javax.jcr.ItemNotFoundException;
import javax.jcr.NoSuchWorkspaceException;
import javax.jcr.NodeIterator;
import javax.jcr.PathNotFoundException;
+import javax.jcr.Repository;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.UnsupportedRepositoryOperationException;
@@ -691,7 +692,11 @@ class JcrWorkspace implements Workspace {
CheckArg.isNotNull(parentAbsPath, "parentAbsPath");
session.checkLive();
Path parentPath = this.context.getValueFactories().getPathFactory().create(parentAbsPath);
- return new JcrContentHandler(this.session, parentPath, uuidBehavior, SaveMode.WORKSPACE);
+ Repository repo = getSession().getRepository();
+ boolean retainLifecycleInfo = repo.getDescriptorValue(Repository.OPTION_LIFECYCLE_SUPPORTED).getBoolean();
+ boolean retainRetentionInfo = repo.getDescriptorValue(Repository.OPTION_RETENTION_SUPPORTED).getBoolean();
+ return new JcrContentHandler(this.session, parentPath, uuidBehavior, SaveMode.WORKSPACE, retainRetentionInfo,
+ retainLifecycleInfo);
}
/**
Index: modeshape-jcr/src/main/java/org/modeshape/jcr/SessionCache.java
===================================================================
--- modeshape-jcr/src/main/java/org/modeshape/jcr/SessionCache.java (revision 2528)
+++ modeshape-jcr/src/main/java/org/modeshape/jcr/SessionCache.java (working copy)
@@ -59,6 +59,7 @@ import org.modeshape.common.util.Logger;
import org.modeshape.graph.ExecutionContext;
import org.modeshape.graph.Graph;
import org.modeshape.graph.Location;
+import org.modeshape.graph.Graph.Batch;
import org.modeshape.graph.connector.RepositorySourceException;
import org.modeshape.graph.property.Binary;
import org.modeshape.graph.property.BinaryFactory;
@@ -146,7 +147,7 @@ class SessionCache {
protected final Path rootPath;
protected final Name residualName;
- private final GraphSession graphSession;
+ private final CustomGraphSession graphSession;
public SessionCache( JcrSession session ) {
this(session, session.workspace().getName(), session.getExecutionContext(), session.nodeTypeManager(), session.graph());
@@ -177,8 +178,7 @@ class SessionCache {
this.residualName = nameFactory.create(JcrNodeType.RESIDUAL_ITEM_NAME);
// Create the graph session, customized for JCR ...
- this.graphSession = new GraphSession(this.store, this.workspaceName,
- new JcrNodeOperations(), new JcrAuthorizer());
+ this.graphSession = new CustomGraphSession(this.store, this.workspaceName, new JcrNodeOperations(), new JcrAuthorizer());
// Set the read-depth if we can...
try {
int depth = Integer.parseInt(session.repository().getOptions().get(Option.READ_DEPTH));
@@ -187,10 +187,33 @@ class SessionCache {
}
}
+ protected class CustomGraphSession extends GraphSession {
+ CustomGraphSession( Graph graph,
+ String workspaceName,
+ Operations nodeOperations,
+ Authorizer authorizer ) {
+ super(graph, workspaceName, nodeOperations, authorizer);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.modeshape.graph.session.GraphSession#operations()
+ */
+ @Override
+ protected Batch operations() {
+ return super.operations();
+ }
+ }
+
final GraphSession graphSession() {
return graphSession;
}
+ Graph.Batch currentBatch() {
+ return graphSession.operations();
+ }
+
JcrSession session() {
return session;
}