/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.jackrabbit.core.nodetype;
import org.apache.jackrabbit.core.value.InternalValue;
import org.apache.jackrabbit.spi.Name;
import org.apache.jackrabbit.spi.QValueConstraint;
import org.apache.jackrabbit.spi.QItemDefinition;
import org.apache.jackrabbit.spi.QPropertyDefinition;
import org.apache.jackrabbit.spi.QNodeDefinition;
import org.apache.jackrabbit.spi.QNodeTypeDefinition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.nodetype.ConstraintViolationException;
import javax.jcr.nodetype.NoSuchNodeTypeException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
import java.util.Set;
import java.util.HashSet;
/**
* An <code>EffectiveNodeType</code> represents one or more
* <code>NodeType</code>s as one 'effective' node type where inheritance
* is resolved.
* <p/>
* Instances of <code>EffectiveNodeType</code> are immutable.
*/
public class EffectiveNodeType implements Cloneable {
private static Logger log = LoggerFactory.getLogger(EffectiveNodeType.class);
// list of explicitly aggregated {i.e. merged) node types
private final TreeSet<Name> mergedNodeTypes;
// list of implicitly aggregated {through inheritance) node types
private final TreeSet<Name> inheritedNodeTypes;
// list of all either explicitly (through aggregation) or implicitly
// (through inheritance) included node types.
private final TreeSet<Name> allNodeTypes;
// map of named item definitions (maps name to list of definitions)
private final HashMap<Name, List<QItemDefinition>> namedItemDefs;
// list of unnamed item definitions (i.e. residual definitions)
private final ArrayList<QItemDefinition> unnamedItemDefs;
// flag indicating whether any included node type supports orderable child nodes
private boolean orderableChildNodes;
private Name primaryItemName;
/**
* private constructor.
*/
private EffectiveNodeType() {
mergedNodeTypes = new TreeSet<Name>();
inheritedNodeTypes = new TreeSet<Name>();
allNodeTypes = new TreeSet<Name>();
namedItemDefs = new HashMap<Name, List<QItemDefinition>>();
unnamedItemDefs = new ArrayList<QItemDefinition>();
orderableChildNodes = false;
primaryItemName = null;
}
/**
* Package private factory method.
* <p/>
* Creates an effective node type representation of a node type definition.
* Note that the definitions of all referenced node types must be contained
* in <code>ntdCache</code>.
*
* @param ntd node type definition
* @param entCache cache of already-built effective node types
* @param ntdCache cache of node type definitions, used to resolve dependencies
* @return an effective node type representation of the given node type definition.
* @throws NodeTypeConflictException if the node type definition is invalid,
* e.g. due to ambiguous child definitions.
* @throws NoSuchNodeTypeException if a node type reference (e.g. a supertype)
* could not be resolved.
*/
static EffectiveNodeType create(QNodeTypeDefinition ntd,
EffectiveNodeTypeCache entCache,
Map<Name, QNodeTypeDefinition> ntdCache)
throws NodeTypeConflictException, NoSuchNodeTypeException {
// create empty effective node type instance
EffectiveNodeType ent = new EffectiveNodeType();
Name ntName = ntd.getName();
// prepare new instance
ent.mergedNodeTypes.add(ntName);
ent.allNodeTypes.add(ntName);
// map of all item definitions (maps id to definition)
// used to effectively detect ambiguous child definitions where
// ambiguity is defined in terms of definition identity
Set<QItemDefinition> itemDefs = new HashSet<QItemDefinition>();
QNodeDefinition[] cnda = ntd.getChildNodeDefs();
for (QNodeDefinition aCnda : cnda) {
// check if child node definition would be ambiguous within
// this node type definition
if (itemDefs.contains(aCnda)) {
// conflict
String msg;
if (aCnda.definesResidual()) {
msg = ntName + " contains ambiguous residual child node definitions";
} else {
msg = ntName + " contains ambiguous definitions for child node named "
+ aCnda.getName();
}
log.debug(msg);
throw new NodeTypeConflictException(msg);
} else {
itemDefs.add(aCnda);
}
if (aCnda.definesResidual()) {
// residual node definition
ent.unnamedItemDefs.add(aCnda);
} else {
// named node definition
Name name = aCnda.getName();
List<QItemDefinition> defs = ent.namedItemDefs.get(name);
if (defs == null) {
defs = new ArrayList<QItemDefinition>();
ent.namedItemDefs.put(name, defs);
}
if (defs.size() > 0) {
/**
* there already exists at least one definition with that
* name; make sure none of them is auto-create
*/
for (QItemDefinition def : defs) {
if (aCnda.isAutoCreated() || def.isAutoCreated()) {
// conflict
String msg = "There are more than one 'auto-create' item definitions for '"
+ name + "' in node type '" + ntName + "'";
log.debug(msg);
throw new NodeTypeConflictException(msg);
}
}
}
defs.add(aCnda);
}
}
QPropertyDefinition[] pda = ntd.getPropertyDefs();
for (QPropertyDefinition aPda : pda) {
// check if property definition would be ambiguous within
// this node type definition
if (itemDefs.contains(aPda)) {
// conflict
String msg;
if (aPda.definesResidual()) {
msg = ntName + " contains ambiguous residual property definitions";
} else {
msg = ntName + " contains ambiguous definitions for property named "
+ aPda.getName();
}
log.debug(msg);
throw new NodeTypeConflictException(msg);
} else {
itemDefs.add(aPda);
}
if (aPda.definesResidual()) {
// residual property definition
ent.unnamedItemDefs.add(aPda);
} else {
// named property definition
Name name = aPda.getName();
List<QItemDefinition> defs = ent.namedItemDefs.get(name);
if (defs == null) {
defs = new ArrayList<QItemDefinition>();
ent.namedItemDefs.put(name, defs);
}
if (defs.size() > 0) {
/**
* there already exists at least one definition with that
* name; make sure none of them is auto-create
*/
for (QItemDefinition def : defs) {
if (aPda.isAutoCreated() || def.isAutoCreated()) {
// conflict
String msg = "There are more than one 'auto-create' item definitions for '"
+ name + "' in node type '" + ntName + "'";
log.debug(msg);
throw new NodeTypeConflictException(msg);
}
}
}
defs.add(aPda);
}
}
// resolve supertypes recursively
Name[] supertypes = ntd.getSupertypes();
if (supertypes.length > 0) {
EffectiveNodeType base =
NodeTypeRegistry.getEffectiveNodeType(supertypes, entCache, ntdCache);
ent.internalMerge(base, true);
}
// resolve 'orderable child nodes' attribute value (JCR-1947)
if (ntd.hasOrderableChildNodes()) {
ent.orderableChildNodes = true;
} else {
Name[] nta = ent.getInheritedNodeTypes();
for (Name aNta : nta) {
QNodeTypeDefinition def = ntdCache.get(aNta);
if (def.hasOrderableChildNodes()) {
ent.orderableChildNodes = true;
break;
}
}
}
// resolve 'primary item' attribute value (JCR-1947)
if (ntd.getPrimaryItemName() != null) {
ent.primaryItemName = ntd.getPrimaryItemName();
} else {
Name[] nta = ent.getInheritedNodeTypes();
for (Name aNta : nta) {
QNodeTypeDefinition def = ntdCache.get(aNta);
if (def.getPrimaryItemName() != null) {
ent.primaryItemName = def.getPrimaryItemName();
break;
}
}
}
// we're done
return ent;
}
/**
* Package private factory method for creating a new 'empty' effective
* node type instance.
*
* @return an 'empty' effective node type instance.
*/
static EffectiveNodeType create() {
return new EffectiveNodeType();
}
/**
* Returns true if any of the included node types supports
* 'orderable child nodes'; returns false otherwise.
* @return <code>true</code> if this effective node type has orderable child nodes
*/
public boolean hasOrderableChildNodes() {
return orderableChildNodes;
}
public Name getPrimaryItemName() {
return primaryItemName;
}
public Name[] getMergedNodeTypes() {
return mergedNodeTypes.toArray(new Name[mergedNodeTypes.size()]);
}
public Name[] getInheritedNodeTypes() {
return inheritedNodeTypes.toArray(new Name[inheritedNodeTypes.size()]);
}
public Name[] getAllNodeTypes() {
return allNodeTypes.toArray(new Name[allNodeTypes.size()]);
}
public QItemDefinition[] getAllItemDefs() {
if (namedItemDefs.size() == 0 && unnamedItemDefs.size() == 0) {
return QItemDefinition.EMPTY_ARRAY;
}
ArrayList<QItemDefinition> defs = new ArrayList<QItemDefinition>(namedItemDefs.size() + unnamedItemDefs.size());
for (List<QItemDefinition> itemDefs : namedItemDefs.values()) {
defs.addAll(itemDefs);
}
defs.addAll(unnamedItemDefs);
if (defs.size() == 0) {
return QItemDefinition.EMPTY_ARRAY;
}
return defs.toArray(new QItemDefinition[defs.size()]);
}
public QItemDefinition[] getNamedItemDefs() {
if (namedItemDefs.size() == 0) {
return QItemDefinition.EMPTY_ARRAY;
}
ArrayList<QItemDefinition> defs = new ArrayList<QItemDefinition>(namedItemDefs.size());
for (List<QItemDefinition> itemDefs : namedItemDefs.values()) {
defs.addAll(itemDefs);
}
if (defs.size() == 0) {
return QItemDefinition.EMPTY_ARRAY;
}
return defs.toArray(new QItemDefinition[defs.size()]);
}
public QItemDefinition[] getUnnamedItemDefs() {
if (unnamedItemDefs.size() == 0) {
return QItemDefinition.EMPTY_ARRAY;
}
return unnamedItemDefs.toArray(new QItemDefinition[unnamedItemDefs.size()]);
}
public boolean hasNamedItemDef(Name name) {
return namedItemDefs.containsKey(name);
}
public QItemDefinition[] getNamedItemDefs(Name name) {
List<QItemDefinition> defs = namedItemDefs.get(name);
if (defs == null || defs.size() == 0) {
return QItemDefinition.EMPTY_ARRAY;
}
return defs.toArray(new QItemDefinition[defs.size()]);
}
public QNodeDefinition[] getAllNodeDefs() {
if (namedItemDefs.size() == 0 && unnamedItemDefs.size() == 0) {
return QNodeDefinition.EMPTY_ARRAY;
}
ArrayList<QNodeDefinition> defs = new ArrayList<QNodeDefinition>(namedItemDefs.size() + unnamedItemDefs.size());
for (QItemDefinition def : unnamedItemDefs) {
if (def.definesNode()) {
defs.add((QNodeDefinition) def);
}
}
for (List<QItemDefinition> list: namedItemDefs.values()) {
for (QItemDefinition def : list) {
if (def.definesNode()) {
defs.add((QNodeDefinition) def);
}
}
}
if (defs.size() == 0) {
return QNodeDefinition.EMPTY_ARRAY;
}
return defs.toArray(new QNodeDefinition[defs.size()]);
}
public QNodeDefinition[] getNamedNodeDefs() {
if (namedItemDefs.size() == 0) {
return QNodeDefinition.EMPTY_ARRAY;
}
ArrayList<QNodeDefinition> defs = new ArrayList<QNodeDefinition>(namedItemDefs.size());
for (List<QItemDefinition> list : namedItemDefs.values()) {
for (QItemDefinition def : list) {
if (def.definesNode()) {
defs.add((QNodeDefinition) def);
}
}
}
if (defs.size() == 0) {
return QNodeDefinition.EMPTY_ARRAY;
}
return defs.toArray(new QNodeDefinition[defs.size()]);
}
public QNodeDefinition[] getNamedNodeDefs(Name name) {
List<QItemDefinition> list = namedItemDefs.get(name);
if (list == null || list.size() == 0) {
return QNodeDefinition.EMPTY_ARRAY;
}
ArrayList<QNodeDefinition> defs = new ArrayList<QNodeDefinition>(list.size());
for (QItemDefinition def : list) {
if (def.definesNode()) {
defs.add((QNodeDefinition) def);
}
}
if (defs.size() == 0) {
return QNodeDefinition.EMPTY_ARRAY;
}
return defs.toArray(new QNodeDefinition[defs.size()]);
}
public QNodeDefinition[] getUnnamedNodeDefs() {
if (unnamedItemDefs.size() == 0) {
return QNodeDefinition.EMPTY_ARRAY;
}
ArrayList<QNodeDefinition> defs = new ArrayList<QNodeDefinition>(unnamedItemDefs.size());
for (QItemDefinition def : unnamedItemDefs) {
if (def.definesNode()) {
defs.add((QNodeDefinition) def);
}
}
if (defs.size() == 0) {
return QNodeDefinition.EMPTY_ARRAY;
}
return defs.toArray(new QNodeDefinition[defs.size()]);
}
public QNodeDefinition[] getAutoCreateNodeDefs() {
// since auto-create items must have a name,
// we're only searching the named item definitions
if (namedItemDefs.size() == 0) {
return QNodeDefinition.EMPTY_ARRAY;
}
ArrayList<QNodeDefinition> defs = new ArrayList<QNodeDefinition>(namedItemDefs.size());
for (List<QItemDefinition> list : namedItemDefs.values()) {
for (QItemDefinition def : list) {
if (def.definesNode() && def.isAutoCreated()) {
defs.add((QNodeDefinition) def);
}
}
}
if (defs.size() == 0) {
return QNodeDefinition.EMPTY_ARRAY;
}
return defs.toArray(new QNodeDefinition[defs.size()]);
}
public QPropertyDefinition[] getAllPropDefs() {
if (namedItemDefs.size() == 0 && unnamedItemDefs.size() == 0) {
return QPropertyDefinition.EMPTY_ARRAY;
}
ArrayList<QPropertyDefinition> defs = new ArrayList<QPropertyDefinition>(namedItemDefs.size() + unnamedItemDefs.size());
for (QItemDefinition def : unnamedItemDefs) {
if (!def.definesNode()) {
defs.add((QPropertyDefinition) def);
}
}
for (List<QItemDefinition> list: namedItemDefs.values()) {
for (QItemDefinition def : list) {
if (!def.definesNode()) {
defs.add((QPropertyDefinition) def);
}
}
}
if (defs.size() == 0) {
return QPropertyDefinition.EMPTY_ARRAY;
}
return defs.toArray(new QPropertyDefinition[defs.size()]);
}
public QPropertyDefinition[] getNamedPropDefs() {
if (namedItemDefs.size() == 0) {
return QPropertyDefinition.EMPTY_ARRAY;
}
ArrayList<QPropertyDefinition> defs = new ArrayList<QPropertyDefinition>(namedItemDefs.size());
for (List<QItemDefinition> list : namedItemDefs.values()) {
for (QItemDefinition def : list) {
if (!def.definesNode()) {
defs.add((QPropertyDefinition) def);
}
}
}
if (defs.size() == 0) {
return QPropertyDefinition.EMPTY_ARRAY;
}
return defs.toArray(new QPropertyDefinition[defs.size()]);
}
public QPropertyDefinition[] getNamedPropDefs(Name name) {
List<QItemDefinition> list = namedItemDefs.get(name);
if (list == null || list.size() == 0) {
return QPropertyDefinition.EMPTY_ARRAY;
}
ArrayList<QPropertyDefinition> defs = new ArrayList<QPropertyDefinition>(list.size());
for (QItemDefinition def : list) {
if (!def.definesNode()) {
defs.add((QPropertyDefinition) def);
}
}
if (defs.size() == 0) {
return QPropertyDefinition.EMPTY_ARRAY;
}
return defs.toArray(new QPropertyDefinition[defs.size()]);
}
public QPropertyDefinition[] getUnnamedPropDefs() {
if (unnamedItemDefs.size() == 0) {
return QPropertyDefinition.EMPTY_ARRAY;
}
ArrayList<QPropertyDefinition> defs = new ArrayList<QPropertyDefinition>(unnamedItemDefs.size());
for (QItemDefinition def : unnamedItemDefs) {
if (!def.definesNode()) {
defs.add((QPropertyDefinition) def);
}
}
if (defs.size() == 0) {
return QPropertyDefinition.EMPTY_ARRAY;
}
return defs.toArray(new QPropertyDefinition[defs.size()]);
}
public QPropertyDefinition[] getAutoCreatePropDefs() {
// since auto-create items must have a name,
// we're only searching the named item definitions
if (namedItemDefs.size() == 0) {
return QPropertyDefinition.EMPTY_ARRAY;
}
ArrayList<QPropertyDefinition> defs = new ArrayList<QPropertyDefinition>(namedItemDefs.size());
for (List<QItemDefinition> list : namedItemDefs.values()) {
for (QItemDefinition def : list) {
if (!def.definesNode() && def.isAutoCreated()) {
defs.add((QPropertyDefinition) def);
}
}
}
if (defs.size() == 0) {
return QPropertyDefinition.EMPTY_ARRAY;
}
return defs.toArray(new QPropertyDefinition[defs.size()]);
}
public QPropertyDefinition[] getMandatoryPropDefs() {
// since mandatory items must have a name,
// we're only searching the named item definitions
if (namedItemDefs.size() == 0) {
return QPropertyDefinition.EMPTY_ARRAY;
}
ArrayList<QPropertyDefinition> defs = new ArrayList<QPropertyDefinition>(namedItemDefs.size());
for (List<QItemDefinition> list : namedItemDefs.values()) {
for (QItemDefinition def : list) {
if (!def.definesNode() && def.isMandatory()) {
defs.add((QPropertyDefinition) def);
}
}
}
if (defs.size() == 0) {
return QPropertyDefinition.EMPTY_ARRAY;
}
return defs.toArray(new QPropertyDefinition[defs.size()]);
}
public QNodeDefinition[] getMandatoryNodeDefs() {
// since mandatory items must have a name,
// we're only searching the named item definitions
if (namedItemDefs.size() == 0) {
return QNodeDefinition.EMPTY_ARRAY;
}
ArrayList<QNodeDefinition> defs = new ArrayList<QNodeDefinition>(namedItemDefs.size());
for (List<QItemDefinition> list : namedItemDefs.values()) {
for (QItemDefinition def : list) {
if (def.definesNode() && def.isMandatory()) {
defs.add((QNodeDefinition) def);
}
}
}
if (defs.size() == 0) {
return QNodeDefinition.EMPTY_ARRAY;
}
return defs.toArray(new QNodeDefinition[defs.size()]);
}
/**
* Determines whether this effective node type representation includes
* (either through inheritance or aggregation) the given node type.
*
* @param nodeTypeName name of node type
* @return <code>true</code> if the given node type is included, otherwise
* <code>false</code>
*/
public boolean includesNodeType(Name nodeTypeName) {
return allNodeTypes.contains(nodeTypeName);
}
/**
* Determines whether this effective node type representation includes
* (either through inheritance or aggregation) all of the given node types.
*
* @param nodeTypeNames array of node type names
* @return <code>true</code> if all of the given node types are included,
* otherwise <code>false</code>
*/
public boolean includesNodeTypes(Name[] nodeTypeNames) {
return allNodeTypes.containsAll(Arrays.asList(nodeTypeNames));
}
/**
* Tests if the value constraints defined in the property definition
* <code>pd</code> are satisfied by the the specified <code>values</code>.
* <p/>
* Note that the <i>protected</i> flag is not checked. Also note that no
* type conversions are attempted if the type of the given values does not
* match the required type as specified in the given definition.
*
* @param pd The definiton of the property
* @param values An array of <code>InternalValue</code> objects.
* @throws ConstraintViolationException if the value constraints defined in
* the property definition are satisfied
* by the the specified values
* @throws RepositoryException if another error occurs
*/
public static void checkSetPropertyValueConstraints(QPropertyDefinition pd,
InternalValue[] values)
throws ConstraintViolationException, RepositoryException {
// check multi-value flag
if (!pd.isMultiple() && values != null && values.length > 1) {
throw new ConstraintViolationException("the property is not multi-valued");
}
QValueConstraint[] constraints = pd.getValueConstraints();
if (constraints == null || constraints.length == 0) {
// no constraints to check
return;
}
if (values != null && values.length > 0) {
// check value constraints on every value
for (InternalValue value : values) {
// constraints are OR-ed together
boolean satisfied = false;
ConstraintViolationException cve = null;
for (QValueConstraint constraint : constraints) {
try {
constraint.check(value);
satisfied = true;
break;
} catch (ConstraintViolationException e) {
cve = e;
}
}
if (!satisfied) {
// re-throw last exception we encountered
throw cve;
}
}
}
}
/**
* @param name
* @throws ConstraintViolationException
*/
public void checkAddNodeConstraints(Name name)
throws ConstraintViolationException {
try {
getApplicableChildNodeDef(name, null, null);
} catch (NoSuchNodeTypeException nsnte) {
String msg = "internal error: inconsistent node type";
log.debug(msg);
throw new ConstraintViolationException(msg, nsnte);
}
}
/**
* @param name
* @param nodeTypeName
* @param ntReg
* @throws ConstraintViolationException
* @throws NoSuchNodeTypeException
*/
public void checkAddNodeConstraints(Name name, Name nodeTypeName,
NodeTypeRegistry ntReg)
throws ConstraintViolationException, NoSuchNodeTypeException {
if (nodeTypeName != null) {
QNodeTypeDefinition ntDef = ntReg.getNodeTypeDef(nodeTypeName);
if (ntDef.isAbstract()) {
throw new ConstraintViolationException(nodeTypeName + " is abstract.");
}
if (ntDef.isMixin()) {
throw new ConstraintViolationException(nodeTypeName + " is mixin.");
}
}
QItemDefinition nd = getApplicableChildNodeDef(name, nodeTypeName, ntReg);
if (nd.isProtected()) {
throw new ConstraintViolationException(name + " is protected");
}
if (nd.isAutoCreated()) {
throw new ConstraintViolationException(name + " is auto-created and can not be manually added");
}
}
/**
* Returns the applicable child node definition for a child node with the
* specified name and node type. If there are multiple applicable definitions
* named definitions will take precedence over residual definitions.
*
* @param name
* @param nodeTypeName
* @param ntReg
* @return
* @throws NoSuchNodeTypeException
* @throws ConstraintViolationException if no applicable child node definition
* could be found
*/
public QNodeDefinition getApplicableChildNodeDef(Name name, Name nodeTypeName,
NodeTypeRegistry ntReg)
throws NoSuchNodeTypeException, ConstraintViolationException {
EffectiveNodeType entTarget;
if (nodeTypeName != null) {
entTarget = ntReg.getEffectiveNodeType(nodeTypeName);
} else {
entTarget = null;
}
// try named node definitions first
QItemDefinition[] defs = getNamedItemDefs(name);
for (QItemDefinition def : defs) {
if (def.definesNode()) {
QNodeDefinition nd = (QNodeDefinition) def;
Name[] types = nd.getRequiredPrimaryTypes();
// node definition with that name exists
if (entTarget != null && types != null) {
// check 'required primary types' constraint
if (entTarget.includesNodeTypes(types)) {
// found named node definition
return nd;
}
} else if (nd.getDefaultPrimaryType() != null) {
// found node definition with default node type
return nd;
}
}
}
// no item with that name defined;
// try residual node definitions
QNodeDefinition[] nda = getUnnamedNodeDefs();
for (QNodeDefinition nd : nda) {
if (entTarget != null && nd.getRequiredPrimaryTypes() != null) {
// check 'required primary types' constraint
if (!entTarget.includesNodeTypes(nd.getRequiredPrimaryTypes())) {
continue;
}
// found residual node definition
return nd;
} else {
// since no node type has been specified for the new node,
// it must be determined from the default node type;
if (nd.getDefaultPrimaryType() != null) {
// found residual node definition with default node type
return nd;
}
}
}
// no applicable definition found
throw new ConstraintViolationException("no matching child node definition found for " + name);
}
/**
* Returns the applicable property definition for a property with the
* specified name, type and multiValued characteristic. If there are
* multiple applicable definitions the following rules will be applied:
* <ul>
* <li>named definitions are preferred to residual definitions</li>
* <li>definitions with specific required type are preferred to definitions
* with required type UNDEFINED</li>
* </ul>
*
* @param name
* @param type
* @param multiValued
* @return
* @throws ConstraintViolationException if no applicable property definition
* could be found
*/
public QPropertyDefinition getApplicablePropertyDef(Name name, int type,
boolean multiValued)
throws ConstraintViolationException {
// try named property definitions first
QPropertyDefinition match =
getMatchingPropDef(getNamedPropDefs(name), type, multiValued);
if (match != null) {
return match;
}
// no item with that name defined;
// try residual property definitions
match = getMatchingPropDef(getUnnamedPropDefs(), type, multiValued);
if (match != null) {
return match;
}
// no applicable definition found
throw new ConstraintViolationException("no matching property definition found for " + name);
}
/**
* Returns the applicable property definition for a property with the
* specified name and type. The multiValued flag is not taken into account
* in the selection algorithm. Other than
* <code>{@link #getApplicablePropertyDef(Name, int, boolean)}</code>
* this method does not take the multiValued flag into account in the
* selection algorithm. If there more than one applicable definitions then
* the following rules are applied:
* <ul>
* <li>named definitions are preferred to residual definitions</li>
* <li>definitions with specific required type are preferred to definitions
* with required type UNDEFINED</li>
* <li>single-value definitions are preferred to multiple-value definitions</li>
* </ul>
*
* @param name
* @param type
* @return
* @throws ConstraintViolationException if no applicable property definition
* could be found
*/
public QPropertyDefinition getApplicablePropertyDef(Name name, int type)
throws ConstraintViolationException {
// try named property definitions first
QPropertyDefinition match = getMatchingPropDef(getNamedPropDefs(name), type);
if (match != null) {
return match;
}
// no item with that name defined;
// try residual property definitions
match = getMatchingPropDef(getUnnamedPropDefs(), type);
if (match != null) {
return match;
}
// no applicable definition found
throw new ConstraintViolationException("no matching property definition found for " + name);
}
private QPropertyDefinition getMatchingPropDef(QPropertyDefinition[] defs, int type) {
QPropertyDefinition match = null;
for (QPropertyDefinition pd : defs) {
int reqType = pd.getRequiredType();
// match type
if (reqType == PropertyType.UNDEFINED
|| type == PropertyType.UNDEFINED
|| reqType == type) {
if (match == null) {
match = pd;
} else {
// check if this definition is a better match than
// the one we've already got
if (match.getRequiredType() != pd.getRequiredType()) {
if (match.getRequiredType() == PropertyType.UNDEFINED) {
// found better match
match = pd;
}
} else {
if (match.isMultiple() && !pd.isMultiple()) {
// found better match
match = pd;
}
}
}
if (match.getRequiredType() != PropertyType.UNDEFINED
&& !match.isMultiple()) {
// found best possible match, get outta here
return match;
}
}
}
return match;
}
private QPropertyDefinition getMatchingPropDef(QPropertyDefinition[] defs, int type,
boolean multiValued) {
QPropertyDefinition match = null;
for (QPropertyDefinition pd : defs) {
int reqType = pd.getRequiredType();
// match type
if (reqType == PropertyType.UNDEFINED
|| type == PropertyType.UNDEFINED
|| reqType == type) {
// match multiValued flag
if (multiValued == pd.isMultiple()) {
// found match
if (pd.getRequiredType() != PropertyType.UNDEFINED) {
// found best possible match, get outta here
return pd;
} else {
if (match == null) {
match = pd;
}
}
}
}
}
return match;
}
/**
* @param name
* @throws ConstraintViolationException
*/
public void checkRemoveItemConstraints(Name name) throws ConstraintViolationException {
/**
* as there might be multiple definitions with the same name and we
* don't know which one is applicable, we check all of them
*/
QItemDefinition[] defs = getNamedItemDefs(name);
if (defs != null) {
for (QItemDefinition def : defs) {
if (def.isMandatory()) {
throw new ConstraintViolationException("can't remove mandatory item");
}
if (def.isProtected()) {
throw new ConstraintViolationException("can't remove protected item");
}
}
}
}
/**
* @param name
* @throws ConstraintViolationException
*/
public void checkRemoveNodeConstraints(Name name) throws ConstraintViolationException {
/**
* as there might be multiple definitions with the same name and we
* don't know which one is applicable, we check all of them
*/
QNodeDefinition[] defs = getNamedNodeDefs(name);
if (defs != null) {
for (QNodeDefinition def : defs) {
if (def.isMandatory()) {
throw new ConstraintViolationException("can't remove mandatory node");
}
if (def.isProtected()) {
throw new ConstraintViolationException("can't remove protected node");
}
}
}
}
/**
* @param name
* @throws ConstraintViolationException
*/
public void checkRemovePropertyConstraints(Name name) throws ConstraintViolationException {
/**
* as there might be multiple definitions with the same name and we
* don't know which one is applicable, we check all of them
*/
QItemDefinition[] defs = getNamedPropDefs(name);
if (defs != null) {
for (QItemDefinition def : defs) {
if (def.isMandatory()) {
throw new ConstraintViolationException("can't remove mandatory property");
}
if (def.isProtected()) {
throw new ConstraintViolationException("can't remove protected property");
}
}
}
}
/**
* Merges another <code>EffectiveNodeType</code> with this one.
* Checks for merge conflicts.
*
* @param other
* @return
* @throws NodeTypeConflictException
*/
EffectiveNodeType merge(EffectiveNodeType other)
throws NodeTypeConflictException {
// create a clone of this instance and perform the merge on
// the 'clone' to avoid a potentially inconsistent state
// of this instance if an exception is thrown during
// the merge.
EffectiveNodeType copy = (EffectiveNodeType) clone();
copy.internalMerge(other, false);
return copy;
}
/**
* Internal helper method which merges another <code>EffectiveNodeType</code>
* instance with <i>this</i> instance.
* <p/>
* Warning: This instance might be in an inconsistent state if an exception
* is thrown.
*
* @param other
* @param supertype true if the merge is a result of inheritance, i.e. <code>other</code>
* represents one or more supertypes of this instance; otherwise false, i.e.
* the merge is the result of an explicit aggregation
* @throws NodeTypeConflictException
*/
private synchronized void internalMerge(EffectiveNodeType other, boolean supertype)
throws NodeTypeConflictException {
Name[] nta = other.getAllNodeTypes();
int includedCount = 0;
for (Name aNta : nta) {
if (includesNodeType(aNta)) {
// redundant node type
log.debug("node type '" + aNta + "' is already contained.");
includedCount++;
}
}
if (includedCount == nta.length) {
// total overlap, ignore
return;
}
// named item definitions
QItemDefinition[] defs = other.getNamedItemDefs();
for (QItemDefinition def : defs) {
if (includesNodeType(def.getDeclaringNodeType())) {
// ignore redundant definitions
continue;
}
Name name = def.getName();
List<QItemDefinition> existingDefs = namedItemDefs.get(name);
if (existingDefs != null) {
if (existingDefs.size() > 0) {
// there already exists at least one definition with that name
for (QItemDefinition existingDef : existingDefs) {
// make sure none of them is auto-create
if (def.isAutoCreated() || existingDef.isAutoCreated()) {
// conflict
String msg = "The item definition for '" + name
+ "' in node type '"
+ def.getDeclaringNodeType()
+ "' conflicts with node type '"
+ existingDef.getDeclaringNodeType()
+ "': name collision with auto-create definition";
log.debug(msg);
throw new NodeTypeConflictException(msg);
}
// check ambiguous definitions
if (def.definesNode() == existingDef.definesNode()) {
if (!def.definesNode()) {
// property definition
QPropertyDefinition pd = (QPropertyDefinition) def;
QPropertyDefinition epd = (QPropertyDefinition) existingDef;
// compare type & multiValued flag
if (pd.getRequiredType() == epd.getRequiredType()
&& pd.isMultiple() == epd.isMultiple()) {
// conflict
String msg = "The property definition for '"
+ name + "' in node type '"
+ def.getDeclaringNodeType()
+ "' conflicts with node type '"
+ existingDef.getDeclaringNodeType()
+ "': ambiguous property definition";
log.debug(msg);
throw new NodeTypeConflictException(msg);
}
} else {
// child node definition
// conflict
String msg = "The child node definition for '"
+ name + "' in node type '"
+ def.getDeclaringNodeType()
+ "' conflicts with node type '"
+ existingDef.getDeclaringNodeType()
+ "': ambiguous child node definition";
log.debug(msg);
throw new NodeTypeConflictException(msg);
}
}
}
}
} else {
existingDefs = new ArrayList<QItemDefinition>();
namedItemDefs.put(name, existingDefs);
}
existingDefs.add(def);
}
// residual item definitions
defs = other.getUnnamedItemDefs();
for (QItemDefinition def : defs) {
if (includesNodeType(def.getDeclaringNodeType())) {
// ignore redundant definitions
continue;
}
for (QItemDefinition existing : unnamedItemDefs) {
// compare with existing definition
if (def.definesNode() == existing.definesNode()) {
if (!def.definesNode()) {
// property definition
QPropertyDefinition pd = (QPropertyDefinition) def;
QPropertyDefinition epd = (QPropertyDefinition) existing;
// compare type & multiValued flag
if (pd.getRequiredType() == epd.getRequiredType()
&& pd.isMultiple() == epd.isMultiple()) {
// conflict
String msg = "A property definition in node type '"
+ def.getDeclaringNodeType()
+ "' conflicts with node type '"
+ existing.getDeclaringNodeType()
+ "': ambiguous residual property definition";
log.debug(msg);
throw new NodeTypeConflictException(msg);
}
} else {
// child node definition
QNodeDefinition nd = (QNodeDefinition) def;
QNodeDefinition end = (QNodeDefinition) existing;
// compare required & default primary types
if (Arrays.equals(nd.getRequiredPrimaryTypes(), end.getRequiredPrimaryTypes())
&& (nd.getDefaultPrimaryType() == null
? end.getDefaultPrimaryType() == null
: nd.getDefaultPrimaryType().equals(end.getDefaultPrimaryType()))) {
// conflict
String msg = "A child node definition in node type '"
+ def.getDeclaringNodeType()
+ "' conflicts with node type '"
+ existing.getDeclaringNodeType()
+ "': ambiguous residual child node definition";
log.debug(msg);
throw new NodeTypeConflictException(msg);
}
}
}
}
unnamedItemDefs.add(def);
}
allNodeTypes.addAll(Arrays.asList(nta));
if (supertype) {
// implicit merge as result of inheritance
// add other merged node types as supertypes
nta = other.getMergedNodeTypes();
inheritedNodeTypes.addAll(Arrays.asList(nta));
// add supertypes of other merged node types as supertypes
nta = other.getInheritedNodeTypes();
inheritedNodeTypes.addAll(Arrays.asList(nta));
} else {
// explicit merge
// merge with other merged node types
nta = other.getMergedNodeTypes();
mergedNodeTypes.addAll(Arrays.asList(nta));
// add supertypes of other merged node types as supertypes
nta = other.getInheritedNodeTypes();
inheritedNodeTypes.addAll(Arrays.asList(nta));
}
// update 'orderable child nodes' attribute value (JCR-1947)
if (other.hasOrderableChildNodes()) {
orderableChildNodes = true;
}
// update 'primary item' attribute value (JCR-1947)
if (primaryItemName == null && other.getPrimaryItemName() != null) {
primaryItemName = other.getPrimaryItemName();
}
}
@Override
protected Object clone() {
EffectiveNodeType clone = new EffectiveNodeType();
clone.mergedNodeTypes.addAll(mergedNodeTypes);
clone.inheritedNodeTypes.addAll(inheritedNodeTypes);
clone.allNodeTypes.addAll(allNodeTypes);
for (Name name : namedItemDefs.keySet()) {
List<QItemDefinition> list = namedItemDefs.get(name);
clone.namedItemDefs.put(name, new ArrayList<QItemDefinition>(list));
}
clone.unnamedItemDefs.addAll(unnamedItemDefs);
clone.orderableChildNodes = orderableChildNodes;
clone.primaryItemName = primaryItemName;
return clone;
}
}