/* * 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.config.process; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Set; import org.compass.core.config.CompassSettings; import org.compass.core.converter.ConverterLookup; import org.compass.core.engine.naming.PropertyNamingStrategy; import org.compass.core.engine.naming.PropertyPath; import org.compass.core.engine.naming.StaticPropertyPath; import org.compass.core.mapping.AliasMapping; import org.compass.core.mapping.CompassMapping; import org.compass.core.mapping.Mapping; import org.compass.core.mapping.MappingException; import org.compass.core.mapping.ResourcePropertyMapping; import org.compass.core.mapping.internal.InternalCompassMapping; import org.compass.core.mapping.internal.InternalMapping; import org.compass.core.mapping.osem.*; import org.compass.core.marshall.MarshallingEnvironment; import org.compass.core.util.IdentityHashSet; import org.compass.core.util.StringUtils; /** * @author kimchy */ public class LateBindingOsemMappingProcessor implements MappingProcessor { private CompassMapping compassMapping; private PropertyNamingStrategy namingStrategy; private ConverterLookup converterLookup; private CompassSettings settings; // helper runtime state private List<ComponentMapping> chainedComponents = new ArrayList<ComponentMapping>(); private LinkedList<String> prefixes = new LinkedList<String>(); /** * Used to externaly control the managed id option (for example for ref-comp-mapping, * where we do not want internal ids being created, since we will never unmarshall it) */ private ManagedId managedId = null; public CompassMapping process(CompassMapping compassMapping, PropertyNamingStrategy namingStrategy, ConverterLookup converterLookup, CompassSettings settings) throws MappingException { this.compassMapping = compassMapping; this.namingStrategy = namingStrategy; this.converterLookup = converterLookup; this.settings = settings; ArrayList<AliasMapping> mappings = new ArrayList<AliasMapping>(); ((InternalCompassMapping) compassMapping).setPath(namingStrategy.getRootPath()); for (AliasMapping aliasMapping : compassMapping.getMappings()) { if (aliasMapping instanceof ClassMapping) { clearRootClassMappingState(); ClassMapping classMapping = (ClassMapping) aliasMapping; if (classMapping.isSupportUnmarshall()) { classMapping = (ClassMapping) classMapping.copy(); secondPass(classMapping, compassMapping); } else { // classMapping = (ClassMapping) classMapping.copy(); secondPassNoUnmarshalling(classMapping); } mappings.add(classMapping); } else { mappings.add(aliasMapping); } } ((InternalCompassMapping) compassMapping).clearMappings(); for (AliasMapping aliasMapping : mappings) { ((InternalCompassMapping) compassMapping).addMapping(aliasMapping); } return compassMapping; } private void secondPassNoUnmarshalling(ClassMapping classMapping) { classMapping.setPath(namingStrategy.buildPath(compassMapping.getPath(), classMapping.getAlias())); classMapping.setClassPath(namingStrategy.buildPath(classMapping.getPath(), MarshallingEnvironment.PROPERTY_CLASS).hintStatic()); classMapping.setEnumNamePath(namingStrategy.buildPath(classMapping.getPath(), MarshallingEnvironment.PROPERTY_ENUM_NAME).hintStatic()); OsemMappingIterator.iterateMappings(new NoUnmarshallingCallback(classMapping), classMapping, true); } private void secondPass(ClassMapping classMapping, CompassMapping fatherMapping) { classMapping.setPath(namingStrategy.buildPath(fatherMapping.getPath(), classMapping.getAlias())); secondPass(classMapping, false, true); } private void secondPass(ClassMapping classMapping, boolean onlyProperties, boolean topmost) { classMapping.setClassPath(namingStrategy.buildPath(classMapping.getPath(), MarshallingEnvironment.PROPERTY_CLASS).hintStatic()); classMapping.setEnumNamePath(namingStrategy.buildPath(classMapping.getPath(), MarshallingEnvironment.PROPERTY_ENUM_NAME).hintStatic()); ArrayList<Mapping> innerMappingsCopy = new ArrayList<Mapping>(); for (Iterator it = classMapping.mappingsIt(); it.hasNext();) { Mapping m = (Mapping) it.next(); Mapping copyMapping = m.copy(); boolean removeMapping = false; if ((copyMapping instanceof ObjectMapping) && topmost) { PropertyPath aliasedPath = namingStrategy.buildPath(compassMapping.getPath(), ((ObjectMapping) copyMapping).getDefinedInAlias()); ((InternalMapping) copyMapping).setPath(namingStrategy.buildPath(aliasedPath, copyMapping.getName())); } else { ((InternalMapping) copyMapping).setPath(namingStrategy.buildPath(classMapping.getPath(), copyMapping.getName())); } if (copyMapping instanceof ClassPropertyMapping) { removeMapping = secondPass((ClassPropertyMapping) copyMapping, classMapping); } else if (copyMapping instanceof IdComponentMapping) { removeMapping = secondPass((IdComponentMapping) copyMapping, classMapping); } else { if (!onlyProperties) { if (copyMapping instanceof ClassDynamicPropertyMapping) { removeMapping = secondPass((ClassDynamicPropertyMapping) copyMapping); } else if (copyMapping instanceof ComponentMapping) { removeMapping = secondPass((ComponentMapping) copyMapping, classMapping); } else if (copyMapping instanceof ReferenceMapping) { removeMapping = secondPass((ReferenceMapping) copyMapping, classMapping); } else if (copyMapping instanceof ConstantMetaDataMapping) { removeMapping = secondPass((ConstantMetaDataMapping) copyMapping); } else if (copyMapping instanceof ParentMapping) { // nothing to do here } else if (copyMapping instanceof AbstractCollectionMapping) { removeMapping = secondPass((AbstractCollectionMapping) copyMapping, classMapping); } else if (copyMapping instanceof DynamicMetaDataMapping) { removeMapping = secondPass((DynamicMetaDataMapping) copyMapping); } } } if (!removeMapping) { innerMappingsCopy.add(copyMapping); } } classMapping.clearMappings(); for (Iterator<Mapping> it = innerMappingsCopy.iterator(); it.hasNext();) { classMapping.addMapping(it.next()); } } private boolean secondPass(AbstractCollectionMapping collectionMapping, Mapping fatherMapping) { Mapping elementMapping = collectionMapping.getElementMapping(); Mapping elementMappingCopy = elementMapping.copy(); ((InternalMapping) elementMappingCopy).setPath(collectionMapping.getPath()); boolean removeMapping = false; if (elementMappingCopy instanceof ClassPropertyMapping) { removeMapping = secondPass((ClassPropertyMapping) elementMappingCopy, fatherMapping); } else if (elementMappingCopy instanceof ComponentMapping) { removeMapping = secondPass((ComponentMapping) elementMappingCopy, fatherMapping); } else if (elementMappingCopy instanceof ReferenceMapping) { removeMapping = secondPass((ReferenceMapping) elementMappingCopy, fatherMapping); } collectionMapping.setElementMapping(elementMappingCopy); collectionMapping.setCollectionTypePath(namingStrategy.buildPath(collectionMapping.getPath(), MarshallingEnvironment.PROPERTY_COLLECTION_TYPE).hintStatic()); collectionMapping.setColSizePath(namingStrategy.buildPath(collectionMapping.getPath(), MarshallingEnvironment.PROPERTY_COLLECTION_SIZE).hintStatic()); return removeMapping; } private boolean secondPass(ReferenceMapping referenceMapping, Mapping fatherMapping) { secondPassJustReference(referenceMapping, fatherMapping); // now configure the component mapping if exists if (referenceMapping.getRefCompAlias() != null) { ClassMapping pointerClass = (ClassMapping) compassMapping.getMappingByAlias(referenceMapping .getRefCompAlias()); if (pointerClass == null) { throw new MappingException("Failed to locate mapping for reference ref-comp-alias [" + referenceMapping.getRefCompAlias() + "]"); } ClassMapping refClass = (ClassMapping) pointerClass.copy(); refClass.setPath(namingStrategy.buildPath(referenceMapping.getPath(), referenceMapping.getRefCompAlias())); // we do not want to create internal ids, since we will never unmarshall it managedId = ManagedId.FALSE; secondPass(refClass, false, false); managedId = null; refClass.setRoot(false); referenceMapping.setRefCompMapping(refClass); } return false; } private void secondPassJustReference(ReferenceMapping referenceMapping, Mapping fatherMapping) { ClassMapping[] refMappings = referenceMapping.getRefClassMappings(); ClassMapping[] copyRefClassMappings = new ClassMapping[refMappings.length]; for (int i = 0; i < refMappings.length; i++) { // in case of reference, use internal ids for the refrence ids // get the original ids, since we will copy them later List<Mapping> ids = refMappings[i].findIdMappings(); // shallow copy the ref class mappings, and ony add the ids (as copies) ClassMapping refClass = (ClassMapping) refMappings[i].shallowCopy(); for (Object id : ids) { refClass.addMapping(((Mapping) id).copy()); } refClass.setPath(referenceMapping.getPath()); secondPass(refClass, true, false); for (ClassIdPropertyMapping mapping : refClass.findClassPropertyIdMappings()) { mapping.clearMappings(); // create the internal id MappingProcessorUtils.addInternalId(settings, converterLookup, mapping, true); } // since we create our own special ref class mapping that only holds the // ids, we need to call the post process here refClass.postProcess(); copyRefClassMappings[i] = refClass; } referenceMapping.setRefClassMappings(copyRefClassMappings); } private boolean secondPass(ComponentMapping compMapping, Mapping fatherMapping) { int numberOfComponentsWithTheSameAlias = 0; for (Iterator<ComponentMapping> it = chainedComponents.iterator(); it.hasNext();) { ComponentMapping tempComponentMapping = it.next(); if (compMapping.hasAtLeastOneRefAlias(tempComponentMapping.getRefAliases())) { numberOfComponentsWithTheSameAlias++; } } if (numberOfComponentsWithTheSameAlias >= compMapping.getMaxDepth()) { return true; } chainedComponents.add(compMapping); if (compMapping.getPrefix() != null) { prefixes.add(compMapping.getPrefix()); } ClassMapping[] refClassMappings = compMapping.getRefClassMappings(); ClassMapping[] copyRefClassMappings = new ClassMapping[refClassMappings.length]; for (int i = 0; i < refClassMappings.length; i++) { ClassMapping refClassMapping = (ClassMapping) refClassMappings[i].copy(); refClassMapping.setPath(compMapping.getPath()); secondPass(refClassMapping, false, false); refClassMapping.setRoot(false); copyRefClassMappings[i] = refClassMapping; } compMapping.setRefClassMappings(copyRefClassMappings); chainedComponents.remove(compMapping); if (compMapping.getPrefix() != null) { prefixes.removeLast(); } return false; } private boolean secondPass(ClassPropertyMapping classPropertyMapping, Mapping fatherMapping) { // we check if we override the managedId option if (managedId != null) { classPropertyMapping.setManagedId(managedId); } ArrayList<ClassPropertyMetaDataMapping> innerMappingsCopy = new ArrayList<ClassPropertyMetaDataMapping>(); for (Iterator it = classPropertyMapping.mappingsIt(); it.hasNext();) { Mapping m = (Mapping) it.next(); ClassPropertyMetaDataMapping metaDataMappingCopy = (ClassPropertyMetaDataMapping) m.copy(); metaDataMappingCopy.setName(buildFullName(metaDataMappingCopy.getOriginalName())); MappingProcessorUtils.process(metaDataMappingCopy, classPropertyMapping, converterLookup); innerMappingsCopy.add(metaDataMappingCopy); } classPropertyMapping.clearMappings(); for (Iterator<ClassPropertyMetaDataMapping> it = innerMappingsCopy.iterator(); it.hasNext();) { classPropertyMapping.addMapping(it.next()); } return false; } private boolean secondPass(ConstantMetaDataMapping constantMapping) { constantMapping.setName(buildFullName(constantMapping.getOriginalName())); constantMapping.setPath(new StaticPropertyPath(constantMapping.getName())); return false; } private boolean secondPass(DynamicMetaDataMapping dynamicMetaDataMapping) { dynamicMetaDataMapping.setName(buildFullName(dynamicMetaDataMapping.getOriginalName())); dynamicMetaDataMapping.setPath(new StaticPropertyPath(dynamicMetaDataMapping.getName())); return false; } private boolean secondPass(ClassDynamicPropertyMapping dynamicPropertyMapping) { dynamicPropertyMapping.setNamePrefix(buildDynamicNamePrefix(dynamicPropertyMapping.getNamePrefix())); return false; } private void clearRootClassMappingState() { chainedComponents.clear(); prefixes.clear(); } private String buildDynamicNamePrefix(String namePrefix) { if (namePrefix == null) { namePrefix = buildFullName(""); } else { namePrefix = buildFullName(namePrefix); } if (StringUtils.hasText(namePrefix)) { return namePrefix; } return null; } private String buildFullName(String name) { if (prefixes.isEmpty()) { return name; } StringBuilder sb = new StringBuilder(); for (String prefix : prefixes) { sb.append(prefix); } sb.append(name); return sb.toString(); } private class NoUnmarshallingCallback implements OsemMappingIterator.ClassMappingCallback { private ClassMapping rootClassMapping; private IdentityHashSet<ClassMapping> idComponents = new IdentityHashSet<ClassMapping>(); private Set<String> cyclicClassMappings = new HashSet<String>(); private ClassPropertyMapping classPropertyMapping; private NoUnmarshallingCallback(ClassMapping rootClassMapping) { this.rootClassMapping = rootClassMapping; } /** * 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 (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()); } public boolean onBeginMultipleMapping(ClassMapping classMapping, Mapping mapping) { if ((mapping instanceof ReferenceMapping) && classMapping != rootClassMapping) { return false; } if (mapping instanceof ComponentMapping) { ComponentMapping componentMapping = (ComponentMapping) mapping; if (componentMapping.getPrefix() != null) { prefixes.add(componentMapping.getPrefix()); } } return true; } public void onEndMultiplMapping(ClassMapping classMapping, Mapping mapping) { if (mapping instanceof ComponentMapping) { ComponentMapping componentMapping = (ComponentMapping) mapping; if (componentMapping.getPrefix() != null) { prefixes.removeLast(); } } } public void onBeginCollectionMapping(AbstractCollectionMapping collectionMapping) { } public void onEndCollectionMapping(AbstractCollectionMapping collectionMapping) { } public void onClassPropertyMapping(ClassMapping classMapping, ClassPropertyMapping classPropertyMapping) { this.classPropertyMapping = classPropertyMapping; if (classMapping == rootClassMapping) { // only apply the path for class properties that belong to the class mapping we started with // and not onces we iterate through recuresivly. This is because then we override (by mistake) // internal ids of other class mappings. PropertyPath aliasedPath = namingStrategy.buildPath(compassMapping.getPath(), classPropertyMapping.getDefinedInAlias()); classPropertyMapping.setPath(namingStrategy.buildPath(aliasedPath, classPropertyMapping.getName())); } else if (idComponents.contains(classMapping)) { classPropertyMapping.setPath(namingStrategy.buildPath(classMapping.getPath(), classPropertyMapping.getName())); } } public void onClassDynamicPropertyMapping(ClassMapping classMapping, ClassDynamicPropertyMapping dynamicPropertyMapping) { if (classMapping == rootClassMapping) { PropertyPath aliasedPath = namingStrategy.buildPath(compassMapping.getPath(), dynamicPropertyMapping.getDefinedInAlias()); dynamicPropertyMapping.setPath(namingStrategy.buildPath(aliasedPath, dynamicPropertyMapping.getName())); } else { dynamicPropertyMapping.setPath(namingStrategy.buildPath(classMapping.getPath(), dynamicPropertyMapping.getName())); } dynamicPropertyMapping.setNamePrefix(buildDynamicNamePrefix(dynamicPropertyMapping.getNamePrefix())); } public void onParentMapping(ClassMapping classMapping, ParentMapping parentMapping) { } public void onCascadeMapping(ClassMapping classMapping, PlainCascadeMapping cascadeMapping) { } public void onComponentMapping(ClassMapping classMapping, ComponentMapping componentMapping) { ClassMapping[] refClassMappings = componentMapping.getRefClassMappings(); ClassMapping[] copyRefClassMappings = new ClassMapping[refClassMappings.length]; for (int i = 0; i < refClassMappings.length; i++) { ClassMapping refClassMapping; if (componentMapping.getPrefix() != null) { refClassMapping = (ClassMapping) refClassMappings[i].copy(); refClassMapping.setPath(componentMapping.getPath()); refClassMapping.setSupportUnmarshall(classMapping.isSupportUnmarshall()); } else if (componentMapping instanceof IdComponentMapping && (classMapping == rootClassMapping)) { refClassMapping = (ClassMapping) refClassMappings[i].copy(); PropertyPath aliasedPath = namingStrategy.buildPath(compassMapping.getPath(), componentMapping.getDefinedInAlias()); refClassMapping.setPath(namingStrategy.buildPath(aliasedPath, componentMapping.getName())); idComponents.add(refClassMapping); // We set here that we support unmarshall since we need to marshall the internal ids of the // id component refClassMapping.setSupportUnmarshall(true); } else { // perform a shalow copy, and copy over the child mappings refClassMapping = (ClassMapping) refClassMappings[i].shallowCopy(); refClassMapping.replaceMappings(refClassMappings[i]); refClassMapping.setPath(componentMapping.getPath()); refClassMapping.setSupportUnmarshall(classMapping.isSupportUnmarshall()); } refClassMapping.setRoot(false); copyRefClassMappings[i] = refClassMapping; } componentMapping.setRefClassMappings(copyRefClassMappings); } public void onReferenceMapping(ClassMapping classMapping, ReferenceMapping referenceMapping) { // TODO why do we even process refernece mapping when we don't support unmarshall? PropertyPath aliasedPath = namingStrategy.buildPath(compassMapping.getPath(), referenceMapping.getDefinedInAlias()); referenceMapping.setPath(namingStrategy.buildPath(aliasedPath, referenceMapping.getName())); secondPassJustReference(referenceMapping, classMapping); } public void onConstantMetaDataMappaing(ClassMapping classMapping, ConstantMetaDataMapping constantMetaDataMapping) { constantMetaDataMapping.setName(buildFullName(constantMetaDataMapping.getOriginalName())); constantMetaDataMapping.setPath(new StaticPropertyPath(constantMetaDataMapping.getName())); } public void onDynamicMetaDataMapping(ClassMapping classMapping, DynamicMetaDataMapping dynamicMetaDataMapping) { dynamicMetaDataMapping.setName(buildFullName(dynamicMetaDataMapping.getOriginalName())); dynamicMetaDataMapping.setPath(new StaticPropertyPath(dynamicMetaDataMapping.getName())); } public void onClassPropertyMetaDataMapping(ClassPropertyMetaDataMapping classPropertyMetaDataMapping) { classPropertyMetaDataMapping.setName(buildFullName(classPropertyMetaDataMapping.getOriginalName())); MappingProcessorUtils.process(classPropertyMetaDataMapping, classPropertyMapping, converterLookup); } public void onResourcePropertyMapping(ResourcePropertyMapping resourcePropertyMapping) { } } }