/*
* 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 java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import java.util.List;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.Value;
import javax.jcr.ValueFactory;
import javax.jcr.nodetype.ConstraintViolationException;
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 org.apache.jackrabbit.core.data.DataStore;
import org.apache.jackrabbit.core.value.InternalValue;
import org.apache.jackrabbit.spi.Name;
import org.apache.jackrabbit.spi.QPropertyDefinition;
import org.apache.jackrabbit.spi.QNodeDefinition;
import org.apache.jackrabbit.spi.QNodeTypeDefinition;
import org.apache.jackrabbit.spi.commons.conversion.NameException;
import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
import org.apache.jackrabbit.spi.commons.nodetype.AbstractNodeType;
import org.apache.jackrabbit.value.ValueHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A <code>NodeTypeImpl</code> ...
*/
public class NodeTypeImpl extends AbstractNodeType implements NodeType, NodeTypeDefinition {
private static Logger log = LoggerFactory.getLogger(NodeTypeImpl.class);
private final EffectiveNodeType ent;
private final NodeTypeManagerImpl ntMgr;
// value factory used for type conversion
private final ValueFactory valueFactory;
private final DataStore store;
/**
* Package private constructor
* <p>
* Creates a valid node type instance. We assume that the node type
* definition is valid and all referenced node types (supertypes, required
* node types etc.) do exist and are valid.
*
* @param ent the effective (i.e. merged and resolved) node type
* representation
* @param ntd the definition of this node type
* @param ntMgr the node type manager associated with this node type
* @param resolver the name path resolver of the session.
* @param valueFactory the value factory of the session.
* @param store the data store or <code>null</code> if none is
* configured.
*/
NodeTypeImpl(EffectiveNodeType ent,
QNodeTypeDefinition ntd,
NodeTypeManagerImpl ntMgr,
NamePathResolver resolver,
ValueFactory valueFactory,
DataStore store) {
super(ntd, ntMgr, resolver);
this.ent = ent;
this.ntMgr = ntMgr;
this.valueFactory = valueFactory;
this.store = store;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isNodeType(Name nodeTypeName) {
return ent.includesNodeType(nodeTypeName);
}
/**
* Checks if this node type is directly or indirectly derived from the
* specified node type.
*
* @param nodeTypeName the name of a node type.
* @return true if this node type is directly or indirectly derived from the
* specified node type, otherwise false.
*/
public boolean isDerivedFrom(Name nodeTypeName) {
return !nodeTypeName.equals(ntd.getName()) && ent.includesNodeType(nodeTypeName);
}
/**
* Returns an array containing only those child node definitions of this
* node type (including the child node definitions inherited from supertypes
* of this node type) where <code>{@link NodeDefinition#isAutoCreated()}</code>
* returns <code>true</code>.
*
* @return an array of child node definitions.
* @see NodeDefinition#isAutoCreated
*/
public NodeDefinition[] getAutoCreatedNodeDefinitions() {
QNodeDefinition[] cnda = ent.getAutoCreateNodeDefs();
NodeDefinition[] nodeDefs = new NodeDefinition[cnda.length];
for (int i = 0; i < cnda.length; i++) {
nodeDefs[i] = ntMgr.getNodeDefinition(cnda[i]);
}
return nodeDefs;
}
/**
* Returns an array containing only those property definitions of this
* node type (including the property definitions inherited from supertypes
* of this node type) where <code>{@link PropertyDefinition#isAutoCreated()}</code>
* returns <code>true</code>.
*
* @return an array of property definitions.
* @see PropertyDefinition#isAutoCreated
*/
public PropertyDefinition[] getAutoCreatedPropertyDefinitions() {
QPropertyDefinition[] pda = ent.getAutoCreatePropDefs();
PropertyDefinition[] propDefs = new PropertyDefinition[pda.length];
for (int i = 0; i < pda.length; i++) {
propDefs[i] = ntMgr.getPropertyDefinition(pda[i]);
}
return propDefs;
}
/**
* Returns an array containing only those property definitions of this
* node type (including the property definitions inherited from supertypes
* of this node type) where <code>{@link PropertyDefinition#isMandatory()}</code>
* returns <code>true</code>.
*
* @return an array of property definitions.
* @see PropertyDefinition#isMandatory
*/
public PropertyDefinition[] getMandatoryPropertyDefinitions() {
QPropertyDefinition[] pda = ent.getMandatoryPropDefs();
PropertyDefinition[] propDefs = new PropertyDefinition[pda.length];
for (int i = 0; i < pda.length; i++) {
propDefs[i] = ntMgr.getPropertyDefinition(pda[i]);
}
return propDefs;
}
/**
* Returns an array containing only those child node definitions of this
* node type (including the child node definitions inherited from supertypes
* of this node type) where <code>{@link NodeDefinition#isMandatory()}</code>
* returns <code>true</code>.
*
* @return an array of child node definitions.
* @see NodeDefinition#isMandatory
*/
public NodeDefinition[] getMandatoryNodeDefinitions() {
QNodeDefinition[] cnda = ent.getMandatoryNodeDefs();
NodeDefinition[] nodeDefs = new NodeDefinition[cnda.length];
for (int i = 0; i < cnda.length; i++) {
nodeDefs[i] = ntMgr.getNodeDefinition(cnda[i]);
}
return nodeDefs;
}
/**
* Returns the <code>Name</code> of this node type.
*
* @return the name
*/
public Name getQName() {
return ntd.getName();
}
/**
* Returns all <i>inherited</i> supertypes of this node type.
*
* @return an array of <code>NodeType</code> objects.
* @see #getSupertypes
* @see #getDeclaredSupertypes
*/
public NodeType[] getInheritedSupertypes() {
// declared supertypes
Name[] ntNames = ntd.getSupertypes();
Set<Name> declared = new HashSet<Name>();
for (Name ntName : ntNames) {
declared.add(ntName);
}
// all supertypes
ntNames = ent.getInheritedNodeTypes();
// filter from all supertypes those that are not declared
List<NodeType> inherited = new ArrayList<NodeType>();
for (Name ntName : ntNames) {
if (!declared.contains(ntName)) {
try {
inherited.add(ntMgr.getNodeType(ntName));
} catch (NoSuchNodeTypeException e) {
// should never get here
log.error("undefined supertype", e);
return new NodeType[0];
}
}
}
return inherited.toArray(new NodeType[inherited.size()]);
}
//---------------------------------------------------< NodeTypeDefinition >
/**
* {@inheritDoc}
*/
public boolean hasOrderableChildNodes() {
return ent.hasOrderableChildNodes();
}
//-------------------------------------------------------------< NodeType >
/**
* {@inheritDoc}
*/
public NodeType[] getSupertypes() {
Name[] ntNames = ent.getInheritedNodeTypes();
NodeType[] supertypes = new NodeType[ntNames.length];
for (int i = 0; i < ntNames.length; i++) {
try {
supertypes[i] = ntMgr.getNodeType(ntNames[i]);
} catch (NoSuchNodeTypeException e) {
// should never get here
log.error("undefined supertype", e);
return new NodeType[0];
}
}
return supertypes;
}
/**
* {@inheritDoc}
*/
public NodeDefinition[] getChildNodeDefinitions() {
QNodeDefinition[] cnda = ent.getAllNodeDefs();
NodeDefinition[] nodeDefs = new NodeDefinition[cnda.length];
for (int i = 0; i < cnda.length; i++) {
nodeDefs[i] = ntMgr.getNodeDefinition(cnda[i]);
}
return nodeDefs;
}
/**
* {@inheritDoc}
*/
public PropertyDefinition[] getPropertyDefinitions() {
QPropertyDefinition[] pda = ent.getAllPropDefs();
PropertyDefinition[] propDefs = new PropertyDefinition[pda.length];
for (int i = 0; i < pda.length; i++) {
propDefs[i] = ntMgr.getPropertyDefinition(pda[i]);
}
return propDefs;
}
/**
* {@inheritDoc}
*/
public boolean canSetProperty(String propertyName, Value value) {
if (value == null) {
// setting a property to null is equivalent of removing it
return canRemoveItem(propertyName);
}
try {
Name name = resolver.getQName(propertyName);
QPropertyDefinition def;
try {
// try to get definition that matches the given value type
def = ent.getApplicablePropertyDef(name, value.getType(), false);
} catch (ConstraintViolationException cve) {
// fallback: ignore type
def = ent.getApplicablePropertyDef(name, PropertyType.UNDEFINED, false);
}
if (def.isProtected()) {
return false;
}
if (def.isMultiple()) {
return false;
}
int targetType;
if (def.getRequiredType() != PropertyType.UNDEFINED
&& def.getRequiredType() != value.getType()) {
// type conversion required
targetType = def.getRequiredType();
} else {
// no type conversion required
targetType = value.getType();
}
// perform type conversion as necessary and create InternalValue
// from (converted) Value
InternalValue internalValue;
if (targetType != value.getType()) {
// type conversion required
Value targetVal = ValueHelper.convert(
value, targetType,
valueFactory);
internalValue = InternalValue.create(targetVal, resolver, store);
} else {
// no type conversion required
internalValue = InternalValue.create(value, resolver, store);
}
EffectiveNodeType.checkSetPropertyValueConstraints(
def, new InternalValue[]{internalValue});
return true;
} catch (NameException be) {
// implementation specific exception, fall through
} catch (RepositoryException re) {
// fall through
}
return false;
}
/**
* {@inheritDoc}
*/
public boolean canSetProperty(String propertyName, Value[] values) {
if (values == null) {
// setting a property to null is equivalent of removing it
return canRemoveItem(propertyName);
}
try {
Name name = resolver.getQName(propertyName);
// determine type of values
int type = PropertyType.UNDEFINED;
for (Value value : values) {
if (value == null) {
// skip null values as those would be purged
continue;
}
if (type == PropertyType.UNDEFINED) {
type = value.getType();
} else if (type != value.getType()) {
// inhomogeneous types
return false;
}
}
QPropertyDefinition def;
try {
// try to get definition that matches the given value type
def = ent.getApplicablePropertyDef(name, type, true);
} catch (ConstraintViolationException cve) {
// fallback: ignore type
def = ent.getApplicablePropertyDef(name, PropertyType.UNDEFINED, true);
}
if (def.isProtected()) {
return false;
}
if (!def.isMultiple()) {
return false;
}
// determine target type
int targetType;
if (def.getRequiredType() != PropertyType.UNDEFINED
&& def.getRequiredType() != type) {
// type conversion required
targetType = def.getRequiredType();
} else {
// no type conversion required
targetType = type;
}
List<InternalValue> list = new ArrayList<InternalValue>();
// convert values and compact array (purge null entries)
for (Value value : values) {
if (value != null) {
// perform type conversion as necessary and create InternalValue
// from (converted) Value
InternalValue internalValue;
if (targetType != type) {
// type conversion required
Value targetVal = ValueHelper.convert(value, targetType, valueFactory);
internalValue = InternalValue.create(targetVal, resolver, store);
} else {
// no type conversion required
internalValue = InternalValue.create(value, resolver, store);
}
list.add(internalValue);
}
}
InternalValue[] internalValues = list.toArray(new InternalValue[list.size()]);
EffectiveNodeType.checkSetPropertyValueConstraints(def, internalValues);
return true;
} catch (NameException be) {
// implementation specific exception, fall through
} catch (RepositoryException re) {
// fall through
}
return false;
}
/**
* {@inheritDoc}
*/
public boolean canAddChildNode(String childNodeName) {
try {
ent.checkAddNodeConstraints(resolver.getQName(childNodeName));
return true;
} catch (NameException be) {
// implementation specific exception, fall through
} catch (RepositoryException re) {
// fall through
}
return false;
}
/**
* {@inheritDoc}
*/
public boolean canAddChildNode(String childNodeName, String nodeTypeName) {
try {
ent.checkAddNodeConstraints(
resolver.getQName(childNodeName),
resolver.getQName(nodeTypeName),
ntMgr.getNodeTypeRegistry());
return true;
} catch (NameException be) {
// implementation specific exception, fall through
} catch (RepositoryException re) {
// fall through
}
return false;
}
/**
* {@inheritDoc}
*/
public boolean canRemoveItem(String itemName) {
try {
ent.checkRemoveItemConstraints(resolver.getQName(itemName));
return true;
} catch (NameException be) {
// implementation specific exception, fall through
} catch (RepositoryException re) {
// fall through
}
return false;
}
//--------------------------------------------------< new JSR 283 methods >
/**
* Returns <code>true</code> if removing the child node called
* <code>nodeName</code> is allowed by this node type. Returns
* <code>false</code> otherwise.
*
* @param nodeName The name of the child node
* @return a boolean
* @since JCR 2.0
*/
public boolean canRemoveNode(String nodeName) {
try {
ent.checkRemoveNodeConstraints(resolver.getQName(nodeName));
return true;
} catch (NameException be) {
// implementation specific exception, fall through
} catch (RepositoryException re) {
// fall through
}
return false;
}
/**
* Returns <code>true</code> if removing the property called
* <code>propertyName</code> is allowed by this node type. Returns
* <code>false</code> otherwise.
*
* @param propertyName The name of the property
* @return a boolean
* @since JCR 2.0
*/
public boolean canRemoveProperty(String propertyName) {
try {
ent.checkRemovePropertyConstraints(resolver.getQName(propertyName));
return true;
} catch (NameException be) {
// implementation specific exception, fall through
} catch (RepositoryException re) {
// fall through
}
return false;
}
}