/*
* 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.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import com.mongodb.BasicDBList;
import com.mongodb.DBObject;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalGenericConverter;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.jspresso.framework.application.backend.BackendControllerHolder;
import org.jspresso.framework.application.backend.IBackendController;
import org.jspresso.framework.model.component.IComponent;
import org.jspresso.framework.model.component.IComponentCollectionFactory;
import org.jspresso.framework.model.descriptor.ICollectionPropertyDescriptor;
import org.jspresso.framework.model.descriptor.IComponentDescriptor;
import org.jspresso.framework.model.descriptor.IPropertyDescriptor;
import org.jspresso.framework.model.descriptor.IReferencePropertyDescriptor;
import org.jspresso.framework.model.descriptor.IRelationshipEndPropertyDescriptor;
import org.jspresso.framework.model.entity.IEntity;
import org.jspresso.framework.model.entity.IEntityFactory;
import org.jspresso.framework.model.entity.IEntityRegistry;
import org.jspresso.framework.model.entity.basic.BasicEntityRegistry;
import org.jspresso.framework.util.bean.PropertyHelper;
/**
* Custom converter for Jspresso entities.
*
* @author Vincent Vandenschrick
*/
public class JspressoEntityReadConverter
implements ConditionalGenericConverter, ApplicationListener<ContextRefreshedEvent> {
private IEntityFactory entityFactory;
private IComponentCollectionFactory collectionFactory;
private MongoTemplate mongo;
private JspressoMappingMongoConverter converter;
/**
* Convert object.
*
* @param source
* the generic source
* @param sourceType
* the source type
* @param targetType
* the target type
* @return the object
*/
@SuppressWarnings("unchecked")
@Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
IEntityRegistry readerRegistry = new BasicEntityRegistry("JspressoEntityReadConverter");
return convertEntity((DBObject) source, (Class<? extends IEntity>) targetType.getType(), readerRegistry);
}
@SuppressWarnings("unchecked")
private IEntity convertEntity(DBObject source, Class<? extends IEntity> entityType, IEntityRegistry readerRegistry) {
Serializable id = (Serializable) source.get("_id");
IComponentDescriptor<? extends IEntity> entityDescriptor = (IComponentDescriptor<? extends IEntity>)
getEntityFactory()
.getComponentDescriptor(entityType);
IEntity entity = getBackendController().getUnitOfWorkOrRegisteredEntity(entityType, id);
if (entity == null) {
entity = readerRegistry.get(entityType, id);
if (entity == null) {
entity = getEntityFactory().createEntityInstance(entityType, id);
readerRegistry.register(entityType, id, entity);
completeComponent(source, entityDescriptor, entity, readerRegistry);
if (entity.getVersion() == null) {
// Make sure that even if persistent store does not have a version property, the entity has one and is
// considered persistent.
entity.setVersion(0);
}
}
}
return entity;
}
@SuppressWarnings("unchecked")
private IEntity convertEntity(Serializable id, Class<IEntity> entityType, IEntityRegistry readerRegistry) {
IEntity entity = getBackendController().getUnitOfWorkOrRegisteredEntity(entityType, id);
if (entity == null) {
entity = readerRegistry.get(entityType, id);
if (entity == null) {
entity = createProxyEntity(id, entityType);
readerRegistry.register(entityType, id, entity);
}
}
return entity;
}
@SuppressWarnings("unchecked")
private Object convertComponent(DBObject source, Class<? extends IComponent> componentType,
IEntityRegistry readerRegistry) {
IComponentDescriptor<? extends IComponent> componentDescriptor = (IComponentDescriptor<? extends IComponent>)
getEntityFactory()
.getComponentDescriptor(componentType);
IComponent component = getEntityFactory().createComponentInstance(componentType);
completeComponent(source, componentDescriptor, component, readerRegistry);
return component;
}
@SuppressWarnings("unchecked")
private void completeComponent(DBObject source, IComponentDescriptor<? extends IComponent> entityDescriptor,
IComponent component, IEntityRegistry readerRegistry) {
Class<? extends IComponent> componentContract = component.getComponentContract();
for (IPropertyDescriptor propertyDescriptor : entityDescriptor.getPropertyDescriptors()) {
if (propertyDescriptor != null && !propertyDescriptor.isComputed()) {
String propertyName = propertyDescriptor.getName();
Class<?> propertyType = propertyDescriptor.getModelType();
String convertedPropertyName = getConverter().getMappingContext().getPersistentEntity(componentContract)
.getPersistentProperty(PropertyHelper.toJavaBeanPropertyName(
propertyName))
.getFieldName();
if (source.containsField(convertedPropertyName)) {
Object propertyValue = source.get(convertedPropertyName);
Class<?> componentRefType = null;
if (propertyDescriptor instanceof IRelationshipEndPropertyDescriptor) {
if (propertyDescriptor instanceof IReferencePropertyDescriptor<?>) {
componentRefType = ((IReferencePropertyDescriptor<?>) propertyDescriptor).getReferencedDescriptor()
.getModelType();
} else if (propertyDescriptor instanceof ICollectionPropertyDescriptor<?>) {
componentRefType = ((ICollectionPropertyDescriptor<?>) propertyDescriptor).getCollectionDescriptor()
.getElementDescriptor()
.getModelType();
}
}
if (propertyValue instanceof DBObject) {
if (propertyValue instanceof BasicDBList) {
if (propertyDescriptor instanceof ICollectionPropertyDescriptor<?>) {
Class<? extends Collection<?>> collectionInterface = ((ICollectionPropertyDescriptor)
propertyDescriptor)
.getCollectionDescriptor().getCollectionInterface();
if (IComponent.class.isAssignableFrom(componentRefType)) {
if (IEntity.class.isAssignableFrom(componentRefType)) {
Collection<Serializable> collectionProperty = getCollectionFactory().createComponentCollection(
collectionInterface);
for (Object element : (BasicDBList) propertyValue) {
collectionProperty.add((Serializable) element);
}
component.straightSetProperty(propertyName, createProxyCollection(collectionProperty,
(Class<IEntity>) componentRefType, collectionInterface));
} else {
Collection<Object> collectionProperty = getCollectionFactory().createComponentCollection(
collectionInterface);
for (Object element : (BasicDBList) propertyValue) {
if (element instanceof DBObject) {
collectionProperty.add(convertComponent((DBObject) element,
(Class<? extends IComponent>) componentRefType, readerRegistry));
}
}
component.straightSetProperty(propertyName, collectionProperty);
}
} else {
Collection<Object> collectionProperty = getCollectionFactory().createComponentCollection(
collectionInterface);
for (Object element : (BasicDBList) propertyValue) {
collectionProperty.add(element);
}
component.straightSetProperty(propertyName, collectionProperty);
}
} else {
component.straightSetProperty(propertyName, propertyValue);
}
} else if (propertyDescriptor instanceof IReferencePropertyDescriptor<?>) {
component.straightSetProperty(propertyName, convertComponent((DBObject) propertyValue,
(Class<? extends IComponent>) componentRefType, readerRegistry));
} else {
Object convertedPropertyValue = getConverter().read(propertyType, (DBObject) propertyValue);
component.straightSetProperty(propertyName, convertedPropertyValue);
}
} else if (componentRefType != null && propertyValue instanceof Serializable) {
component.straightSetProperty(propertyName, convertEntity((Serializable) propertyValue,
(Class<IEntity>) componentRefType, readerRegistry));
} else {
Object convertedPropertyValue = getConverter().getConversionService().convert(propertyValue, propertyType);
component.straightSetProperty(propertyName, convertedPropertyValue);
}
}
}
}
}
/**
* Gets entity factory.
*
* @return the entity factory
*/
protected IEntityFactory getEntityFactory() {
return entityFactory;
}
/**
* Sets entity factory.
*
* @param entityFactory
* the entity factory
*/
public void setEntityFactory(IEntityFactory entityFactory) {
this.entityFactory = entityFactory;
}
/**
* Matches boolean.
*
* @param sourceType
* the source type
* @param targetType
* the target type
* @return the boolean
*/
@Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
return DBObject.class.isAssignableFrom(sourceType.getType()) && IEntity.class.isAssignableFrom(
targetType.getType());
}
/**
* Gets convertible types.
*
* @return the convertible types
*/
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(DBObject.class, IEntity.class));
}
/**
* Gets collection factory.
*
* @return the collection factory
*/
protected IComponentCollectionFactory getCollectionFactory() {
return collectionFactory;
}
/**
* Sets collection factory.
*
* @param collectionFactory
* the collection factory
*/
public void setCollectionFactory(IComponentCollectionFactory collectionFactory) {
this.collectionFactory = collectionFactory;
}
/**
* Gets mongo.
*
* @return the mongo
*/
protected MongoTemplate getMongo() {
return mongo;
}
/**
* Sets mongo.
*
* @param mongo
* the mongo
*/
public void setMongo(MongoTemplate mongo) {
this.mongo = mongo;
}
/**
* On application event.
*
* @param event
* the event
*/
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
setMongo(event.getApplicationContext().getBean("mongoTemplate", MongoTemplate.class));
}
private IEntity createProxyEntity(Serializable id, Class<IEntity> entityContract) {
return (IEntity) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class[]{entityContract, JspressoMongoEntityProxy.class}, new JspressoMongoEntityProxyHandler(id,
entityContract, getMongo()));
}
private Object createProxyCollection(Collection<Serializable> ids, Class<IEntity> entityContract,
Class<? extends Collection<?>> collectionContract) {
InvocationHandler handler;
if (List.class.isAssignableFrom(collectionContract)) {
handler = new JspressoMongoEntityListHandler(ids, entityContract, getMongo());
} else {
handler = new JspressoMongoEntitySetHandler(ids, entityContract, getMongo());
}
return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class[]{collectionContract, JspressoMongoProxy.class}, handler);
}
/**
* Gets the getBackendController().
*
* @return the backendController.
*/
protected IBackendController getBackendController() {
return BackendControllerHolder.getCurrentBackendController();
}
/**
* Gets converter.
*
* @return the converter
*/
protected JspressoMappingMongoConverter getConverter() {
return converter;
}
/**
* Sets converter.
*
* @param converter
* the converter
*/
public void setConverter(JspressoMappingMongoConverter converter) {
this.converter = converter;
}
}