/* * 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.osem; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import org.compass.core.engine.naming.PropertyPath; import org.compass.core.mapping.AliasMapping; import org.compass.core.mapping.Mapping; import org.compass.core.mapping.MappingException; import org.compass.core.mapping.ResourceMapping; import org.compass.core.mapping.ResourcePropertyMapping; import org.compass.core.mapping.internal.PostProcessingMapping; import org.compass.core.mapping.support.AbstractResourceMapping; import org.compass.core.util.Assert; import org.compass.core.util.reflection.ReflectionConstructor; /** * @author kimchy */ public class ClassMapping extends AbstractResourceMapping implements ResourceMapping, PostProcessingMapping { private ManagedId managedId; private PropertyPath enumNamePath; private PropertyPath classPath; private PropertyPath basePath; private Class clazz; private boolean poly; private Class polyClass; private Boolean supportUnmarshall; private Boolean filterDuplicates; private ResourcePropertyMapping[] resourcePropertyMappings; private ClassPropertyMapping[] classPropertyMappings; private ClassIdPropertyMapping[] classIdPropertyMappings; private HashMap<String, ResourcePropertyMapping> pathMappings; private ReflectionConstructor constructor; private ReflectionConstructor polyConstructor; public Mapping copy() { ClassMapping copy = new ClassMapping(); super.copy(copy); copy.setPoly(isPoly()); copy.setClassPath(getClassPath()); copy.setEnumNamePath(getEnumNamePath()); copy.setClazz(getClazz()); copy.setPolyClass(getPolyClass()); copy.setConstructor(getConstructor()); copy.setPolyConstructor(getPolyConstructor()); copy.supportUnmarshall = supportUnmarshall; copy.filterDuplicates = filterDuplicates; copy.setManagedId(getManagedId()); copy.setBasePath(getBasePath()); return copy; } public AliasMapping shallowCopy() { ClassMapping copy = new ClassMapping(); super.shallowCopy(copy); copy.setPoly(isPoly()); copy.setClassPath(getClassPath()); copy.setEnumNamePath(getEnumNamePath()); copy.setClazz(getClazz()); copy.setPolyClass(getPolyClass()); copy.setConstructor(getConstructor()); copy.setPolyConstructor(getPolyConstructor()); copy.supportUnmarshall = supportUnmarshall; copy.filterDuplicates = filterDuplicates; copy.setManagedId(getManagedId()); copy.setBasePath(getBasePath()); return copy; } /** * Post process by using the dynamic find operations to cache them. * * @throws MappingException */ protected void doPostProcess() throws MappingException { PostProcessMappingCallback callback = new PostProcessMappingCallback(); OsemMappingIterator.iterateMappings(callback, this, true); resourcePropertyMappings = callback.getResourcePropertyMappings().toArray(new ResourcePropertyMapping[callback.getResourcePropertyMappings().size()]); classPropertyMappings = callback.getClassPropertyMappings().toArray(new ClassPropertyMapping[callback.getClassPropertyMappings().size()]); List<ClassIdPropertyMapping> idMappings = findClassPropertyIdMappings(); classIdPropertyMappings = idMappings.toArray(new ClassIdPropertyMapping[idMappings.size()]); pathMappings = callback.getPathMappings(); } public ResourcePropertyMapping[] getResourcePropertyMappings() { return resourcePropertyMappings; } public ClassPropertyMapping[] getClassPropertyMappings() { return classPropertyMappings; } public ClassIdPropertyMapping[] getClassIdPropertyMappings() { return classIdPropertyMappings; } /** * Dynamically find the id mappings. */ public List<Mapping> findIdMappings() { ArrayList<Mapping> idMappingList = new ArrayList<Mapping>(); for (Iterator it = mappingsIt(); it.hasNext();) { Mapping m = (Mapping) it.next(); if (m instanceof ClassIdPropertyMapping) { idMappingList.add(m); } if (m instanceof IdComponentMapping) { idMappingList.add(m); } } return idMappingList; } /** * Dynamically finds all the {@link ClassIdPropertyMapping}s for the class. */ public List<ClassIdPropertyMapping> findClassPropertyIdMappings() { ArrayList<ClassIdPropertyMapping> idMappingList = new ArrayList<ClassIdPropertyMapping>(); for (Iterator it = mappingsIt(); it.hasNext();) { Mapping m = (Mapping) it.next(); if (m instanceof ClassIdPropertyMapping) { idMappingList.add((ClassIdPropertyMapping) m); } if (m instanceof IdComponentMapping) { IdComponentMapping idComponentMapping = (IdComponentMapping) m; idMappingList.addAll(idComponentMapping.getRefClassMappings()[0].findClassPropertyIdMappings()); } } return idMappingList; } public List<ClassPropertyMapping> findClassPropertiesRequireProcessing() { ArrayList<ClassPropertyMapping> idMappingList = new ArrayList<ClassPropertyMapping>(); for (Iterator it = mappingsIt(); it.hasNext();) { Mapping m = (Mapping) it.next(); if (m instanceof ClassIdPropertyMapping) { idMappingList.add((ClassIdPropertyMapping) m); } else if (m instanceof IdComponentMapping) { IdComponentMapping idComponentMapping = (IdComponentMapping) m; idMappingList.addAll(idComponentMapping.getRefClassMappings()[0].findClassPropertyIdMappings()); } else if (m instanceof ClassPropertyMapping) { if (((ClassPropertyMapping) m).requiresIdProcessing()) { idMappingList.add((ClassPropertyMapping) m); } } } return idMappingList; } public ResourcePropertyMapping getResourcePropertyMappingByDotPath(String path) { return pathMappings.get(path); } public ManagedId getManagedId() { return managedId; } public void setManagedId(ManagedId managedId) { this.managedId = managedId; } public boolean isPoly() { return poly; } public void setPoly(boolean poly) { this.poly = poly; } public PropertyPath getClassPath() { return classPath; } public void setClassPath(PropertyPath classPath) { this.classPath = classPath; } public PropertyPath getEnumNamePath() { return enumNamePath; } public void setEnumNamePath(PropertyPath enumNamePath) { this.enumNamePath = enumNamePath; } public Class getClazz() { return clazz; } public void setClazz(Class clazz) { this.clazz = clazz; } /** * In case poly is set to <code>true</code>, this will be the class that will * be instanciated for all persisted classes. If not set, Compass will persist * the actual class in the index, and will use it to instanciate the class. */ public Class getPolyClass() { return polyClass; } public void setPolyClass(Class polyClass) { this.polyClass = polyClass; } public boolean isSupportUnmarshall() { // possible NPE, will take care of it in setting it return supportUnmarshall; } public void setSupportUnmarshall(boolean supportUnmarshall) { this.supportUnmarshall = supportUnmarshall; } public boolean isSupportUnmarshallSet() { return supportUnmarshall != null; } public Boolean isFilterDuplicates() { return filterDuplicates; } public void setFilterDuplicates(Boolean filterDuplicates) { this.filterDuplicates = filterDuplicates; } public ReflectionConstructor getConstructor() { return constructor; } public void setConstructor(ReflectionConstructor constructor) { this.constructor = constructor; } public ReflectionConstructor getPolyConstructor() { return polyConstructor; } public void setPolyConstructor(ReflectionConstructor polyConstructor) { this.polyConstructor = polyConstructor; } public PropertyPath getBasePath() { return basePath; } public void setBasePath(PropertyPath basePath) { this.basePath = basePath; } public class PostProcessMappingCallback extends OsemMappingIterator.ClassPropertyAndResourcePropertyGatherer { private HashMap<String, ResourcePropertyMapping> pathMappings = new HashMap<String, ResourcePropertyMapping>(); private ArrayList<String> pathSteps = new ArrayList<String>(); private StringBuilder sb = new StringBuilder(); private Set<String> cyclicClassMappings = new HashSet<String>(); class NoUnmarshallHolder { ClassMapping parent; ClassMapping classMapping; NoUnmarshallHolder(ClassMapping parent, ClassMapping classMapping) { this.parent = parent; this.classMapping = classMapping; } } /** * In case we do not need to support unmarshalling, we need to perform simple cyclic detection * and return <code>false</code> (won't iterate into this class mapping) if we already passed * this class mapping. We will remove the marker in the {@link #onEndClassMapping(ClassMapping)}. */ public boolean onBeginClassMapping(ClassMapping classMapping) { if (classMapping.isSupportUnmarshall()) { return true; } if (cyclicClassMappings.contains(classMapping.getAlias())) { return false; } cyclicClassMappings.add(classMapping.getAlias()); return true; } /** * If we do not support unmarshalling, we need to clean up our marker for this class mapping. */ public void onEndClassMapping(ClassMapping classMapping) { if (classMapping.isSupportUnmarshall()) { return; } cyclicClassMappings.remove(classMapping.getAlias()); } /** * <p>Since we did not process duplicate mappings, we need to replace them with the original mappings that * were processed (for example, we added internal ids to it where needed). */ protected void onDuplicateMapping(ClassMapping classMapping, ObjectMapping actualMapping, ObjectMapping duplicateMapping) { Assert.isTrue(actualMapping.getPropertyName().equals(duplicateMapping.getPropertyName()), "Internal Error in Compass, Original[" + duplicateMapping.getName() + "] does not equal [" + actualMapping.getName() + "]"); // TODO since we replace the mappings here, some attributes will be inacurate (like objClass) for the replaced class mapping int index = classMapping.mappings.indexOf(duplicateMapping); if (index < 0) { // let's look in the collection, if we find it as an element // then we just replace it (the duplicate mapping might raise // a duplicate for a collection, but with the collection element) for (int i = 0; i < classMapping.mappings.size(); i++) { Object o = classMapping.mappings.get(i); if (o instanceof AbstractCollectionMapping) { AbstractCollectionMapping temp = (AbstractCollectionMapping) o; if (temp.getElementMapping() == duplicateMapping) { temp.setElementMapping(actualMapping); index = i; break; } } } } else { classMapping.mappingsByNameMap.put(duplicateMapping.getName(), actualMapping); classMapping.mappings.set(index, actualMapping); } if (index < 0) { throw new IllegalStateException("Internal Error in Compass, original mapping [" + duplicateMapping.getName() + "] not found"); } } private void addToPath(String name) { pathSteps.add(name); } private void removeFromPath() { if (pathSteps.size() > 0) { pathSteps.remove(pathSteps.size() - 1); } } /** */ public boolean onBeginMultipleMapping(ClassMapping classMapping, Mapping mapping) { boolean retVal = super.onBeginMultipleMapping(classMapping, mapping); addToPath(mapping.getName()); return retVal; } public void onEndMultiplMapping(ClassMapping classMapping, Mapping mapping) { super.onEndMultiplMapping(classMapping, mapping); removeFromPath(); } public void onClassPropertyMapping(ClassMapping classMapping, ClassPropertyMapping mapping) { super.onClassPropertyMapping(classMapping, mapping); ResourcePropertyMapping resourcePropertyMapping = mapping.getIdMapping(); if (resourcePropertyMapping == null && mapping.mappingsSize() > 0) { resourcePropertyMapping = (ResourcePropertyMapping) mapping.mappingsIt().next(); } pathMappings.put(currentPath(), resourcePropertyMapping); } public void onResourcePropertyMapping(ResourcePropertyMapping mapping) { super.onResourcePropertyMapping(mapping); if (!mapping.isInternal()) { addToPath(mapping.getName()); } pathMappings.put(currentPath(), mapping); if (!mapping.isInternal()) { removeFromPath(); } if (mapping instanceof ClassPropertyMetaDataMapping) { if (!mapping.isInternal()) { addToPath(((ClassPropertyMetaDataMapping) mapping).getOriginalName()); } pathMappings.put(currentPath(), mapping); if (!mapping.isInternal()) { removeFromPath(); } } } public HashMap<String, ResourcePropertyMapping> getPathMappings() { return pathMappings; } private String currentPath() { sb.setLength(0); for (int i = 0; i < pathSteps.size(); i++) { if (i > 0) { sb.append('.'); } sb.append(pathSteps.get(i)); } return sb.toString(); } } }