/*
* Copyright (c) 2005-2016 Vincent Vandenschrick. All rights reserved.
*
* This file is part of the Jspresso framework.
*
* Jspresso is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Jspresso is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Jspresso. If not, see <http://www.gnu.org/licenses/>.
*/
package org.jspresso.framework.model.persistence.mongo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Proxy;
import java.util.HashSet;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PropertyPath;
import org.springframework.data.mapping.SimplePropertyHandler;
import org.springframework.data.mapping.context.PersistentPropertyPath;
import org.springframework.data.mapping.model.AbstractPersistentProperty;
import org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.util.TypeInformation;
import org.jspresso.framework.model.descriptor.IComponentDescriptor;
import org.jspresso.framework.model.descriptor.IComponentDescriptorRegistry;
import org.jspresso.framework.model.descriptor.IPropertyDescriptor;
import org.jspresso.framework.model.entity.IEntity;
import org.jspresso.framework.util.bean.PropertyHelper;
import org.jspresso.framework.util.exception.NestedRuntimeException;
import org.jspresso.framework.util.reflect.ReflectHelper;
/**
* Custom Jspresso Mongo mapping context.
*
* @author Vincent Vandenschrick
*/
public class JspressoMongoMappingContext extends MongoMappingContext {
private static final Logger LOG = LoggerFactory.getLogger(JspressoMongoMappingContext.class);
private IComponentDescriptorRegistry descriptorRegistry;
/**
* Gets persistent entity.
*
* @param type
* the type
* @return the persistent entity
*/
@Override
public BasicMongoPersistentEntity<?> getPersistentEntity(Class<?> type) {
return super.getPersistentEntity(getEntityContractFromType(type));
}
/**
* Has persistent entity for.
*
* @param type
* the type
* @return the boolean
*/
@Override
public boolean hasPersistentEntityFor(Class<?> type) {
return super.hasPersistentEntityFor(getEntityContractFromType(type));
}
/**
* Gets persistent property path.
*
* @param propertyPath
* the property path
* @param type
* the type
* @return the persistent property path
*/
@Override
public PersistentPropertyPath<MongoPersistentProperty> getPersistentPropertyPath(String propertyPath, Class<?> type) {
StringBuilder fixedPropertyPath = new StringBuilder();
Class<?> entityContract = getEntityContractFromType(type);
IComponentDescriptor<?> baseComponentDescriptor = descriptorRegistry.getComponentDescriptor(entityContract);
for (String propertyName : propertyPath.split("\\.")) {
IPropertyDescriptor propertyDescriptor = baseComponentDescriptor.getPropertyDescriptor(propertyName);
String fixedPropertyName = propertyName;
if (propertyDescriptor == null) {
for (IPropertyDescriptor pd : baseComponentDescriptor.getPropertyDescriptors()) {
if (propertyName.equals(pd.getPersistenceFormula())) {
fixedPropertyName = pd.getName();
}
}
}
if (fixedPropertyPath.length() > 0) {
fixedPropertyPath.append(".");
}
fixedPropertyPath.append(fixedPropertyName);
}
return super.getPersistentPropertyPath(fixedPropertyPath.toString(), entityContract);
}
/**
* Gets persistent property path.
*
* @param propertyPath
* the property path
* @return the persistent property path
*/
@Override
public PersistentPropertyPath<MongoPersistentProperty> getPersistentPropertyPath(PropertyPath propertyPath) {
return getPersistentPropertyPath(propertyPath.toDotPath(), propertyPath.getOwningType().getType());
}
/**
* Add persistent entity.
*
* @param type
* the type
* @return the basic mongo persistent entity
*/
@Override
protected BasicMongoPersistentEntity<?> addPersistentEntity(Class<?> type) {
return super.addPersistentEntity(getEntityContractFromType(type));
}
/**
* Add persistent entity.
*
* @param typeInformation
* the type information
* @return the basic mongo persistent entity
*/
@SuppressWarnings("unchecked")
@Override
protected BasicMongoPersistentEntity<?> addPersistentEntity(TypeInformation<?> typeInformation) {
final BasicMongoPersistentEntity<?> entity = super.addPersistentEntity(typeInformation);
final Class<?> entityType = typeInformation.getType();
final IComponentDescriptor<? extends IEntity> entityDescriptor = (IComponentDescriptor<? extends IEntity>)
getDescriptorRegistry()
.getComponentDescriptor(entityType);
if (entityDescriptor != null) {
final Set<String> entityDeclaredPropertyNames = new HashSet<>();
try {
for (PropertyDescriptor propertyDescriptor : Introspector.getBeanInfo(entityType).getPropertyDescriptors()) {
entityDeclaredPropertyNames.add(propertyDescriptor.getName());
}
} catch (IntrospectionException e) {
LOG.error("Could not extract bean info from class {}", entityType, e);
throw new NestedRuntimeException(e);
}
for (Class<?> superInterface : entityType.getInterfaces()) {
final IComponentDescriptor<? extends IEntity> parentDescriptor = (IComponentDescriptor<? extends IEntity>)
getDescriptorRegistry()
.getComponentDescriptor(superInterface);
if (parentDescriptor != null) {
BasicMongoPersistentEntity<?> superEntity = getPersistentEntity(superInterface);
if (superEntity != null) {
superEntity.doWithProperties(new SimplePropertyHandler() {
@Override
public void doWithPersistentProperty(PersistentProperty<?> parentPersistentProperty) {
String propertyName = parentPersistentProperty.getName();
IPropertyDescriptor propertyDescriptor = parentDescriptor.getPropertyDescriptor(propertyName);
MongoPersistentProperty declaredPersistentProperty = entity.getPersistentProperty(
PropertyHelper.toJavaBeanPropertyName(propertyName));
if (declaredPersistentProperty != null && declaredPersistentProperty.getSetter() == null &&
parentPersistentProperty.getSetter() != null) {
try {
// Some properties will be writable in parent but not locally, so we must fix their descriptor.
PropertyDescriptor localDescriptor = (PropertyDescriptor) ReflectHelper.getPrivateFieldValue(
AbstractPersistentProperty.class, "propertyDescriptor", declaredPersistentProperty);
localDescriptor.setWriteMethod(parentPersistentProperty.getSetter());
} catch (IllegalAccessException | NoSuchFieldException | IntrospectionException e) {
LOG.error("Could not extract propertyDescriptor {} from persistent class {}", propertyName,
entityType.getName());
}
}
if (parentPersistentProperty instanceof MongoPersistentProperty && declaredPersistentProperty == null
&& !entityDeclaredPropertyNames.contains(propertyName) && propertyDescriptor != null
&& !propertyDescriptor.isComputed()) {
entity.addPersistentProperty((MongoPersistentProperty) parentPersistentProperty);
}
}
});
}
}
}
}
return entity;
}
private Class<?> getEntityContractFromType(Class<?> type) {
if (Proxy.isProxyClass(type)) {
for (Class<?> superInterface : type.getInterfaces()) {
if (IEntity.class.isAssignableFrom(superInterface)) {
return superInterface;
}
}
}
return type;
}
/**
* Gets descriptor registry.
*
* @return the descriptor registry
*/
protected IComponentDescriptorRegistry getDescriptorRegistry() {
return descriptorRegistry;
}
/**
* Sets descriptor registry.
*
* @param descriptorRegistry
* the descriptor registry
*/
public void setDescriptorRegistry(IComponentDescriptorRegistry descriptorRegistry) {
this.descriptorRegistry = descriptorRegistry;
}
}