/*
* ModeShape (http://www.modeshape.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.modeshape.jcr;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.jcr.RepositoryException;
import javax.jcr.UnsupportedRepositoryOperationException;
import javax.jcr.Value;
import javax.jcr.nodetype.InvalidNodeTypeDefinitionException;
import javax.jcr.nodetype.NoSuchNodeTypeException;
import javax.jcr.nodetype.NodeDefinition;
import javax.jcr.nodetype.NodeType;
import javax.jcr.nodetype.NodeTypeDefinition;
import javax.jcr.nodetype.PropertyDefinition;
import javax.jcr.query.InvalidQueryException;
import org.modeshape.common.annotation.GuardedBy;
import org.modeshape.common.annotation.ThreadSafe;
import org.modeshape.common.logging.Logger;
import org.modeshape.common.util.CheckArg;
import org.modeshape.jcr.api.query.qom.QueryCommand;
import org.modeshape.jcr.cache.NodeCache;
import org.modeshape.jcr.cache.NodeKey;
import org.modeshape.jcr.cache.RepositoryCache;
import org.modeshape.jcr.cache.SessionCache;
import org.modeshape.jcr.cache.change.Change;
import org.modeshape.jcr.cache.change.ChangeSet;
import org.modeshape.jcr.cache.change.ChangeSetListener;
import org.modeshape.jcr.cache.change.NodeAdded;
import org.modeshape.jcr.cache.change.NodeRemoved;
import org.modeshape.jcr.cache.change.PropertyChanged;
import org.modeshape.jcr.query.CancellableQuery;
import org.modeshape.jcr.query.NodeSequence;
import org.modeshape.jcr.query.NodeSequence.Batch;
import org.modeshape.jcr.query.QueryResults;
import org.modeshape.jcr.query.model.TypeSystem;
import org.modeshape.jcr.query.parse.BasicSqlQueryParser;
import org.modeshape.jcr.query.parse.QueryParser;
import org.modeshape.jcr.query.validate.Schemata;
import org.modeshape.jcr.value.Name;
import org.modeshape.jcr.value.NameFactory;
import org.modeshape.jcr.value.Path;
import org.modeshape.jcr.value.PathFactory;
import org.modeshape.jcr.value.ValueFactory;
/**
* The {@link RepositoryNodeTypeManager} is the maintainer of node type information for the entire repository at run-time. The
* repository manager maintains a list of all node types and the ability to retrieve node types by {@link Name}.
* <p>
* The JCR 1.0 and 2.0 specifications both require that node type information be shared across all sessions within a repository
* and that the {@link javax.jcr.nodetype.NodeTypeManager} perform operations based on the string versions of {@link Name}s based
* on the permanent (workspace-scoped) and transient (session-scoped) namespace mappings. ModeShape achieves this by maintaining a
* single master repository of all node type information (the {@link RepositoryNodeTypeManager}) and per-session wrappers (
* {@link JcrNodeTypeManager}) for this master repository that perform {@link String} to {@link Name} translation based on the
* {@link javax.jcr.Session}'s transient mappings and then delegating node type lookups to the repository manager.
* </p>
*/
@ThreadSafe
class RepositoryNodeTypeManager implements ChangeSetListener, NodeTypes.Supplier {
private final JcrRepository.RunningState repository;
private final ExecutionContext context;
private final String systemWorkspaceName;
private final Path nodeTypesPath;
private final NameFactory nameFactory;
private final Logger logger = Logger.getLogger(getClass());
private final ReadWriteLock nodeTypesLock = new ReentrantReadWriteLock();
@GuardedBy( "nodeTypesLock" )
private volatile NodeTypes nodeTypesCache;
private final QueryParser queryParser;
private final boolean includeColumnsForInheritedProperties;
private final boolean includePseudoColumnsInSelectStar;
private volatile NodeTypeSchemata schemata;
private final CopyOnWriteArrayList<NodeTypes.Listener> listeners = new CopyOnWriteArrayList<>();
RepositoryNodeTypeManager( JcrRepository.RunningState repository,
boolean includeColumnsForInheritedProperties,
boolean includePseudoColumnsInSelectStar ) {
this.repository = repository;
this.context = repository.context();
this.nameFactory = this.context.getValueFactories().getNameFactory();
this.systemWorkspaceName = this.repository.repositoryCache().getSystemWorkspaceName();
PathFactory pathFactory = this.context.getValueFactories().getPathFactory();
this.nodeTypesPath = pathFactory.createAbsolutePath(JcrLexicon.SYSTEM, JcrLexicon.NODE_TYPES);
this.nodeTypesCache = new NodeTypes(this.context);
this.includeColumnsForInheritedProperties = includeColumnsForInheritedProperties;
this.includePseudoColumnsInSelectStar = includePseudoColumnsInSelectStar;
queryParser = new BasicSqlQueryParser();
}
RepositoryNodeTypeManager with( JcrRepository.RunningState repository,
boolean includeColumnsForInheritedProperties,
boolean includePseudoColumnsInSelectStar ) {
assert this.systemWorkspaceName.equals(repository.repositoryCache().getSystemWorkspaceName());
PathFactory pathFactory = repository.context().getValueFactories().getPathFactory();
Path nodeTypesPath = pathFactory.createAbsolutePath(JcrLexicon.SYSTEM, JcrLexicon.NODE_TYPES);
assert this.nodeTypesPath.equals(nodeTypesPath);
RepositoryNodeTypeManager result = new RepositoryNodeTypeManager(repository, includeColumnsForInheritedProperties,
includePseudoColumnsInSelectStar);
// Now copy the node types from this cache into the new manager's cache ...
// (If we didn't do this, we'd have to refresh from the system storage)
result.nodeTypesCache = result.nodeTypesCache.with(this.nodeTypesCache.getAllNodeTypes());
// Do not copy the listeners
return result;
}
protected final ValueFactory<String> strings() {
return this.context.getValueFactories().getStringFactory();
}
@Override
public NodeTypes getNodeTypes() {
return nodeTypesCache;
}
/**
* Add a listener that will be notified when the NodeTypes changes. Listeners will be called in a single thread, and should do
* almost no work.
*
* @param listener the new listener
* @return true if the listener was registered, or false if {@code listener} is null or if it is already registered
*/
final boolean registerListener( NodeTypes.Listener listener ) {
return listener != null ? this.listeners.addIfAbsent(listener) : false;
}
/**
* Remove an existing listener to NodeTypes changes.
*
* @param listener the existing listener
* @return true if the listener was removed, or false otherwise
*/
final boolean unregisterListener( NodeTypes.Listener listener ) {
return this.listeners.remove(listener);
}
private void notifiyListeners() {
for (NodeTypes.Listener listener : this.listeners) {
assert listener != null;
try {
listener.notify(nodeTypesCache);
} catch (RuntimeException e) {
logger.error(e, JcrI18n.errorNotifyingNodeTypesListener, listener);
}
}
}
/**
* Allows the collection of node types to be unregistered if they are not referenced by other node types as supertypes,
* default primary types of child nodes, or required primary types of child nodes.
*
* @param nodeTypeNames the names of the node types to be unregistered
* @param failIfNodeTypesAreUsed true if this method should fail to unregister the named node types if any of the node types
* are still in use by nodes, or false if this method should not perform such a check
* @throws NoSuchNodeTypeException if any of the node type names do not correspond to a registered node type
* @throws InvalidNodeTypeDefinitionException if any of the node types with the given names cannot be unregistered because
* they are the supertype, one of the required primary types, or a default primary type of a node type that is not
* being unregistered.
* @throws RepositoryException if any other error occurs
*/
void unregisterNodeType( Collection<Name> nodeTypeNames,
boolean failIfNodeTypesAreUsed )
throws NoSuchNodeTypeException, InvalidNodeTypeDefinitionException, RepositoryException {
CheckArg.isNotNull(nodeTypeNames, "nodeTypeNames");
if (nodeTypeNames.isEmpty()) return;
if (failIfNodeTypesAreUsed) {
long start = System.nanoTime();
// Search the content graph to make sure that this type isn't being used
for (Name nodeTypeName : nodeTypeNames) {
if (isNodeTypeInUse(nodeTypeName)) {
String name = nodeTypeName.getString(context.getNamespaceRegistry());
throw new InvalidNodeTypeDefinitionException(JcrI18n.cannotUnregisterInUseType.text(name));
}
}
long time = TimeUnit.MILLISECONDS.convert(Math.abs(System.nanoTime() - start), TimeUnit.NANOSECONDS);
logger.debug("{0} milliseconds to check if any of these node types are unused before unregistering them: {1}", time,
nodeTypeNames);
}
try {
/*
* Grab an exclusive lock on this data to keep other nodes from being added/saved while the unregistration checks are occurring
*/
List<JcrNodeType> removedNodeTypes = new ArrayList<JcrNodeType>(nodeTypeNames.size());
nodeTypesLock.writeLock().lock();
final NodeTypes nodeTypes = this.nodeTypesCache;
for (Name nodeTypeName : nodeTypeNames) {
/*
* Check that the type names are valid
*/
if (nodeTypeName == null) {
throw new NoSuchNodeTypeException(JcrI18n.invalidNodeTypeName.text());
}
String name = nodeTypeName.getString(context.getNamespaceRegistry());
JcrNodeType foundNodeType = nodeTypes.getNodeType(nodeTypeName);
if (foundNodeType == null) {
throw new NoSuchNodeTypeException(JcrI18n.noSuchNodeType.text(name));
}
removedNodeTypes.add(foundNodeType);
/*
* Check that no other node definitions have dependencies on any of the named types
*/
for (JcrNodeType nodeType : nodeTypes.getAllNodeTypes()) {
// If this node is also being unregistered, don't run checks against it
if (nodeTypeNames.contains(nodeType.getInternalName())) {
continue;
}
for (JcrNodeType supertype : nodeType.supertypes()) {
if (nodeTypeName.equals(supertype.getInternalName())) {
throw new InvalidNodeTypeDefinitionException(JcrI18n.cannotUnregisterSupertype.text(name,
nodeType.getName()));
}
}
for (JcrNodeDefinition childNode : nodeType.childNodeDefinitions()) {
NodeType defaultPrimaryType = childNode.getDefaultPrimaryType();
if (defaultPrimaryType != null && name.equals(defaultPrimaryType.getName())) {
throw new InvalidNodeTypeDefinitionException(
JcrI18n.cannotUnregisterDefaultPrimaryType.text(name,
nodeType.getName(),
childNode.getName()));
}
if (childNode.requiredPrimaryTypeNameSet().contains(nodeTypeName)) {
throw new InvalidNodeTypeDefinitionException(
JcrI18n.cannotUnregisterRequiredPrimaryType.text(name,
nodeType.getName(),
childNode.getName()));
}
}
}
}
// Create the new cache ...
NodeTypes newNodeTypes = nodeTypes.without(removedNodeTypes);
// Remove the node types from persistent storage ...
SessionCache system = repository.createSystemSession(context, false);
SystemContent systemContent = new SystemContent(system);
systemContent.unregisterNodeTypes(removedNodeTypes.toArray(new JcrNodeType[removedNodeTypes.size()]));
systemContent.save();
// Now change the cache ...
this.nodeTypesCache = newNodeTypes;
this.schemata = null;
notifiyListeners();
} finally {
nodeTypesLock.writeLock().unlock();
}
}
NodeTypeSchemata getRepositorySchemata() {
// Try reading first, since this will work most of the time ...
if (schemata != null) return schemata;
// This is idempotent, so it's okay not to lock ...
schemata = new NodeTypeSchemata(context, nodeTypesCache, includeColumnsForInheritedProperties,
includePseudoColumnsInSelectStar);
return schemata;
}
void signalNamespaceChanges() {
this.schemata = null;
}
/**
* Check if the named node type is in use in any workspace in the repository
*
* @param nodeTypeName the name of the node type to check
* @return true if at least one node is using that type; false otherwise
* @throws InvalidQueryException if there is an error searching for uses of the named node type
*/
boolean isNodeTypeInUse( Name nodeTypeName ) throws InvalidQueryException {
String nodeTypeString = nodeTypeName.getString(context.getNamespaceRegistry());
String expression = "SELECT * from [" + nodeTypeString + "] LIMIT 1";
TypeSystem typeSystem = context.getValueFactories().getTypeSystem();
// Parsing must be done now ...
QueryCommand command = queryParser.parseQuery(expression, typeSystem);
assert command != null : "Could not parse " + expression;
Schemata schemata = getRepositorySchemata();
// Now query the entire repository for any nodes that use this node type ...
RepositoryCache repoCache = repository.repositoryCache();
RepositoryQueryManager queryManager = repository.queryManager();
Set<String> workspaceNames = repoCache.getWorkspaceNames();
Map<String, NodeCache> overridden = null;
NodeTypes nodeTypes = repository.nodeTypeManager().getNodeTypes();
RepositoryIndexes indexDefns = repository.queryManager().getIndexes();
CancellableQuery query = queryManager.query(context, repoCache, workspaceNames, overridden, command, schemata,
indexDefns, nodeTypes, null, null);
try {
QueryResults result = query.execute();
if (result.isEmpty()) return false;
if (result.getRowCount() < 0) {
// Try to get the first row ...
NodeSequence seq = result.getRows();
Batch batch = seq.nextBatch();
while (batch != null) {
if (batch.hasNext()) return true;
// It's not common for the first batch may be empty, but it's possible. So try the next batch ...
batch = seq.nextBatch();
}
return false;
}
return result.getRowCount() > 0;
} catch (RepositoryException e) {
logger.error(e, JcrI18n.errorCheckingNodeTypeUsage, nodeTypeName, e.getLocalizedMessage());
return true;
}
}
/**
* Registers a new node type or updates an existing node type using the specified definition and returns the resulting
* {@code NodeType} object.
* <p>
* For details, see {@link #registerNodeTypes(Iterable)}.
* </p>
*
* @param ntd the {@code NodeTypeDefinition} to register
* @return the newly registered (or updated) {@code NodeType}
* @throws InvalidNodeTypeDefinitionException if the {@code NodeTypeDefinition} is invalid
* @throws NodeTypeExistsException if <code>allowUpdate</code> is false and the {@code NodeTypeDefinition} specifies a node
* type name that is already registered
* @throws RepositoryException if another error occurs
*/
JcrNodeType registerNodeType( NodeTypeDefinition ntd )
throws InvalidNodeTypeDefinitionException, NodeTypeExistsException, RepositoryException {
return registerNodeType(ntd, true);
}
/**
* Registers a new node type or updates an existing node type using the specified definition and returns the resulting
* {@code NodeType} object.
* <p>
* For details, see {@link #registerNodeTypes(Iterable)}.
* </p>
*
* @param ntd the {@code NodeTypeDefinition} to register
* @param failIfNodeTypeExists indicates whether the registration should proceed if there is already a type with the same
* name; {@code true} indicates that the registration should fail with an error if a node type with the same name
* already exists
* @return the newly registered (or updated) {@code NodeType}
* @throws InvalidNodeTypeDefinitionException if the {@code NodeTypeDefinition} is invalid
* @throws NodeTypeExistsException if <code>allowUpdate</code> is false and the {@code NodeTypeDefinition} specifies a node
* type name that is already registered
* @throws RepositoryException if another error occurs
*/
JcrNodeType registerNodeType( NodeTypeDefinition ntd,
boolean failIfNodeTypeExists )
throws InvalidNodeTypeDefinitionException, NodeTypeExistsException, RepositoryException {
assert ntd != null;
List<JcrNodeType> result = registerNodeTypes(Collections.singletonList(ntd), failIfNodeTypeExists, false, true);
return result.isEmpty() ? null : result.get(0);
}
/**
* Registers or updates the specified {@code Collection} of {@link NodeTypeDefinition} objects.
* <p>
* This method is used to register or update a set of node types with mutual dependencies.
* </p>
* <p>
* The effect of this method is "all or nothing"; if an error occurs, no node types are registered or updated.
* </p>
* <p>
* <b>ModeShape Implementation Notes</b>
* </p>
* <p>
* ModeShape currently supports registration of batches of types with some constraints. ModeShape will allow types to be
* registered if they meet the following criteria:
* <ol>
* <li>The batch must consist of {@code NodeTypeDefinitionTemplate node type definition templates} created through the user's
* JCR session.</li>
* <li>Existing types cannot be modified in-place - They must be unregistered and re-registered</li>
* <li>Types must have a non-null, non-empty name</li>
* <li>If a primary item name is specified for the node type, it must match the name of a property OR a child node, not both</li>
* <li>Each type must have a valid set of supertypes - that is, the type's supertypes must meet the following criteria:
* <ol>
* <li>The type must have at least one supertype (unless the type is {@code nt:base}.</li>
* <li>No two supertypes {@code t1} and {@code t2} can declare each declare a property ({@code p1} and {@code p2}) with the
* same name and cardinality ({@code p1.isMultiple() == p2.isMultiple()}). Note that this does prohibit each {@code t1} and
* {@code t2} from having a common supertype (or super-supertype, etc.) that declares a property).</li>
* <li>No two supertypes {@code t1} and {@code t2} can declare each declare a child node ({@code n1} and {@code n2}) with the
* same name and SNS status ({@code p1.allowsSameNameSiblings() == p2.allowsSameNameSiblings()}). Note that this does prohibit
* each {@code t1} and {@code t2} from having a common supertype (or super-supertype, etc.) that declares a child node).</li>
* </ol>
* </li>
* <li>Each type must have a valid set of properties - that is, the type's properties must meet the following criteria:
* <ol>
* <li>Residual property definitions cannot be mandatory</li>
* <li>If the property is auto-created, it must specify a default value</li>
* <li>If the property is single-valued, it can only specify a single default value</li>
* <li>If the property overrides an existing property definition from a supertype, the new definition must be mandatory if the
* old definition was mandatory</li>
* <li>The property cannot override an existing property definition from a supertype if the ancestor definition is protected</li>
* <li>If the property overrides an existing property definition from a supertype that specifies value constraints, the new
* definition must have the same value constraints as the old definition. <i>This requirement may be relaxed in a future
* version of ModeShape.</i></li>
* <li>If the property overrides an existing property definition from a supertype, the new definition must have the same
* required type as the old definition or a required type that can ALWAYS be cast to the required type of the ancestor (see
* section 3.6.4 of the JCR 2.0 specification)</li>
* </ol>
* Note that an empty set of properties would meet the above criteria.</li>
* <li>The type must have a valid set of child nodes - that is, the types's child nodes must meet the following criteria:
* <ol>
* <li>Residual child node definitions cannot be mandatory</li>
* <li>If the child node is auto-created, it must specify a default primary type name</li>
* <li>If the child node overrides an existing child node definition from a supertype, the new definition must be mandatory if
* the old definition was mandatory</li>
* <li>The child node cannot override an existing child node definition from a supertype if the ancestor definition is
* protected</li>
* <li>If the child node overrides an existing child node definition from a supertype, the required primary types of the new
* definition must be more restrictive than the required primary types of the old definition - that is, the new primary types
* must defined such that any type that satisfies all of the required primary types for the new definition must also satisfy
* all of the required primary types for the old definition. This requirement is analogous to the requirement that overriding
* property definitions have a required type that is always convertible to the required type of the overridden definition.</li>
* </ol>
* Note that an empty set of child nodes would meet the above criteria.</li>
* </p>
*
* @param nodeTypeDefns the {@link NodeTypeDefinition node type definitions} to register
* @return the newly registered (or updated) {@link NodeType NodeTypes}
* @throws UnsupportedRepositoryOperationException if {@code allowUpdates == true}. ModeShape does not support this capability
* at this time but the parameter has been retained for API compatibility.
* @throws InvalidNodeTypeDefinitionException if the {@link NodeTypeDefinition} is invalid
* @throws NodeTypeExistsException if <code>allowUpdate</code> is false and the {@link NodeTypeDefinition} specifies a node
* type name that is already registered
* @throws RepositoryException if another error occurs
*/
List<JcrNodeType> registerNodeTypes( Iterable<NodeTypeDefinition> nodeTypeDefns )
throws InvalidNodeTypeDefinitionException, NodeTypeExistsException, RepositoryException {
return registerNodeTypes(nodeTypeDefns, true, false, true);
}
List<JcrNodeType> registerNodeTypes( Iterable<NodeTypeDefinition> nodeTypeDefns,
boolean failIfNodeTypeDefinitionsExist,
boolean skipIfNodeTypeDefinitionExists,
boolean persist )
throws InvalidNodeTypeDefinitionException, NodeTypeExistsException, RepositoryException {
if (nodeTypeDefns == null) {
return Collections.emptyList();
}
List<JcrNodeType> typesPendingRegistration = new ArrayList<JcrNodeType>();
try {
nodeTypesLock.writeLock().lock();
final NodeTypes nodeTypes = this.nodeTypesCache;
for (NodeTypeDefinition nodeTypeDefn : nodeTypeDefns) {
if (nodeTypeDefn instanceof JcrNodeTypeTemplate) {
// Switch to use this context, so names are properly prefixed ...
nodeTypeDefn = ((JcrNodeTypeTemplate)nodeTypeDefn).with(context);
}
Name internalName = nodeTypes.nameFactory().create(nodeTypeDefn.getName());
if (internalName == null || internalName.getLocalName().length() == 0) {
throw new InvalidNodeTypeDefinitionException(JcrI18n.invalidNodeTypeName.text());
}
boolean found = nodeTypes.hasNodeType(internalName);
if (found && failIfNodeTypeDefinitionsExist) {
String name = nodeTypeDefn.getName();
throw new NodeTypeExistsException(internalName, JcrI18n.nodeTypeAlreadyExists.text(name));
}
if (found && skipIfNodeTypeDefinitionExists) continue;
List<JcrNodeType> supertypes = nodeTypes.supertypesFor(nodeTypeDefn, typesPendingRegistration);
JcrNodeType nodeType = nodeTypeFrom(nodeTypeDefn, supertypes);
typesPendingRegistration.add(nodeType);
}
if (!typesPendingRegistration.isEmpty()) {
// Make sure the nodes have primary types that are either already registered, or pending registration ...
validateTypes(typesPendingRegistration);
// Validate each of types that should be registered
for (JcrNodeType typePendingRegistration : typesPendingRegistration) {
nodeTypes.validate(typePendingRegistration, Arrays.asList(typePendingRegistration.getDeclaredSupertypes()),
typesPendingRegistration);
}
SystemContent system = null;
if (persist) {
SessionCache systemCache = repository.createSystemSession(context, false);
system = new SystemContent(systemCache);
}
for (JcrNodeType nodeType : typesPendingRegistration) {
if (system != null) system.store(nodeType, true);
}
// Create the new cache ...
NodeTypes newNodeTypes = nodeTypes.with(typesPendingRegistration);
// Save the changes ...
if (system != null) system.save();
// And finally update the capabilities cache ...
this.nodeTypesCache = newNodeTypes;
this.schemata = null;
notifiyListeners();
}
} finally {
nodeTypesLock.writeLock().unlock();
}
return typesPendingRegistration;
}
private void validateTypes( List<JcrNodeType> typesPendingRegistration ) throws RepositoryException {
NodeTypes nodeTypes = this.nodeTypesCache;
for (JcrNodeType nodeType : typesPendingRegistration) {
for (JcrNodeDefinition nodeDef : nodeType.getDeclaredChildNodeDefinitions()) {
Name[] requiredPrimaryTypeNames = nodeDef.requiredPrimaryTypeNames();
for (Name primaryTypeName : requiredPrimaryTypeNames) {
JcrNodeType requiredPrimaryType = nodeTypes.findTypeInMapOrList(primaryTypeName, typesPendingRegistration);
if (requiredPrimaryType == null) {
String msg = JcrI18n.invalidPrimaryTypeName.text(primaryTypeName, nodeType.getName());
throw new RepositoryException(msg);
}
}
}
if (nodeType.isMixin()) {
for (NodeType superType : nodeType.getSupertypes()) {
if (!superType.isMixin()) {
String msg = JcrI18n.invalidMixinSupertype.text(nodeType.getName(), superType.getName());
throw new RepositoryException(msg);
}
}
}
}
}
private JcrNodeType nodeTypeFrom( NodeTypeDefinition nodeType,
List<JcrNodeType> supertypes ) throws RepositoryException {
PropertyDefinition[] propDefns = nodeType.getDeclaredPropertyDefinitions();
NodeDefinition[] childDefns = nodeType.getDeclaredChildNodeDefinitions();
List<JcrPropertyDefinition> properties = new ArrayList<JcrPropertyDefinition>();
List<JcrNodeDefinition> childNodes = new ArrayList<JcrNodeDefinition>();
if (propDefns != null) {
for (PropertyDefinition propDefn : propDefns) {
properties.add(propertyDefinitionFrom(propDefn));
}
}
if (childDefns != null) {
for (NodeDefinition childNodeDefn : childDefns) {
childNodes.add(childNodeDefinitionFrom(childNodeDefn));
}
}
Name name = nameFactory.create(nodeType.getName());
Name primaryItemName = nameFactory.create(nodeType.getPrimaryItemName());
boolean mixin = nodeType.isMixin();
boolean isAbstract = nodeType.isAbstract();
boolean queryable = nodeType.isQueryable();
boolean orderableChildNodes = nodeType.hasOrderableChildNodes();
NodeKey prototypeKey = repository.repositoryCache().getSystemKey();
return new JcrNodeType(prototypeKey, this.context, null, this, name, supertypes, primaryItemName, childNodes, properties,
mixin, isAbstract, queryable, orderableChildNodes);
}
private JcrPropertyDefinition propertyDefinitionFrom( PropertyDefinition propDefn ) throws RepositoryException {
Name propertyName = nameFactory.create(propDefn.getName());
int onParentVersionBehavior = propDefn.getOnParentVersion();
int requiredType = propDefn.getRequiredType();
boolean mandatory = propDefn.isMandatory();
boolean multiple = propDefn.isMultiple();
boolean autoCreated = propDefn.isAutoCreated();
boolean isProtected = propDefn.isProtected();
boolean fullTextSearchable = propDefn.isFullTextSearchable();
boolean queryOrderable = propDefn.isQueryOrderable();
Value[] defaultValues = propDefn.getDefaultValues();
JcrValue[] jcrDefaultValues = null;
if (defaultValues != null) {
jcrDefaultValues = new JcrValue[defaultValues.length];
for (int i = 0; i != defaultValues.length; ++i) {
Value value = defaultValues[i];
jcrDefaultValues[i] = new JcrValue(this.context.getValueFactories(), value);
}
}
String[] valueConstraints = propDefn.getValueConstraints();
String[] queryOperators = propDefn.getAvailableQueryOperators();
if (valueConstraints == null) valueConstraints = new String[0];
NodeKey prototypeKey = repository.repositoryCache().getSystemKey();
return new JcrPropertyDefinition(this.context, null, prototypeKey, propertyName, onParentVersionBehavior, autoCreated,
mandatory, isProtected, jcrDefaultValues, requiredType, valueConstraints, multiple,
fullTextSearchable, queryOrderable, queryOperators);
}
private JcrNodeDefinition childNodeDefinitionFrom( NodeDefinition childNodeDefn ) {
Name childNodeName = nameFactory.create(childNodeDefn.getName());
Name defaultPrimaryTypeName = nameFactory.create(childNodeDefn.getDefaultPrimaryTypeName());
int onParentVersion = childNodeDefn.getOnParentVersion();
boolean mandatory = childNodeDefn.isMandatory();
boolean allowsSns = childNodeDefn.allowsSameNameSiblings();
boolean autoCreated = childNodeDefn.isAutoCreated();
boolean isProtected = childNodeDefn.isProtected();
Name[] requiredTypes;
String[] requiredTypeNames = childNodeDefn.getRequiredPrimaryTypeNames();
if (requiredTypeNames != null) {
List<Name> names = new ArrayList<Name>(requiredTypeNames.length);
for (String typeName : requiredTypeNames) {
names.add(nameFactory.create(typeName));
}
requiredTypes = names.toArray(new Name[names.size()]);
} else {
requiredTypes = new Name[0];
}
NodeKey prototypeKey = repository.repositoryCache().getSystemKey();
return new JcrNodeDefinition(this.context, null, prototypeKey, childNodeName, onParentVersion, autoCreated, mandatory,
isProtected, allowsSns, defaultPrimaryTypeName, requiredTypes);
}
@Override
public void notify( ChangeSet changeSet ) {
if (!systemWorkspaceName.equals(changeSet.getWorkspaceName())) {
// The change does not affect the 'system' workspace, so skip it ...
return;
}
if (context.getProcessId().equals(changeSet.getProcessKey())) {
// We generated these changes, so skip them ...
return;
}
// Now process the changes ...
Set<Name> nodeTypesToRefresh = new HashSet<Name>();
Set<Name> nodeTypesToDelete = new HashSet<Name>();
for (Change change : changeSet) {
if (change instanceof NodeAdded) {
NodeAdded added = (NodeAdded)change;
Path addedPath = added.getPath();
if (nodeTypesPath.isAncestorOf(addedPath)) {
// Get the name of the node type ...
Name nodeTypeName = addedPath.getSegment(2).getName();
nodeTypesToRefresh.add(nodeTypeName);
}
} else if (change instanceof NodeRemoved) {
NodeRemoved removed = (NodeRemoved)change;
Path removedPath = removed.getPath();
if (nodeTypesPath.isAncestorOf(removedPath)) {
// Get the name of the node type ...
Name nodeTypeName = removedPath.getSegment(2).getName();
if (removedPath.size() == 3) {
nodeTypesToDelete.add(nodeTypeName);
} else {
// It's a child defn or property defn ...
if (!nodeTypesToDelete.contains(nodeTypeName)) {
// The child defn or property defn is being removed but the node type is not ...
nodeTypesToRefresh.add(nodeTypeName);
}
}
}
} else if (change instanceof PropertyChanged) {
PropertyChanged propChanged = (PropertyChanged)change;
Path changedPath = propChanged.getPathToNode();
if (nodeTypesPath.isAncestorOf(changedPath)) {
// Get the name of the node type ...
Name nodeTypeName = changedPath.getSegment(2).getName();
nodeTypesToRefresh.add(nodeTypeName);
}
} // we don't care about node moves (don't happen) or property added/removed (handled by node add/remove)
}
if (nodeTypesToRefresh.isEmpty() && nodeTypesToDelete.isEmpty()) {
// No changes
return;
}
// There were at least some changes ...
this.nodeTypesLock.writeLock().lock();
try {
// Re-register the node types that were changed or added ...
SessionCache systemCache = repository.createSystemSession(context, false);
SystemContent system = new SystemContent(systemCache);
Collection<NodeTypeDefinition> nodeTypes = system.readNodeTypes(nodeTypesToRefresh);
registerNodeTypes(nodeTypes, false, false, false);
// Unregister those that were removed ...
unregisterNodeType(nodeTypesToDelete, false);
} catch (Throwable e) {
logger.error(e, JcrI18n.errorRefreshingNodeTypes, repository.name());
} finally {
this.nodeTypesLock.writeLock().unlock();
}
}
/**
* Refresh the node types from the stored representation.
*
* @return true if there was at least one node type found, or false if there were none
*/
protected boolean refreshFromSystem() {
this.nodeTypesLock.writeLock().lock();
try {
// Re-read and re-register all of the node types ...
SessionCache systemCache = repository.createSystemSession(context, true);
SystemContent system = new SystemContent(systemCache);
Collection<NodeTypeDefinition> nodeTypes = system.readAllNodeTypes();
if (nodeTypes.isEmpty()) return false;
registerNodeTypes(nodeTypes, false, false, false);
return true;
} catch (Throwable e) {
logger.error(e, JcrI18n.errorRefreshingNodeTypes, repository.name());
return false;
} finally {
this.nodeTypesLock.writeLock().unlock();
}
}
@Override
public String toString() {
return getNodeTypes().toString();
}
}