Index: modeshape-integration-tests/src/test/java/org/modeshape/test/integration/ClusteringTest.java =================================================================== --- modeshape-integration-tests/src/test/java/org/modeshape/test/integration/ClusteringTest.java (revision 2072) +++ modeshape-integration-tests/src/test/java/org/modeshape/test/integration/ClusteringTest.java (working copy) @@ -25,7 +25,9 @@ package org.modeshape.test.integration; import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsNull.notNullValue; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; import java.io.InputStream; import java.net.URL; import java.util.ArrayList; @@ -34,10 +36,16 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import javax.jcr.ImportUUIDBehavior; import javax.jcr.Node; +import javax.jcr.PropertyType; import javax.jcr.Repository; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.UnsupportedRepositoryOperationException; +import javax.jcr.nodetype.NodeDefinitionTemplate; +import javax.jcr.nodetype.NodeType; +import javax.jcr.nodetype.NodeTypeManager; +import javax.jcr.nodetype.NodeTypeTemplate; +import javax.jcr.nodetype.PropertyDefinitionTemplate; import javax.jcr.observation.Event; import javax.jcr.observation.EventIterator; import javax.jcr.observation.EventListener; @@ -51,6 +59,7 @@ import org.modeshape.connector.store.jpa.JpaSource; import org.modeshape.jcr.JcrConfiguration; import org.modeshape.jcr.JcrEngine; import org.modeshape.jcr.ModeShapeRoles; +import org.modeshape.jcr.NodeTypeAssertion; import org.modeshape.jcr.JcrRepository.Option; public class ClusteringTest { @@ -62,7 +71,7 @@ public class ClusteringTest { private static List sessions = new ArrayList(); @BeforeClass - public static void beforeEach() throws Exception { + public static void beforeAll() throws Exception { // Delete the database files, if there are any ... FileUtil.delete("target/db"); @@ -295,6 +304,88 @@ public class ClusteringTest { assertThat(session3.getNode(path).getProperty(propName).getString(), is(propValue)); } + @Test + public void shouldPropagateTypeUnregistrationAcrossCluster() throws Exception { + Session session1 = sessionFrom(engine1); + Session session2 = sessionFrom(engine2); + Session session3 = sessionFrom(engine3); + + NodeTypeManager typeManager1 = session1.getWorkspace().getNodeTypeManager(); + NodeTypeManager typeManager2 = session2.getWorkspace().getNodeTypeManager(); + NodeTypeManager typeManager3 = session3.getWorkspace().getNodeTypeManager(); + + final String NODE_TYPE_NAME = "nt:file"; + + typeManager1.unregisterNodeType(NODE_TYPE_NAME); + assertFalse(typeManager1.hasNodeType(NODE_TYPE_NAME)); + + for (int i = 0; i < 50; i++) { + try { + if (!typeManager2.hasNodeType(NODE_TYPE_NAME) && !typeManager3.hasNodeType(NODE_TYPE_NAME)) break; + Thread.sleep(100); + } catch (InterruptedException ie) { + return; + } + } + + assertFalse(typeManager2.hasNodeType(NODE_TYPE_NAME)); + assertFalse(typeManager3.hasNodeType(NODE_TYPE_NAME)); + } + + @SuppressWarnings( "unchecked" ) + @Test + public void shouldPropagateTypeRegistrationAcrossCluster() throws Exception { + Session session1 = sessionFrom(engine1); + Session session2 = sessionFrom(engine2); + Session session3 = sessionFrom(engine3); + + NodeTypeManager typeManager1 = session1.getWorkspace().getNodeTypeManager(); + NodeTypeManager typeManager2 = session2.getWorkspace().getNodeTypeManager(); + NodeTypeManager typeManager3 = session3.getWorkspace().getNodeTypeManager(); + + final String NODE_TYPE_NAME = "mode:newType"; + NodeTypeTemplate nodeType = typeManager1.createNodeTypeTemplate(); + nodeType.setName(NODE_TYPE_NAME); + nodeType.setMixin(true); + nodeType.setDeclaredSuperTypeNames(new String[] {"mix:referenceable"}); + nodeType.setOrderableChildNodes(true); + nodeType.setQueryable(true); + + PropertyDefinitionTemplate prop = typeManager1.createPropertyDefinitionTemplate(); + prop.setName("prop"); + prop.setAvailableQueryOperators(new String[] {"="}); + prop.setMultiple(true); + prop.setRequiredType(PropertyType.STRING); + nodeType.getPropertyDefinitionTemplates().add(prop); + + NodeDefinitionTemplate child = typeManager1.createNodeDefinitionTemplate(); + child.setName("child"); + child.setSameNameSiblings(true); + nodeType.getNodeDefinitionTemplates().add(child); + + typeManager1.registerNodeType(nodeType, false); + assertTrue(typeManager1.hasNodeType(NODE_TYPE_NAME)); + NodeType nodeType1 = typeManager1.getNodeType(NODE_TYPE_NAME); + + for (int i = 0; i < 50; i++) { + try { + if (typeManager2.hasNodeType(NODE_TYPE_NAME) && typeManager3.hasNodeType(NODE_TYPE_NAME)) break; + Thread.sleep(100); + } catch (InterruptedException ie) { + return; + } + } + + assertTrue(typeManager2.hasNodeType(NODE_TYPE_NAME)); + assertTrue(typeManager3.hasNodeType(NODE_TYPE_NAME)); + + NodeTypeTemplate template2 = typeManager2.createNodeTypeTemplate(typeManager2.getNodeType(NODE_TYPE_NAME)); + NodeTypeTemplate template3 = typeManager3.createNodeTypeTemplate(typeManager3.getNodeType(NODE_TYPE_NAME)); + + NodeTypeAssertion.compareTemplateToNodeType(template2, nodeType1); + NodeTypeAssertion.compareTemplateToNodeType(template3, nodeType1); + } + // ---------------------------------------------------------------------------------------------------------------- // Utility Methods // ---------------------------------------------------------------------------------------------------------------- Index: modeshape-jcr/src/main/java/org/modeshape/jcr/GraphNodeTypeReader.java =================================================================== --- modeshape-jcr/src/main/java/org/modeshape/jcr/GraphNodeTypeReader.java (revision 2072) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/GraphNodeTypeReader.java (working copy) @@ -105,7 +105,7 @@ import org.modeshape.graph.property.basic.LocalNamespaceRegistry; *

*/ @NotThreadSafe -abstract class GraphNodeTypeReader implements Iterable { +class GraphNodeTypeReader implements Iterable { private static final Map PROPERTY_TYPE_VALUES_FROM_NAME; @@ -321,6 +321,7 @@ abstract class GraphNodeTypeReader implements Iterable { NodeTypeDefinition nodeType = nodeTypeFrom(nodeTypeNode, nodeTypeSubgraph); results.add(nodeType); } catch (ConstraintViolationException e) { + e.printStackTrace(); String resource = stringFactory.create(locationOfParentOfNodeTypes.getPath()); problems.addError(e, JcrI18n.errorImportingNodeTypeContent, resource, e.getMessage()); } @@ -373,6 +374,8 @@ abstract class GraphNodeTypeReader implements Iterable { JcrLexicon.ON_PARENT_VERSION, OnParentVersionAction.nameFromValue(OnParentVersionAction.COPY)); int onParentVersion = OnParentVersionAction.valueFromName(onParentVersionName); + + String requiredTypeName = readString(propertyDefinitionNode, JcrLexicon.REQUIRED_TYPE, null); int requiredType = PROPERTY_TYPE_VALUES_FROM_NAME.get(readString(propertyDefinitionNode, JcrLexicon.REQUIRED_TYPE, null)); boolean mandatory = readBoolean(propertyDefinitionNode, JcrLexicon.MANDATORY, false); @@ -386,7 +389,9 @@ abstract class GraphNodeTypeReader implements Iterable { List queryOps = readStrings(propertyDefinitionNode, JcrLexicon.QUERY_OPERATORS); PropertyDefinitionTemplate template = new JcrPropertyDefinitionTemplate(context); - template.setName(name); + if (name != null) { + template.setName(name); + } template.setAutoCreated(autoCreated); template.setMandatory(mandatory); template.setMultiple(multiple); @@ -420,7 +425,9 @@ abstract class GraphNodeTypeReader implements Iterable { List requiredTypes = readStrings(childNodeDefinitionNode, JcrLexicon.REQUIRED_PRIMARY_TYPES); NodeDefinitionTemplate template = new JcrNodeDefinitionTemplate(context); - template.setName(childNodeName); + if (childNodeName != null) { + template.setName(childNodeName); + } template.setAutoCreated(autoCreated); template.setMandatory(mandatory); template.setSameNameSiblings(allowsSns); @@ -541,15 +548,17 @@ abstract class GraphNodeTypeReader implements Iterable { /** * Method that loads into the graph destination the content containing the node type definitions. * - * @param graphDestination - * @param path - * @param content - * @param resourceName + * @param graphDestination the destination to which the node type content should be written; never null + * @param path the path within the destination at which the node type content should be rooted; never null + * @param content the content containing some string representation of the node types to be imported; never null + * @param resourceName a descriptive name for this import (used only for error messages); may be null * @throws Exception if there is a problem importing from the content; this will be automatically recorded in the problems */ - protected abstract void importFrom( Destination graphDestination, - Path path, - String content, - String resourceName ) throws Exception; + protected void importFrom( Destination graphDestination, + Path path, + String content, + String resourceName ) throws Exception { + throw new UnsupportedOperationException(); + } } Index: modeshape-jcr/src/main/java/org/modeshape/jcr/JcrI18n.java =================================================================== --- modeshape-jcr/src/main/java/org/modeshape/jcr/JcrI18n.java (revision 2072) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/JcrI18n.java (working copy) @@ -200,6 +200,10 @@ public final class JcrI18n { public static I18n primaryTypeCannotBeAbstract; public static I18n setPrimaryTypeNotSupported; + public static I18n errorReadingNodeTypesFromRemote; + public static I18n problemReadingNodeTypesFromRemote; + public static I18n errorSynchronizingNodeTypes; + // Lock messages public static I18n nodeNotLockable; public static I18n cannotRemoveLockToken; Index: modeshape-jcr/src/main/java/org/modeshape/jcr/JcrNodeTypeManager.java =================================================================== --- modeshape-jcr/src/main/java/org/modeshape/jcr/JcrNodeTypeManager.java (revision 2072) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/JcrNodeTypeManager.java (working copy) @@ -91,6 +91,10 @@ public class JcrNodeTypeManager implements NodeTypeManager { this.schemata = null; } + void signalExternalNodeTypeChanges() { + this.schemata = null; + } + /** * {@inheritDoc} * @@ -207,6 +211,7 @@ public class JcrNodeTypeManager implements NodeTypeManager { for (NodeDefinition definition : repositoryTypeManager.getNodeType(ModeShapeLexicon.ROOT).getChildNodeDefinitions()) { if (definition.getName().equals(JcrNodeType.RESIDUAL_ITEM_NAME)) return (JcrNodeDefinition)definition; } + assert false; // should not get here return null; } @@ -672,13 +677,15 @@ public class JcrNodeTypeManager implements NodeTypeManager { JcrNodeDefinitionTemplate ndt = new JcrNodeDefinitionTemplate(context()); ndt.setAutoCreated(nodeDefinition.isAutoCreated()); - ndt.setDefaultPrimaryType(ndt.getDefaultPrimaryTypeName()); - ndt.setMandatory(ndt.isMandatory()); - ndt.setName(ndt.getName()); - ndt.setOnParentVersion(ndt.getOnParentVersion()); - ndt.setProtected(ndt.isProtected()); - ndt.setRequiredPrimaryTypeNames(ndt.getRequiredPrimaryTypeNames()); - ndt.setSameNameSiblings(ndt.allowsSameNameSiblings()); + ndt.setDefaultPrimaryType(nodeDefinition.getDefaultPrimaryTypeName()); + ndt.setMandatory(nodeDefinition.isMandatory()); + if (nodeDefinition.getName() != null) { + ndt.setName(nodeDefinition.getName()); + } + ndt.setOnParentVersion(nodeDefinition.getOnParentVersion()); + ndt.setProtected(nodeDefinition.isProtected()); + ndt.setRequiredPrimaryTypeNames(nodeDefinition.getRequiredPrimaryTypeNames()); + ndt.setSameNameSiblings(nodeDefinition.allowsSameNameSiblings()); ntt.getNodeDefinitionTemplates().add(ndt); } @@ -692,7 +699,9 @@ public class JcrNodeTypeManager implements NodeTypeManager { pdt.setFullTextSearchable(propertyDefinition.isFullTextSearchable()); pdt.setMandatory(propertyDefinition.isMandatory()); pdt.setMultiple(propertyDefinition.isMultiple()); - pdt.setName(propertyDefinition.getName()); + if (propertyDefinition.getName() != null) { + pdt.setName(propertyDefinition.getName()); + } pdt.setOnParentVersion(propertyDefinition.getOnParentVersion()); pdt.setProtected(propertyDefinition.isProtected()); pdt.setQueryOrderable(propertyDefinition.isQueryOrderable()); Index: modeshape-jcr/src/main/java/org/modeshape/jcr/JcrRepository.java =================================================================== --- modeshape-jcr/src/main/java/org/modeshape/jcr/JcrRepository.java (revision 2072) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/JcrRepository.java (working copy) @@ -299,7 +299,7 @@ public class JcrRepository implements Repository { /** * The default value for the {@link Option#PROJECT_NODE_TYPES} option is {@value} . */ - public static final String PROJECT_NODE_TYPES = Boolean.FALSE.toString(); + public static final String PROJECT_NODE_TYPES = Boolean.TRUE.toString(); /** * The default value for the {@link Option#JAAS_LOGIN_CONFIG_NAME} option is {@value} . @@ -416,10 +416,6 @@ public class JcrRepository implements Repository { private final RepositoryObservationManager repositoryObservationManager; private final SecurityContext anonymousUserContext; private final QueryParsers queryParsers; - /** - * Immutable collection of objects observing changes to the system graph - */ - private final Collection jcrSystemObservers; // Until the federated connector supports queries, we have to use a search engine ... private final RepositoryQueryManager queryManager; @@ -557,10 +553,17 @@ public class JcrRepository implements Repository { } // Set up the repository type manager ... + Path parentOfTypeNodes = null; + + if (Boolean.valueOf(this.options.get(Option.PROJECT_NODE_TYPES))) { + parentOfTypeNodes = pathFactory.create(systemPath, JcrLexicon.NODE_TYPES); + } + try { boolean includeInheritedProperties = Boolean.valueOf(this.options.get(Option.TABLES_INCLUDE_COLUMNS_FOR_INHERITED_PROPERTIES)); + // this.repositoryTypeManager = new RepositoryNodeTypeManager(this, includeInheritedProperties); - this.repositoryTypeManager = new RepositoryNodeTypeManager(this, includeInheritedProperties); + this.repositoryTypeManager = new RepositoryNodeTypeManager(this, parentOfTypeNodes, includeInheritedProperties); CndNodeTypeReader nodeTypeReader = new CndNodeTypeReader(this.executionContext); nodeTypeReader.readBuiltInTypes(); this.repositoryTypeManager.registerNodeTypes(nodeTypeReader); @@ -572,11 +575,6 @@ public class JcrRepository implements Repository { throw new IllegalStateException("Could not access node type definition files", ioe); } if (WORKSPACES_SHARE_SYSTEM_BRANCH) { - if (Boolean.valueOf(this.options.get(Option.PROJECT_NODE_TYPES))) { - // Note that the node types are written directly to the system workspace. - Path parentOfTypeNodes = pathFactory.create(systemPath, JcrLexicon.NODE_TYPES); - this.repositoryTypeManager.projectOnto(systemGraph, parentOfTypeNodes); - } // Create the projection for the system repository ... ProjectionParser projectionParser = ProjectionParser.getInstance(); @@ -729,11 +727,9 @@ public class JcrRepository implements Repository { }; // Define the set of "/jcr:system" observers ... - this.jcrSystemObservers = Collections.unmodifiableList(Arrays.asList(new JcrSystemObserver[] {repositoryLockManager, - namespaceObserver})); - // This observer picks up notification of changes to the system graph in a cluster. It's a NOP if there is no cluster. - this.repositoryObservationManager.register(new SystemChangeObserver()); + repositoryObservationManager.register(new SystemChangeObserver(Arrays.asList(new JcrSystemObserver[] { + repositoryLockManager, namespaceObserver, repositoryTypeManager}))); } protected void addWorkspace( String workspaceName, @@ -937,13 +933,6 @@ public class JcrRepository implements Repository { } /** - * @return jcrSystemObservers - */ - Collection getSystemObservers() { - return jcrSystemObservers; - } - - /** * Get the name of the source that we want to observe. * * @return the name of the source that should be observed; never null @@ -1726,11 +1715,16 @@ public class JcrRepository implements Repository { */ class SystemChangeObserver implements Observer { + /** + * Immutable collection of objects observing changes to the system graph + */ + private final Collection jcrSystemObservers; private final String processId; private final String systemSourceName; private final String systemWorkspaceName; - SystemChangeObserver() { + SystemChangeObserver( Collection jcrSystemObservers ) { + this.jcrSystemObservers = Collections.unmodifiableCollection(jcrSystemObservers); processId = getExecutionContext().getProcessId(); systemSourceName = getSystemSourceName(); systemWorkspaceName = getSystemWorkspaceName(); @@ -1742,9 +1736,21 @@ public class JcrRepository implements Repository { @Override public void notify( Changes changes ) { - // Don't process changes from outside the system graph - if (!changes.getSourceName().equals(systemSourceName)) return; + if (!changes.getSourceName().equals(systemSourceName)) { + /* + * It's permissable for the system source to be the same as the source for the workspaces. + * In that case, the RepositoryObservationManager would have already translated the system + * source name into the observeable source name (from getObservableSourceName()), obscuring + * the actual source. + * + * So if the change source name doesn't equal the system source name BUT the system source name + * is the same as the repository (read: workspace) source name, don't give up yet. The difference + * may be due to RepositoryObservationManager. Rely on the systemWorkspaceName check below to + * be sure. + */ + if (!systemSourceName.equals(getRepositorySourceName())) return; + } // Don't process changes from this repository if (changes.getProcessId().equals(processId)) return; @@ -1758,7 +1764,7 @@ public class JcrRepository implements Repository { Path changedPath = change.changedLocation().getPath(); if (changedPath == null) continue; - for (JcrSystemObserver jcrSystemObserver : getSystemObservers()) { + for (JcrSystemObserver jcrSystemObserver : jcrSystemObservers) { if (changedPath.isAtOrBelow(jcrSystemObserver.getObservedPath())) { systemChanges.put(jcrSystemObserver, change); } Index: modeshape-jcr/src/main/java/org/modeshape/jcr/RepositoryLockManager.java =================================================================== --- modeshape-jcr/src/main/java/org/modeshape/jcr/RepositoryLockManager.java (revision 2072) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/RepositoryLockManager.java (working copy) @@ -118,8 +118,7 @@ class RepositoryLockManager implements JcrSystemObserver { for (Location lockLocation : locksGraph.getRoot().getChildren()) { Node lockNode = locksGraph.getNode(lockLocation); - Boolean isSessionScoped = booleanFactory.create(lockNode.getProperty(ModeShapeLexicon.IS_SESSION_SCOPED) - .getFirstValue()); + Boolean isSessionScoped = booleanFactory.create(lockNode.getProperty(ModeShapeLexicon.IS_SESSION_SCOPED).getFirstValue()); if (!isSessionScoped) continue; String lockingSession = stringFactory.create(lockNode.getProperty(ModeShapeLexicon.LOCKING_SESSION).getFirstValue()); @@ -128,8 +127,7 @@ class RepositoryLockManager implements JcrSystemObserver { if (activeSessionIds.contains(lockingSession)) { systemGraph.set(ModeShapeLexicon.EXPIRATION_DATE).on(lockLocation).to(newExpirationDate); } else { - DateTime expirationDate = dateFactory.create(lockNode.getProperty(ModeShapeLexicon.EXPIRATION_DATE) - .getFirstValue()); + DateTime expirationDate = dateFactory.create(lockNode.getProperty(ModeShapeLexicon.EXPIRATION_DATE).getFirstValue()); // Destroy expired locks (if it was still held by an active session, it would have been extended by now) if (expirationDate.isBefore(now)) { String workspaceName = stringFactory.create(lockNode.getProperty(ModeShapeLexicon.WORKSPACE).getFirstValue()); @@ -170,14 +168,11 @@ class RepositoryLockManager implements JcrSystemObserver { UUID lockedNodeUuid = UUID.fromString(string(rawUuid)); assert lockedNodeUuid != null; - String workspaceName = change.changedWorkspace(); - WorkspaceLockManager workspaceManager = getLockManager(workspaceName); - assert workspaceManager != null; - switch (change.getType()) { case CREATE_NODE: CreateNodeRequest create = (CreateNodeRequest)change; + Property workspaceNameProp = null; Property lockOwnerProp = null; Property lockUuidProp = null; Property isDeepProp = null; @@ -192,19 +187,33 @@ class RepositoryLockManager implements JcrSystemObserver { isSessionScopedProp = prop; } else if (JcrLexicon.UUID.equals(prop.getName())) { isSessionScopedProp = prop; + } else if (ModeShapeLexicon.WORKSPACE_NAME.equals(prop.getName())) { + workspaceNameProp = prop; } } String lockOwner = firstString(lockOwnerProp); + String workspaceName = firstString(workspaceNameProp); UUID lockUuid = firstUuid(lockUuidProp); boolean isDeep = firstBoolean(isDeepProp); boolean isSessionScoped = firstBoolean(isSessionScopedProp); + WorkspaceLockManager workspaceManager = getLockManager(workspaceName); + assert workspaceManager != null; + workspaceManager.lockNodeInternally(lockOwner, lockUuid, lockedNodeUuid, isDeep, isSessionScoped); break; case DELETE_BRANCH: - boolean success = workspaceManager.unlockNodeInternally(lockedNodeUuid); + + + boolean success = false; + for (WorkspaceLockManager workspaceLockManager : lockManagers.values()) { + if (workspaceLockManager.lockFor(lockedNodeUuid) != null) { + success |= workspaceLockManager.unlockNodeInternally(lockedNodeUuid); + break; + } + } assert success : "No internal lock existed for node " + lockedNodeUuid.toString(); Index: modeshape-jcr/src/main/java/org/modeshape/jcr/RepositoryNodeTypeManager.java =================================================================== --- modeshape-jcr/src/main/java/org/modeshape/jcr/RepositoryNodeTypeManager.java (revision 2072) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/RepositoryNodeTypeManager.java (working copy) @@ -48,14 +48,17 @@ import javax.jcr.query.InvalidQueryException; import javax.jcr.version.OnParentVersionAction; import net.jcip.annotations.GuardedBy; import net.jcip.annotations.ThreadSafe; +import org.modeshape.common.collection.Problems; import org.modeshape.common.i18n.I18n; import org.modeshape.common.text.TextEncoder; import org.modeshape.common.text.XmlNameEncoder; import org.modeshape.common.util.CheckArg; +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.Subgraph; +import org.modeshape.graph.observe.Changes; import org.modeshape.graph.property.Name; import org.modeshape.graph.property.NameFactory; import org.modeshape.graph.property.Path; @@ -69,6 +72,7 @@ import org.modeshape.graph.query.model.TypeSystem; import org.modeshape.graph.query.parse.QueryParser; import org.modeshape.graph.query.parse.SqlQueryParser; import org.modeshape.graph.query.validate.Schemata; +import org.modeshape.graph.request.ChangeRequest; import org.modeshape.jcr.nodetype.InvalidNodeTypeDefinitionException; import org.modeshape.jcr.nodetype.NodeTypeExistsException; @@ -85,13 +89,15 @@ import org.modeshape.jcr.nodetype.NodeTypeExistsException; *

*/ @ThreadSafe -class RepositoryNodeTypeManager { +class RepositoryNodeTypeManager implements JcrSystemObserver { + private static final Logger LOGGER = Logger.getLogger(RepositoryNodeTypeManager.class); private static final TextEncoder NAME_ENCODER = new XmlNameEncoder(); private final JcrRepository repository; private final QueryParser queryParser; private final ExecutionContext context; + private final Path nodeTypesPath; @GuardedBy( "nodeTypeManagerLock" ) private final Map nodeTypes; @@ -130,8 +136,10 @@ class RepositoryNodeTypeManager { } RepositoryNodeTypeManager( JcrRepository repository, + Path nodeTypesPath, boolean includeColumnsForInheritedProperties ) { this.repository = repository; + this.nodeTypesPath = nodeTypesPath; this.context = repository.getExecutionContext(); this.includeColumnsForInheritedProperties = includeColumnsForInheritedProperties; this.propertyFactory = context.getPropertyFactory(); @@ -143,6 +151,14 @@ class RepositoryNodeTypeManager { nodeTypes = new HashMap(50); queryParser = new SqlQueryParser(); + if (nodeTypesPath != null) { + Graph systemGraph = repository.createSystemGraph(context); + try { + systemGraph.getNodeAt(nodeTypesPath); + } catch (PathNotFoundException pnfe) { + systemGraph.create(nodeTypesPath).with(JcrLexicon.PRIMARY_TYPE, ModeShapeLexicon.NODE_TYPES).and(); + } + } } /** @@ -1139,7 +1155,7 @@ class RepositoryNodeTypeManager { propsList.add(propertyFactory.create(JcrLexicon.HAS_ORDERABLE_CHILD_NODES, nodeType.hasOrderableChildNodes())); propsList.add(propertyFactory.create(JcrLexicon.SUPERTYPES, supertypeNames)); - batch.create(nodeTypePath).with(propsList).and(); + batch.create(nodeTypePath).with(propsList).orReplace().and(); PropertyDefinition[] propertyDefs = nodeType.getDeclaredPropertyDefinitions(); for (int i = 0; i < propertyDefs.length; i++) { @@ -1192,7 +1208,8 @@ class RepositoryNodeTypeManager { propsList.add(propertyFactory.create(JcrLexicon.PROTECTED, jcrPropDef.isProtected())); propsList.add(propertyFactory.create(JcrLexicon.ON_PARENT_VERSION, OnParentVersionAction.nameFromValue(jcrPropDef.getOnParentVersion()))); - propsList.add(propertyFactory.create(JcrLexicon.REQUIRED_TYPE, PropertyType.nameFromValue(jcrPropDef.getRequiredType()))); + propsList.add(propertyFactory.create(JcrLexicon.REQUIRED_TYPE, + PropertyType.nameFromValue(jcrPropDef.getRequiredType()).toUpperCase())); Value[] defaultValues = jcrPropDef.getDefaultValues(); if (defaultValues.length > 0) { @@ -1211,7 +1228,7 @@ class RepositoryNodeTypeManager { String[] valueConstraints = jcrPropDef.getValueConstraints(); if (valueConstraints.length > 0) { - propsList.add(propertyFactory.create(JcrLexicon.DEFAULT_VALUES, (Object[])valueConstraints)); + propsList.add(propertyFactory.create(JcrLexicon.VALUE_CONSTRAINTS, (Object[])valueConstraints)); } batch.create(propDefPath).with(propsList).and(); } @@ -1254,7 +1271,7 @@ class RepositoryNodeTypeManager { propsList.add(propertyFactory.create(JcrLexicon.DEFAULT_PRIMARY_TYPE, jcrNodeDef.getDefaultPrimaryType().getName())); } - propsList.add(propertyFactory.create(JcrLexicon.REQUIRED_PRIMARY_TYPES, jcrNodeDef.requiredPrimaryTypeNameSet())); + propsList.add(propertyFactory.create(JcrLexicon.REQUIRED_PRIMARY_TYPES, (Object[])jcrNodeDef.requiredPrimaryTypeNames())); propsList.add(propertyFactory.create(JcrLexicon.SAME_NAME_SIBLINGS, jcrNodeDef.allowsSameNameSiblings())); propsList.add(propertyFactory.create(JcrLexicon.ON_PARENT_VERSION, OnParentVersionAction.nameFromValue(jcrNodeDef.getOnParentVersion()))); @@ -1347,6 +1364,17 @@ class RepositoryNodeTypeManager { } this.nodeTypes.keySet().removeAll(nodeTypeNames); + + if (nodeTypesPath != null) { + Graph.Batch batch = repository.createSystemGraph(context).batch(); + + for (Name nodeTypeName : nodeTypeNames) { + Path nodeTypePath = pathFactory.create(nodeTypesPath, nodeTypeName); + batch.delete(nodeTypePath).and(); + } + + batch.execute(); + } this.schemata = null; } finally { @@ -1546,6 +1574,9 @@ class RepositoryNodeTypeManager { } } + Graph.Batch batch = null; + if (nodeTypesPath != null) batch = repository.createSystemGraph(context).batch(); + for (JcrNodeType nodeType : typesPendingRegistration) { /* * See comment in constructor. Using a ConcurrentHashMap seems to be to weak of a @@ -1561,15 +1592,18 @@ class RepositoryNodeTypeManager { propertyDefinitions.put(propertyDefinition.getId(), propertyDefinition); } - // projectNodeTypeOnto(nodeType, parentOfTypeNodes, batch); + if (nodeTypesPath != null) projectNodeTypeOnto(nodeType, nodeTypesPath, batch); } // Throw away the schemata, since the node types have changed ... this.schemata = null; + if (nodeTypesPath != null) { + assert batch != null; + batch.execute(); + } } finally { nodeTypeManagerLock.writeLock().unlock(); } - // batch.execute(); return typesPendingRegistration; } @@ -1739,16 +1773,16 @@ class RepositoryNodeTypeManager { } /** - * Finds the named type in the given list of types pending registration if it exists, else returns the type definition from - * the repository + * Finds the named type in the given collection of types pending registration if it exists, else returns the type definition + * from the repository * * @param typeName the name of the type to retrieve - * @param pendingList a list of types that have passed validation but have not yet been committed to the repository - * @return the node type with the given name from {@code pendingList} if it exists in the list or from the {@link #nodeTypes - * registered types} if it exists there; may be null + * @param pendingList a collection of types that have passed validation but have not yet been committed to the repository + * @return the node type with the given name from {@code pendingList} if it exists in the collection or from the + * {@link #nodeTypes registered types} if it exists there; may be null */ private JcrNodeType findTypeInMapOrList( Name typeName, - List pendingList ) { + Collection pendingList ) { for (JcrNodeType pendingNodeType : pendingList) { if (pendingNodeType.getInternalName().equals(typeName)) { return pendingNodeType; @@ -1770,7 +1804,7 @@ class RepositoryNodeTypeManager { * already-registered node type or a node type that is pending registration */ private List supertypesFor( NodeTypeDefinition nodeType, - List pendingTypes ) throws RepositoryException { + Collection pendingTypes ) throws RepositoryException { assert nodeType != null; List supertypes = new LinkedList(); @@ -2204,4 +2238,86 @@ class RepositoryNodeTypeManager { } } + @Override + public Path getObservedPath() { + return this.nodeTypesPath; + } + + @Override + public void notify( Changes changes ) { + boolean needsReload = false; + + for (ChangeRequest change : changes.getChangeRequests()) { + assert change.changedLocation().hasPath(); + + Path changedPath = change.changedLocation().getPath(); + if (changedPath.equals(nodeTypesPath)) { + // nothing to do with the "/jcr:system/jcr:nodeTypes" node ... + continue; + } + assert nodeTypesPath.isAncestorOf(changedPath); + + switch (change.getType()) { + case CREATE_NODE: + case DELETE_BRANCH: + needsReload = true; + + break; + default: + assert false : "Unexpected change request: " + change; + } + } + + if (!needsReload) return; + + this.nodeTypeManagerLock.writeLock().lock(); + try { + GraphNodeTypeReader reader = new GraphNodeTypeReader(this.context); + Graph systemGraph = repository.createSystemGraph(this.context); + + reader.read(systemGraph, nodeTypesPath, null); + + Problems readerProblems = reader.getProblems(); + if (readerProblems.hasProblems()) { + if (readerProblems.hasErrors()) { + LOGGER.error(JcrI18n.errorReadingNodeTypesFromRemote, reader.getProblems()); + return; + } + + LOGGER.warn(JcrI18n.problemReadingNodeTypesFromRemote, reader.getProblems()); + } + + Map newNodeTypeMap = new HashMap(); + try { + for (NodeTypeDefinition nodeTypeDefn : reader.getNodeTypeDefinitions()) { + List supertypes = supertypesFor(nodeTypeDefn, newNodeTypeMap.values()); + JcrNodeType nodeType = nodeTypeFrom(nodeTypeDefn, supertypes); + + newNodeTypeMap.put(nodeType.getInternalName(), nodeType); + } + } catch (Throwable re) { + LOGGER.error(JcrI18n.errorSynchronizingNodeTypes, re); + } + + this.nodeTypes.clear(); + this.nodeTypes.putAll(newNodeTypeMap); + + assert this.nodeTypes.get(ModeShapeLexicon.ROOT) != null; + + for (JcrSession activeSession : repository.activeSessions()) { + JcrWorkspace workspace = activeSession.workspace(); + if (workspace == null) continue; + + JcrNodeTypeManager nodeTypeManager = workspace.nodeTypeManager(); + if (nodeTypeManager == null) continue; + + nodeTypeManager.signalExternalNodeTypeChanges(); + } + + } finally { + this.schemata = null; + this.nodeTypeManagerLock.writeLock().unlock(); + } + + } } Index: modeshape-jcr/src/main/resources/org/modeshape/jcr/JcrI18n.properties =================================================================== --- modeshape-jcr/src/main/resources/org/modeshape/jcr/JcrI18n.properties (revision 2072) +++ modeshape-jcr/src/main/resources/org/modeshape/jcr/JcrI18n.properties (working copy) @@ -187,6 +187,10 @@ cannotUseMixinTypeAsPrimaryType = This operation requires a primary type, but "{ primaryTypeCannotBeAbstract = The prrimary type of a node cannot be abstract, like "{0}" setPrimaryTypeNotSupported = ModeShape does not currently allow modifying the primary type of a node +errorReadingNodeTypesFromRemote = Node types changed due to remote update. Node types are likely to be different across nodes in the cluster. Encountered following errors reading node types from graph: {0} +problemReadingNodeTypesFromRemote = Node types changed due to remote update. Encountered following problems reading node types from graph: {0} +errorSynchronizingNodeTypes = Node types changed due to remote update. Could not rebuild node type map. Node types are likely to be different across nodes in the cluster. + # Lock messages nodeNotLockable = The node at '{0}' is not lockable. Add the 'mix:lockable' mixin type to make it lockable. cannotRemoveLockToken = The lock token '{0}' is a session-scoped lock Index: modeshape-jcr/src/test/java/org/modeshape/jcr/AbstractJcrTest.java =================================================================== --- modeshape-jcr/src/test/java/org/modeshape/jcr/AbstractJcrTest.java (revision 2072) +++ modeshape-jcr/src/test/java/org/modeshape/jcr/AbstractJcrTest.java (working copy) @@ -83,7 +83,7 @@ public abstract class AbstractJcrTest { repoLockManager = mock(RepositoryLockManager.class); when(repository.getRepositoryLockManager()).thenReturn(repoLockManager); - rntm = new RepositoryNodeTypeManager(repository, true); + rntm = new RepositoryNodeTypeManager(repository, null, true); try { CndNodeTypeReader cndReader = new CndNodeTypeReader(context); cndReader.readBuiltInTypes(); Index: modeshape-jcr/src/test/java/org/modeshape/jcr/AbstractSessionTest.java =================================================================== --- modeshape-jcr/src/test/java/org/modeshape/jcr/AbstractSessionTest.java (revision 2072) +++ modeshape-jcr/src/test/java/org/modeshape/jcr/AbstractSessionTest.java (working copy) @@ -145,7 +145,7 @@ public abstract class AbstractSessionTest { when(repository.getRepositoryLockManager()).thenReturn(repoLockManager); // Stub out the repository, since we only need a few methods ... - repoTypeManager = new RepositoryNodeTypeManager(repository, true); + repoTypeManager = new RepositoryNodeTypeManager(repository, null, true); when(repository.getRepositoryTypeManager()).thenReturn(repoTypeManager); try { @@ -194,7 +194,6 @@ public abstract class AbstractSessionTest { registry = session.getExecutionContext().getNamespaceRegistry(); } - @SuppressWarnings( "unused" ) protected List getTestTypes() throws ConstraintViolationException { return Collections.emptyList(); } Index: modeshape-jcr/src/test/java/org/modeshape/jcr/CndNodeTypeRegistrationTest.java =================================================================== --- modeshape-jcr/src/test/java/org/modeshape/jcr/CndNodeTypeRegistrationTest.java (revision 2072) +++ modeshape-jcr/src/test/java/org/modeshape/jcr/CndNodeTypeRegistrationTest.java (working copy) @@ -62,7 +62,7 @@ public class CndNodeTypeRegistrationTest { when(repository.getExecutionContext()).thenReturn(context); - repoTypeManager = new RepositoryNodeTypeManager(repository, true); + repoTypeManager = new RepositoryNodeTypeManager(repository, null, true); try { CndNodeTypeReader cndReader = new CndNodeTypeReader(context); cndReader.readBuiltInTypes(); Index: modeshape-jcr/src/test/java/org/modeshape/jcr/JackrabbitXmlNodeTypeRegistrationTest.java =================================================================== --- modeshape-jcr/src/test/java/org/modeshape/jcr/JackrabbitXmlNodeTypeRegistrationTest.java (revision 2072) +++ modeshape-jcr/src/test/java/org/modeshape/jcr/JackrabbitXmlNodeTypeRegistrationTest.java (working copy) @@ -67,7 +67,7 @@ public class JackrabbitXmlNodeTypeRegistrationTest { when(repository.getExecutionContext()).thenReturn(context); - repoTypeManager = new RepositoryNodeTypeManager(repository, true); + repoTypeManager = new RepositoryNodeTypeManager(repository, null, true); try { CndNodeTypeReader cndFactory = new CndNodeTypeReader(context); cndFactory.readBuiltInTypes(); Index: modeshape-jcr/src/test/java/org/modeshape/jcr/TypeRegistrationTest.java =================================================================== --- modeshape-jcr/src/test/java/org/modeshape/jcr/TypeRegistrationTest.java (revision 2072) +++ modeshape-jcr/src/test/java/org/modeshape/jcr/TypeRegistrationTest.java (working copy) @@ -32,9 +32,7 @@ import java.util.Arrays; import java.util.List; import javax.jcr.PropertyType; import javax.jcr.nodetype.NoSuchNodeTypeException; -import javax.jcr.nodetype.NodeDefinition; import javax.jcr.nodetype.NodeTypeDefinition; -import javax.jcr.nodetype.PropertyDefinition; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; @@ -43,10 +41,8 @@ import org.modeshape.graph.property.Name; import org.modeshape.graph.property.NameFactory; import org.modeshape.graph.property.NamespaceRegistry; import org.modeshape.jcr.nodetype.InvalidNodeTypeDefinitionException; -import org.modeshape.jcr.nodetype.NodeDefinitionTemplate; import org.modeshape.jcr.nodetype.NodeTypeExistsException; import org.modeshape.jcr.nodetype.NodeTypeTemplate; -import org.modeshape.jcr.nodetype.PropertyDefinitionTemplate; public class TypeRegistrationTest extends AbstractSessionTest { @@ -921,113 +917,6 @@ public class TypeRegistrationTest extends AbstractSessionTest { assertThat(nodeType.nodeTypeManager(), is(notNullValue())); } - assertThat(nodeType, is(notNullValue())); - assertThat(nodeType.getName(), is(template.getName())); - assertThat(nodeType.getDeclaredSupertypes().length, is(template.getDeclaredSupertypeNames().length)); - for (int i = 0; i < template.getDeclaredSupertypeNames().length; i++) { - assertThat(template.getDeclaredSupertypeNames()[i], is(nodeType.getDeclaredSupertypes()[i].getName())); - } - assertThat(template.isMixin(), is(nodeType.isMixin())); - assertThat(template.hasOrderableChildNodes(), is(nodeType.hasOrderableChildNodes())); - - PropertyDefinition[] propertyDefs = nodeType.getDeclaredPropertyDefinitions(); - List propertyTemplates = template.getPropertyDefinitionTemplates(); - - assertThat(propertyDefs.length, is(propertyTemplates.size())); - for (PropertyDefinitionTemplate pt : propertyTemplates) { - JcrPropertyDefinitionTemplate propertyTemplate = (JcrPropertyDefinitionTemplate)pt; - - PropertyDefinition matchingDefinition = null; - for (int i = 0; i < propertyDefs.length; i++) { - PropertyDefinition pd = propertyDefs[i]; - - String ptName = propertyTemplate.getName() == null ? JcrNodeType.RESIDUAL_ITEM_NAME : propertyTemplate.getName(); - if (pd.getName().equals(ptName) && pd.getRequiredType() == propertyTemplate.getRequiredType() - && pd.isMultiple() == propertyTemplate.isMultiple()) { - matchingDefinition = pd; - break; - } - } - - comparePropertyTemplateToPropertyDefinition(propertyTemplate, (JcrPropertyDefinition)matchingDefinition); - } - - NodeDefinition[] childNodeDefs = nodeType.getDeclaredChildNodeDefinitions(); - List childNodeTemplates = template.getNodeDefinitionTemplates(); - - assertThat(childNodeDefs.length, is(childNodeTemplates.size())); - for (NodeDefinitionTemplate nt : childNodeTemplates) { - JcrNodeDefinitionTemplate childNodeTemplate = (JcrNodeDefinitionTemplate)nt; - - NodeDefinition matchingDefinition = null; - for (int i = 0; i < childNodeDefs.length; i++) { - NodeDefinition nd = childNodeDefs[i]; - - String ntName = childNodeTemplate.getName() == null ? JcrNodeType.RESIDUAL_ITEM_NAME : childNodeTemplate.getName(); - if (nd.getName().equals(ntName) && nd.allowsSameNameSiblings() == childNodeTemplate.allowsSameNameSiblings()) { - - if (nd.getRequiredPrimaryTypes().length != childNodeTemplate.getRequiredPrimaryTypeNames().length) continue; - - boolean matchesOnRequiredTypes = true; - for (int j = 0; j < nd.getRequiredPrimaryTypes().length; j++) { - String ndName = nd.getRequiredPrimaryTypes()[j].getName(); - String tempName = childNodeTemplate.getRequiredPrimaryTypeNames()[j]; - if (!ndName.equals(tempName)) { - matchesOnRequiredTypes = false; - break; - } - } - - if (matchesOnRequiredTypes) { - matchingDefinition = nd; - break; - } - } - } - - compareNodeTemplateToNodeDefinition(childNodeTemplate, (JcrNodeDefinition)matchingDefinition); - } - - } - - private void comparePropertyTemplateToPropertyDefinition( JcrPropertyDefinitionTemplate template, - JcrPropertyDefinition definition ) { - - assertThat(definition, is(notNullValue())); - assertThat(definition.getDeclaringNodeType(), is(notNullValue())); - // Had to match on name to even get to the definition - // assertThat(template.getName(), is(definition.getName())); - - assertThat(emptyIfNull(template.getValueConstraints()), is(definition.getValueConstraints())); - assertThat(template.getOnParentVersion(), is(definition.getOnParentVersion())); - assertThat(template.getRequiredType(), is(definition.getRequiredType())); - assertThat(template.isAutoCreated(), is(definition.isAutoCreated())); - assertThat(template.isMandatory(), is(definition.isMandatory())); - assertThat(template.isMultiple(), is(definition.isMultiple())); - assertThat(template.isProtected(), is(definition.isProtected())); - } - - private void compareNodeTemplateToNodeDefinition( JcrNodeDefinitionTemplate template, - JcrNodeDefinition definition ) { - assertThat(definition, is(notNullValue())); - assertThat(definition.getDeclaringNodeType(), is(notNullValue())); - // Had to match on name to even get to the definition - // assertThat(template.getName(), is(definition.getName())); - - assertThat(template.getOnParentVersion(), is(definition.getOnParentVersion())); - assertThat(template.isAutoCreated(), is(definition.isAutoCreated())); - assertThat(template.isMandatory(), is(definition.isMandatory())); - assertThat(template.isProtected(), is(definition.isProtected())); - - assertThat(template.getDefaultPrimaryType(), is(definition.getDefaultPrimaryType())); - assertThat(template.allowsSameNameSiblings(), is(definition.allowsSameNameSiblings())); - - // assertThat(template.getRequiredPrimaryTypeNames(), is(definition.getRequiredPrimaryTypeNames())); - - } - - private String[] emptyIfNull( String[] incoming ) { - if (incoming != null) return incoming; - return new String[0]; + NodeTypeAssertion.compareTemplateToNodeType(template, nodeType); } } Index: modeshape-jcr/src/test/java/org/modeshape/jcr/WorkspaceLockManagerTest.java =================================================================== --- modeshape-jcr/src/test/java/org/modeshape/jcr/WorkspaceLockManagerTest.java (revision 2072) +++ modeshape-jcr/src/test/java/org/modeshape/jcr/WorkspaceLockManagerTest.java (working copy) @@ -101,7 +101,9 @@ public class WorkspaceLockManagerTest { }); // Stub out the repository, since we only need a few methods ... - repoTypeManager = new RepositoryNodeTypeManager(repository, true); + Path nodeTypesPath = context.getValueFactories().getPathFactory().createAbsolutePath(JcrLexicon.SYSTEM, + JcrLexicon.NODE_TYPES); + repoTypeManager = new RepositoryNodeTypeManager(repository, nodeTypesPath, true); when(repository.getRepositoryTypeManager()).thenReturn(repoTypeManager); @@ -153,8 +155,6 @@ public class WorkspaceLockManagerTest { when(session.getExecutionContext()).thenReturn(context); workspaceLockManager.lockNodeInRepository(session, validUuid, lockOwner, isDeep); - // At the moment, a VerifyWorkspaceRequest is being executed when the graph is created. - executedRequests.poll(); assertNextRequestIsLock(validLocation, LockScope.SELF_ONLY, 0); } @@ -163,8 +163,6 @@ public class WorkspaceLockManagerTest { ModeShapeLock lock = workspaceLockManager.createLock("testOwner", UUID.randomUUID(), validUuid, false, false); workspaceLockManager.unlockNodeInRepository(context, lock); - // At the moment, a VerifyWorkspaceRequest is being executed when the graph is created. - executedRequests.poll(); assertNextRequestIsUnlock(validLocation); } Index: modeshape-jcr/src/test/resources/tck/default/configRepository.xml =================================================================== --- modeshape-jcr/src/test/resources/tck/default/configRepository.xml (revision 2072) +++ modeshape-jcr/src/test/resources/tck/default/configRepository.xml (working copy) @@ -72,7 +72,7 @@ - +