/*
* Copyright 2004-2009 the original author or authors.
*
* 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.compass.core.mapping.internal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.compass.core.converter.ConverterLookup;
import org.compass.core.engine.naming.PropertyPath;
import org.compass.core.mapping.AliasMapping;
import org.compass.core.mapping.Cascade;
import org.compass.core.mapping.MappingException;
import org.compass.core.mapping.ResourceMapping;
import org.compass.core.mapping.ResourcePropertyLookup;
import org.compass.core.mapping.ResourcePropertyMapping;
import org.compass.core.mapping.osem.ClassMapping;
import org.compass.core.mapping.rsem.RawResourceMapping;
import org.compass.core.mapping.support.NullResourceMapping;
/**
* @author kimchy
*/
public class DefaultCompassMapping implements InternalCompassMapping {
private final Map<String, AliasMapping> mappings = new ConcurrentHashMap<String, AliasMapping>();
private final Map<String, ResourceMapping> rootMappingsByAlias = new ConcurrentHashMap<String, ResourceMapping>();
private final Map<String, ResourceMapping> nonRootMappingsByAlias = new ConcurrentHashMap<String, ResourceMapping>();
private final ResourceMappingsByNameHolder mappingsByClass = new ResourceMappingsByNameHolder();
private final ResourceMappingsByNameHolder cachedMappingsByClass = new ResourceMappingsByNameHolder();
private final ResourceMappingsByNameHolder rootMappingsByClass = new ResourceMappingsByNameHolder();
private final ResourceMappingsByNameHolder cachedRootMappingsByClass = new ResourceMappingsByNameHolder();
private final ResourceMappingsByNameHolder nonRootMappingsByClass = new ResourceMappingsByNameHolder();
private final ResourceMappingsByNameHolder cachedNonRootMappingsByClass = new ResourceMappingsByNameHolder();
private ResourceMapping[] rootMappingsArr = new ResourceMapping[0];
private ConverterLookup converterLookup;
private final NullResourceMapping nullResourceMappingEntryInCache = new NullResourceMapping();
private final Map<String, ResourcePropertyMapping[]> resourcePropertyMappingByPath = new ConcurrentHashMap<String, ResourcePropertyMapping[]>();
private PropertyPath path;
private ReadWriteLock rwl = new ReentrantReadWriteLock();
private final Lock readLock = rwl.readLock();
private final Lock writeLock = rwl.writeLock();
public DefaultCompassMapping() {
}
public InternalCompassMapping copy(ConverterLookup converterLookup) {
writeLock.lock();
try {
DefaultCompassMapping copy = new DefaultCompassMapping();
copy.converterLookup = converterLookup;
copy.setPath(getPath());
for (AliasMapping aliasMapping : mappings.values()) {
AliasMapping copyMapping = (AliasMapping) (aliasMapping).copy();
copy.addMapping(copyMapping);
}
return copy;
} finally {
writeLock.unlock();
}
}
public void postProcess() {
writeLock.lock();
try {
ResourceMapping[] rootMappings = getRootMappings();
for (ResourceMapping rootMapping : rootMappings) {
// update the resource property mapping
ResourcePropertyMapping[] resourcePropertyMappings = rootMapping.getResourcePropertyMappings();
for (ResourcePropertyMapping rpm : resourcePropertyMappings) {
if (rpm.getPath() != null) {
String path = rpm.getPath().getPath();
ResourcePropertyMapping[] rpms = resourcePropertyMappingByPath.get(path);
if (rpms == null) {
resourcePropertyMappingByPath.put(path, new ResourcePropertyMapping[]{rpm});
} else {
ResourcePropertyMapping[] tmpRpms = new ResourcePropertyMapping[rpms.length + 1];
System.arraycopy(rpms, 0, tmpRpms, 0, rpms.length);
tmpRpms[tmpRpms.length - 1] = rpm;
resourcePropertyMappingByPath.put(path, tmpRpms);
}
}
}
}
} finally {
writeLock.unlock();
}
}
public void clearMappings() {
writeLock.lock();
try {
mappings.clear();
rootMappingsByAlias.clear();
nonRootMappingsByAlias.clear();
mappingsByClass.clear();
rootMappingsByClass.clear();
nonRootMappingsByClass.clear();
rootMappingsArr = new ResourceMapping[0];
resourcePropertyMappingByPath.clear();
clearCache();
} finally {
writeLock.unlock();
}
}
public void clearCache() {
writeLock.lock();
try {
cachedMappingsByClass.clear();
cachedRootMappingsByClass.clear();
cachedNonRootMappingsByClass.clear();
} finally {
writeLock.unlock();
}
}
/**
* Adds the given Alias mapping.
*/
public void addMapping(AliasMapping mapping) throws MappingException {
writeLock.lock();
try {
if (mappings.get(mapping.getAlias()) != null) {
throw new MappingException("Compass does not allow multiple aliases for alias [" + mapping.getAlias() + "]");
}
mappings.put(mapping.getAlias(), mapping);
if (mapping instanceof ResourceMapping) {
ResourceMapping resourceMapping = (ResourceMapping) mapping;
if (resourceMapping.isRoot()) {
rootMappingsByAlias.put(mapping.getAlias(), resourceMapping);
if (resourceMapping instanceof ClassMapping) {
rootMappingsByClass.addMapping(resourceMapping.getName(), resourceMapping);
mappingsByClass.addMapping(resourceMapping.getName(), resourceMapping);
}
ResourceMapping[] result = new ResourceMapping[rootMappingsArr.length + 1];
int i;
for (i = 0; i < rootMappingsArr.length; i++) {
result[i] = rootMappingsArr[i];
}
result[i] = resourceMapping;
rootMappingsArr = result;
} else {
nonRootMappingsByAlias.put(mapping.getAlias(), resourceMapping);
if (resourceMapping instanceof ClassMapping) {
mappingsByClass.addMapping(resourceMapping.getName(), resourceMapping);
nonRootMappingsByClass.addMapping(resourceMapping.getName(), resourceMapping);
}
}
}
} finally {
writeLock.unlock();
}
}
public boolean removeMappingByClass(String className) throws MappingException {
writeLock.lock();
try {
boolean result = false;
List<ResourceMapping> resourceMappings = mappingsByClass.getMappingsByName(className);
if (resourceMappings != null) {
ResourceMapping[] rm = resourceMappings.toArray(new ResourceMapping[resourceMappings.size()]);
for (ResourceMapping resourceMapping : rm) {
result |= removeMappingByAlias(resourceMapping.getAlias());
}
}
return result;
} finally {
writeLock.unlock();
}
}
public boolean removeMappingByAlias(String alias) throws MappingException {
writeLock.lock();
try {
AliasMapping aliasMapping = mappings.remove(alias);
if (aliasMapping == null) {
return false;
}
if (aliasMapping instanceof ResourceMapping) {
ResourceMapping resourceMapping = (ResourceMapping) aliasMapping;
if (resourceMapping.isRoot()) {
rootMappingsByAlias.remove(alias);
if (resourceMapping instanceof ClassMapping) {
rootMappingsByClass.removeMapping(resourceMapping);
mappingsByClass.removeMapping(resourceMapping);
}
List<ResourceMapping> l = new ArrayList<ResourceMapping>(rootMappingsArr.length);
for (ResourceMapping rm : rootMappingsArr) {
if (rm != resourceMapping) {
l.add(resourceMapping);
}
}
rootMappingsArr = l.toArray(new ResourceMapping[l.size()]);
} else {
nonRootMappingsByAlias.remove(alias);
if (resourceMapping instanceof ClassMapping) {
mappingsByClass.removeMapping(resourceMapping);
nonRootMappingsByClass.removeMapping(resourceMapping);
}
}
}
clearCache();
return true;
} finally {
writeLock.unlock();
}
}
/**
* Returns a resoruce lookup for a specific name. Supports dot path notation ([alias].[class property].).
* Allows to get the meta-data/resource property mapping through it (or a list of mappings).
*/
public ResourcePropertyLookup getResourcePropertyLookup(String name) throws IllegalArgumentException {
return new ResourcePropertyLookup(this, name);
}
/**
* Finds the {@link ResourcePropertyMapping} definition for the specified path. The
* path is in the format of: [alias].[class property mapping].[meta data mapping] in
* case of class mapping, and [alias].[resource property mapping] in case of resource
* mapping. The format of [alias].[class property mapping] can also be applied, and
* will result in the meta data id of the given class property mapping.
*
* @param path the path to the resource property mapping
* @return the resource property mapping for the given path
*/
public ResourcePropertyMapping getResourcePropertyMappingByPath(String path) {
int dotIndex = path.indexOf('.');
if (dotIndex == -1) {
return null;
}
String alias = path.substring(0, dotIndex);
String propertyName = path.substring(dotIndex + 1);
AliasMapping aliasMapping = getAliasMapping(alias);
ResourcePropertyMapping resourcePropertyMapping = null;
if (aliasMapping instanceof ResourceMapping) {
resourcePropertyMapping = ((ResourceMapping) aliasMapping).getResourcePropertyMappingByDotPath(propertyName);
} else if (aliasMapping != null) {
// go over alias mappings (such as contract mappings) and try and find if someone that extends it
// defines mappings for this dot path notation (since we only post process root resource mappings).
// note, if the extedning resource mapping also overrides the meta-data, then it will use it and
// not the one defined within the contract mapping.
String[] extendingAliases = aliasMapping.getExtendingAliases();
if (extendingAliases != null) {
for (String extendingAlias : extendingAliases) {
ResourceMapping resourceMapping = getRootMappingByAlias(extendingAlias);
if (resourceMapping != null) {
resourcePropertyMapping = resourceMapping.getResourcePropertyMappingByDotPath(propertyName);
if (resourcePropertyMapping != null) {
break;
}
}
}
}
}
if (resourcePropertyMapping == null) {
throw new IllegalArgumentException("Failed to find mapping for alias [" + alias + "] and path [" + path + "]");
}
return resourcePropertyMapping;
}
/**
* Returns an array of all the given {@link org.compass.core.mapping.ResourcePropertyMapping} for the given
* path. If the path is in "dot path" notation, will reutrn a single mappings matching it (see
* {@link #getResourcePropertyMappingByPath(String)}). Otherwise will return all the ones mapped to
* the given name.
*/
public ResourcePropertyMapping[] getResourcePropertyMappingsByPath(String path) {
int dotIndex = path.indexOf('.');
if (dotIndex != -1) {
return new ResourcePropertyMapping[]{getResourcePropertyMappingByPath(path)};
}
return resourcePropertyMappingByPath.get(path);
}
/**
* Returns an array of all the current mappings.
*/
public AliasMapping[] getMappings() {
return mappings.values().toArray(new AliasMapping[mappings.size()]);
}
/**
* Returns the alias mapping for the given alias (most if not all of the times, this will
* be a {@link org.compass.core.mapping.ResourceMapping}).
*/
public AliasMapping getAliasMapping(String alias) {
return mappings.get(alias);
}
/**
* Returns the resource mapping for the given alias.
*/
public ResourceMapping getMappingByAlias(String alias) {
return (ResourceMapping) mappings.get(alias);
}
/**
* Returns the root resource mapping associated with the alias. Retruns
* <code>null</code> if no root mapping (or no mapping) is associated with the alias.
*/
public ResourceMapping getRootMappingByAlias(String alias) {
return rootMappingsByAlias.get(alias);
}
/**
* Returns the non root resource mapping associated with the alias. Retruns
* <code>null</code> if no non root mapping (or no mapping) is associated with the alias.
*/
public ResourceMapping getNonRootMappingByAlias(String alias) {
return nonRootMappingsByAlias.get(alias);
}
/**
* Returns <code>true</code> if the given alias has a root resource mapping.
*/
public boolean hasRootMappingByAlias(String alias) {
return rootMappingsByAlias.get(alias) != null;
}
/**
* Returns <code>true</code> if there is a <b>root</b> {@link org.compass.core.mapping.osem.ClassMapping}
* for the given alias.
*/
public boolean hasRootClassMapping(String alias) {
return (rootMappingsByAlias.get(alias) instanceof ClassMapping);
}
/**
* Returns <code>true</code> if there is a <b>root</b> {@link org.compass.core.mapping.rsem.RawResourceMapping}
* for the given alias.
*/
public boolean hasRootRawResourceMapping(String alias) {
return (rootMappingsByAlias.get(alias) instanceof RawResourceMapping);
}
/**
* Returns <code>true</code> if the given <b>className</b> has multiple class mappings.
*/
public boolean hasMultipleRootClassMapping(String className) {
return rootMappingsByClass.hasMultipleMappingsByName(className);
}
/**
* Returns the direct class mapping for the given class (root or not). Will not try to
* navigate up the interface/superclass in order to find the "nearset" class mapping.
*
* <p>If a class has more than one mappings (using differnet aliases) will return the
* first one.
*/
public ResourceMapping getDirectMappingByClass(Class clazz) {
return mappingsByClass.getResourceMappingByName(clazz.getName());
}
/**
* Returns all the direct class mapping for the given class (root or not). Will not
* try to navigate up the interface/superclass in order to find the "nearest" class
* mapping.
*/
public List<ResourceMapping> getAllDirectMappingByClass(Class clazz) {
return mappingsByClass.getUnmodifiableMappingsByName(clazz.getName());
}
public boolean hasMappingForClass(Class clazz, Cascade cascade) {
ResourceMapping resourceMapping = getRootMappingByClass(clazz);
if (resourceMapping != null) {
return true;
}
resourceMapping = getNonRootMappingByClass(clazz);
return resourceMapping != null && resourceMapping.operationAllowed(cascade);
}
public boolean hasMappingForAlias(String alias, Cascade cascade) {
ResourceMapping resourceMapping = getRootMappingByAlias(alias);
if (resourceMapping != null) {
return true;
}
resourceMapping = getNonRootMappingByAlias(alias);
return resourceMapping != null && resourceMapping.operationAllowed(cascade);
}
/**
* Finds the Resource mapping that is the "nearest" to the provided class.
* Similar way that {@link #findRootMappingByClass(Class)} except the search
* is on all the ClassMappings (even ones that are not marked as root).
*/
public ResourceMapping getMappingByClass(Class clazz) {
return doGetResourceMappingByClass(clazz, false, mappingsByClass, cachedMappingsByClass);
}
/**
* Finds a root mapping by the class name. If a root mapping is not found
* for the class name, than searches for mappings for the interfaces, if not
* found, checks for subclasses, and subclassess interfaces. Note: If there
* is no direct mapping that match the class name, then the mapping that is
* found should be marked as poly.
*
* @param clazz The class to find root mapping for
* @return The resource mapping
*/
public ResourceMapping findRootMappingByClass(Class clazz) throws MappingException {
return doGetResourceMappingByClass(clazz, true, rootMappingsByClass, cachedRootMappingsByClass);
}
/**
* Does exactly the same as {@link #findRootMappingByClass(Class)}, but returns <code>null</code>
* if nothing is found (does not throw an exception).
*/
public ResourceMapping getRootMappingByClass(Class clazz) throws MappingException {
return doGetResourceMappingByClass(clazz, false, rootMappingsByClass, cachedRootMappingsByClass);
}
/**
* Finds a non root mapping by the class name. If a non root mapping is not found
* for the class name, than searches for mappings for the interfaces, if not
* found, checks for subclasses, and subclassess interfaces. Note: If there
* is no direct mapping that match the class name, then the mapping that is
* found should be marked as poly.
*
* @param clazz The class to find root mapping for
* @return The resource mapping
*/
public ResourceMapping findNonRootMappingByClass(Class clazz) throws MappingException {
return doGetResourceMappingByClass(clazz, true, nonRootMappingsByClass, cachedNonRootMappingsByClass);
}
/**
* Does exactly the same as {@link #findNonRootMappingByClass(Class)}, but returns <code>null</code>
* if nothing is found (does not throw an exception).
*/
public ResourceMapping getNonRootMappingByClass(Class clazz) throws MappingException {
return doGetResourceMappingByClass(clazz, false, nonRootMappingsByClass, cachedNonRootMappingsByClass);
}
private ResourceMapping doGetResourceMappingByClass(Class clazz, boolean throwEx,
ResourceMappingsByNameHolder mappingByClass,
ResourceMappingsByNameHolder cachedMappingsByClass) throws MappingException {
// we don't really care that we might execute it twice (for caching)
String className = clazz.getName();
ResourceMapping rm = cachedMappingsByClass.getResourceMappingByName(className);
if (rm != null) {
if (rm == nullResourceMappingEntryInCache) {
if (throwEx) {
throw new MappingException("Failed to find any mappings for class [" + className + "]");
}
return null;
}
return rm;
}
rm = doGetActualResourceMappingByClass(clazz, mappingByClass);
if (rm == null) {
cachedMappingsByClass.addMapping(className, nullResourceMappingEntryInCache);
if (throwEx) {
throw new MappingException("Failed to find any mappings for class [" + className + "]");
}
return null;
} else {
cachedMappingsByClass.addMapping(className, rm);
}
return rm;
}
private ResourceMapping doGetActualResourceMappingByClass(Class clazz, ResourceMappingsByNameHolder mappingByClass) {
ResourceMapping rm = mappingByClass.getResourceMappingByName(clazz.getName());
if (rm != null) {
return rm;
}
for (Class anInterface : clazz.getInterfaces()) {
rm = mappingByClass.getResourceMappingByName(anInterface.getName());
if (rm != null) {
return rm;
}
}
Class superClass = clazz.getSuperclass();
if (superClass == null) {
return null;
}
return doGetActualResourceMappingByClass(superClass, mappingByClass);
}
public ResourceMapping[] getRootMappings() {
return rootMappingsArr;
}
public ConverterLookup getConverterLookup() {
return converterLookup;
}
public PropertyPath getPath() {
return path;
}
public void setPath(PropertyPath path) {
this.path = path;
}
/**
* A resource mapping holder based on a name (actually, any string). Holds a map keyed
* by the name and the value a list of ResourceMapping registered under the name
*/
private class ResourceMappingsByNameHolder {
private final HashMap<String, List<ResourceMapping>> mappings = new HashMap<String, List<ResourceMapping>>();
private ReadWriteLock rwl = new ReentrantReadWriteLock();
void addMapping(String name, ResourceMapping resourceMapping) {
rwl.writeLock().lock();
try {
List<ResourceMapping> l = mappings.get(name);
if (l == null) {
l = new ArrayList<ResourceMapping>();
mappings.put(name, l);
}
l.add(resourceMapping);
} finally {
rwl.writeLock().unlock();
}
}
void removeMapping(ResourceMapping resourceMapping) {
rwl.writeLock().lock();
try {
for (List<ResourceMapping> l : mappings.values()) {
l.remove(resourceMapping);
}
} finally {
rwl.writeLock().unlock();
}
}
public void clear() {
rwl.writeLock().lock();
try {
mappings.clear();
} finally {
rwl.writeLock().unlock();
}
}
public List<ResourceMapping> getMappingsByName(String name) {
rwl.readLock().lock();
try {
return mappings.get(name);
} finally {
rwl.readLock().unlock();
}
}
public List<ResourceMapping> getUnmodifiableMappingsByName(String name) {
rwl.readLock().lock();
try {
return Collections.unmodifiableList(mappings.get(name));
} finally {
rwl.readLock().unlock();
}
}
/**
* Returns the first class mapping matching the given name. Returns
* <code>null</code> of no mapping matches the name.
*/
public ResourceMapping getResourceMappingByName(String name) {
rwl.readLock().lock();
try {
List<ResourceMapping> l = getMappingsByName(name);
if (l == null) {
return null;
}
return l.get(l.size() - 1);
} finally {
rwl.readLock().unlock();
}
}
public boolean hasMultipleMappingsByName(String name) {
rwl.readLock().lock();
try {
List<ResourceMapping> l = getMappingsByName(name);
return l != null && l.size() > 1;
} finally {
rwl.readLock().unlock();
}
}
}
}