/*
* 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.query;
import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry;
import org.apache.jackrabbit.core.nodetype.NodeTypeRegistryListener;
import org.apache.jackrabbit.spi.Name;
import org.apache.jackrabbit.spi.QPropertyDefinition;
import org.apache.jackrabbit.spi.QNodeTypeDefinition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.jcr.PropertyType;
import javax.jcr.nodetype.NoSuchNodeTypeException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* The <code>PropertyTypeRegistry</code> keeps track of registered node type
* definitions and its property types. It provides a fast type lookup for a
* given property name.
*/
public class PropertyTypeRegistry implements NodeTypeRegistryListener {
/** The logger instance for this class */
private static final Logger log = LoggerFactory.getLogger(PropertyTypeRegistry.class);
/**
* Empty <code>TypeMapping</code> array as return value if no type is
* found
*/
private static final TypeMapping[] EMPTY = new TypeMapping[0];
/** The NodeTypeRegistry */
private final NodeTypeRegistry registry;
/** Property Name to TypeMapping[] mapping */
private final Map<Name, TypeMapping[]> typeMapping = new HashMap<Name, TypeMapping[]>();
/**
* Creates a new <code>PropertyTypeRegistry</code> instance. This instance
* is *not* registered as listener to the NodeTypeRegistry in the constructor!
* @param reg the <code>NodeTypeRegistry</code> where to read the property
* type information.
*/
public PropertyTypeRegistry(NodeTypeRegistry reg) {
this.registry = reg;
fillCache();
}
/**
* Returns an array of type mappings for a given property name
* <code>propName</code>. If <code>propName</code> is not defined as a property
* in any registered node type an empty array is returned.
* @param propName the name of the property.
* @return an array of <code>TypeMapping</code> instances.
*/
public TypeMapping[] getPropertyTypes(Name propName) {
synchronized (typeMapping) {
TypeMapping[] types = typeMapping.get(propName);
if (types != null) {
return types;
} else {
return EMPTY;
}
}
}
public void nodeTypeRegistered(Name ntName) {
try {
QNodeTypeDefinition def = registry.getNodeTypeDef(ntName);
QPropertyDefinition[] propDefs = def.getPropertyDefs();
synchronized (typeMapping) {
for (QPropertyDefinition propDef : propDefs) {
int type = propDef.getRequiredType();
if (!propDef.definesResidual() && type != PropertyType.UNDEFINED) {
Name name = propDef.getName();
// only remember defined property types
TypeMapping[] types = typeMapping.get(name);
if (types == null) {
types = new TypeMapping[1];
} else {
TypeMapping[] tmp = new TypeMapping[types.length + 1];
System.arraycopy(types, 0, tmp, 0, types.length);
types = tmp;
}
types[types.length - 1] = new TypeMapping(ntName, type, propDef.isMultiple());
typeMapping.put(name, types);
}
}
}
} catch (NoSuchNodeTypeException e) {
log.error("Unable to get newly registered node type definition for name: " + ntName);
}
}
public void nodeTypeReRegistered(Name ntName) {
nodeTypesUnregistered(Collections.singleton(ntName));
nodeTypeRegistered(ntName);
}
public void nodeTypesUnregistered(Collection<Name> names) {
// remove all TypeMapping instances refering to this ntName
synchronized (typeMapping) {
Map<Name, TypeMapping[]> modified = new HashMap<Name, TypeMapping[]>();
for (Iterator it = typeMapping.keySet().iterator(); it.hasNext();) {
Name propName = (Name) it.next();
TypeMapping[] mapping = typeMapping.get(propName);
List<TypeMapping> remove = null;
for (TypeMapping tm : mapping) {
if (names.contains(tm.ntName)) {
if (remove == null) {
// not yet created
remove = new ArrayList<TypeMapping>(mapping.length);
}
remove.add(tm);
}
}
if (remove != null) {
it.remove();
if (mapping.length == remove.size()) {
// all removed -> done
} else {
// only some removed
List<TypeMapping> remaining = new ArrayList<TypeMapping>(Arrays.asList(mapping));
remaining.removeAll(remove);
modified.put(propName, remaining.toArray(new TypeMapping[remaining.size()]));
}
}
}
// finally re-add the modified mappings
typeMapping.putAll(modified);
}
}
/**
* Initially fills the cache of this registry with property type definitions
* from the {@link org.apache.jackrabbit.core.nodetype.NodeTypeRegistry}.
*/
private void fillCache() {
for (Name ntName : registry.getRegisteredNodeTypes()) {
nodeTypeRegistered(ntName);
}
}
public static class TypeMapping {
/** The property type as an integer */
public final int type;
/** The Name of the node type where this type mapping originated */
final Name ntName;
/** True if the property type is multi-valued */
public final boolean isMultiValued;
private TypeMapping(Name ntName, int type, boolean isMultiValued) {
this.type = type;
this.ntName = ntName;
this.isMultiValued = isMultiValued;
}
}
}