/*
* This code is distributed under The GNU Lesser General Public License (LGPLv3)
* Please visit GNU site for LGPLv3 http://www.gnu.org/copyleft/lesser.html
*
* Copyright Denis Pavlov 2009
* Web: http://www.genericdtoassembler.org
* SVN: https://svn.code.sf.net/p/geda-genericdto/code/trunk/
* SVN (mirror): http://geda-genericdto.googlecode.com/svn/trunk/
*/
package com.inspiresoftware.lib.dto.geda.assembler;
import com.inspiresoftware.lib.dto.geda.adapter.BeanFactory;
import com.inspiresoftware.lib.dto.geda.adapter.EntityRetriever;
import com.inspiresoftware.lib.dto.geda.adapter.ValueConverter;
import com.inspiresoftware.lib.dto.geda.assembler.extension.DataReader;
import com.inspiresoftware.lib.dto.geda.assembler.extension.DataWriter;
import com.inspiresoftware.lib.dto.geda.assembler.meta.FieldPipeMetadata;
import com.inspiresoftware.lib.dto.geda.exception.*;
import com.inspiresoftware.lib.dto.geda.exception.AnnotationMissingBindingException.MissingBindingType;
import java.util.Map;
/**
* Object that handles read and write streams between Dto and Entity objects.
*
* @author Denis Pavlov
* @since 1.0.0
*
*/
class DataPipe implements Pipe {
private final FieldPipeMetadata meta;
private final DataReader dtoParentKeyRead;
private final AssemblerContext context;
private final DataReader dtoRead;
private final DataWriter dtoWrite;
private final DataReader entityRead;
private final DataWriter entityWrite;
private final boolean readOnly;
private final boolean usesConverter;
private final boolean hasSubEntity;
private static final Object NULL = null;
/**
* @param context assembler context
* @param dtoRead method for reading data from DTO field
* @param dtoWrite method for writing data to DTO field
* @param dtoParentKeyRead method for reading Parent key data from DTO field
* @param entityRead method for reading data from Entity field
* @param entityWrite method for writing data to Entity field
* @param meta meta data for this pipe.
*
* @throws AnnotationMissingBindingException if some of the parameter missing from the annotation
* @throws AnnotationValidatingBindingException if binding pipes are invalid
*/
public DataPipe(final AssemblerContext context,
final DataReader dtoRead,
final DataWriter dtoWrite,
final DataReader dtoParentKeyRead,
final DataReader entityRead,
final DataWriter entityWrite,
final FieldPipeMetadata meta) throws AnnotationMissingBindingException, AnnotationValidatingBindingException {
this.meta = meta;
this.usesConverter = meta.getConverterKey() != null && meta.getConverterKey().length() > 0;
this.hasSubEntity = meta.getDtoBeanKey() != null && meta.getDtoBeanKey().length() > 0;
this.context = context;
this.dtoWrite = dtoWrite;
this.entityRead = entityRead;
this.readOnly = meta.isReadOnly();
if (this.readOnly) {
PipeValidator.validateReadPipeNonNull(this.dtoWrite, this.meta.getDtoFieldName(),
this.entityRead, this.meta.getEntityFieldName());
this.dtoRead = null;
this.entityWrite = null;
if (!usesConverter) {
PipeValidator.validateReadPipeTypes(context.getDslRegistry(), this.dtoWrite, this.meta.getDtoFieldName(),
this.entityRead, this.meta.getEntityFieldName());
}
} else {
this.dtoRead = dtoRead;
this.entityWrite = entityWrite;
PipeValidator.validatePipeNonNull(this.dtoRead, this.dtoWrite, this.meta.getDtoFieldName(),
this.entityRead, this.entityWrite, this.meta.getEntityFieldName());
if (!usesConverter) {
PipeValidator.validatePipeTypes(context.getDslRegistry(), this.dtoRead, this.dtoWrite, this.meta.getDtoFieldName(),
this.entityRead, this.entityWrite, this.meta.getEntityFieldName());
}
}
if (this.meta.isChild()) {
this.dtoParentKeyRead = dtoParentKeyRead;
PipeValidator.validatePipeNonNull(this.dtoParentKeyRead, MissingBindingType.PARENT_READ, this.meta.getDtoFieldName());
} else {
this.dtoParentKeyRead = null;
}
}
/** {@inheritDoc} */
public String getBinding() {
return meta.getEntityFieldName();
}
/** {@inheritDoc} */
public void writeFromEntityToDto(final Object entity,
final Object dto,
final Map<String, Object> converters,
final BeanFactory dtoBeanFactory)
throws BeanFactoryNotFoundException, BeanFactoryUnableToCreateInstanceException,
AnnotationMissingException, NotValueConverterException, ValueConverterNotFoundException,
InspectionInvalidDtoInstanceException, InspectionInvalidEntityInstanceException, InspectionScanningException,
UnableToCreateInstanceException, InspectionPropertyNotFoundException, InspectionBindingNotFoundException,
AnnotationMissingBindingException, AnnotationValidatingBindingException, GeDARuntimeException,
AnnotationDuplicateBindingException, CollectionEntityGenericReturnTypeException {
if (entity == null) {
return;
}
final Object entityData = this.entityRead.read(entity);
if (entityData != null) {
if (hasSubEntity) {
createDtoAndWriteFromEntityToDto(dto, converters, dtoBeanFactory, entityData);
} else {
if (usesConverter) {
this.dtoWrite.write(dto, getConverter(converters).convertToDto(entityData, dtoBeanFactory));
} else {
this.dtoWrite.write(dto, entityData);
}
}
} else {
this.dtoWrite.write(dto, NULL);
}
}
private void createDtoAndWriteFromEntityToDto(final Object dto,
final Map<String, Object> converters,
final BeanFactory dtoBeanFactory,
final Object entityData)
throws BeanFactoryNotFoundException, BeanFactoryUnableToCreateInstanceException, AnnotationMissingException,
InspectionInvalidDtoInstanceException, InspectionInvalidEntityInstanceException, InspectionScanningException,
UnableToCreateInstanceException, InspectionPropertyNotFoundException, InspectionBindingNotFoundException,
AnnotationMissingBindingException, AnnotationValidatingBindingException, GeDARuntimeException,
AnnotationDuplicateBindingException, NotValueConverterException, ValueConverterNotFoundException,
CollectionEntityGenericReturnTypeException {
if (dtoBeanFactory == null) {
throw new BeanFactoryNotFoundException(meta.getDtoFieldName(), meta.getDtoBeanKey(), true);
}
final Object newDtoObject = this.meta.newDtoBean(dtoBeanFactory);
final Assembler assembler = context.newAssembler(newDtoObject.getClass(), entityData.getClass());
assembler.assembleDto(newDtoObject, entityData, converters, dtoBeanFactory);
this.dtoWrite.write(dto, newDtoObject);
}
/** {@inheritDoc} */
public void writeFromDtoToEntity(final Object entity,
final Object dto,
final Map<String, Object> converters,
final BeanFactory entityBeanFactory)
throws BeanFactoryNotFoundException, BeanFactoryUnableToCreateInstanceException,
NotEntityRetrieverException, EntityRetrieverNotFoundException, NotValueConverterException,
ValueConverterNotFoundException, AnnotationMissingBeanKeyException, AnnotationMissingException,
InspectionInvalidDtoInstanceException, InspectionInvalidEntityInstanceException, InspectionScanningException,
UnableToCreateInstanceException, InspectionPropertyNotFoundException, InspectionBindingNotFoundException,
AnnotationMissingBindingException, AnnotationValidatingBindingException, GeDARuntimeException,
AnnotationDuplicateBindingException, CollectionEntityGenericReturnTypeException, DtoToEntityMatcherNotFoundException,
NotDtoToEntityMatcherException {
if (this.readOnly) {
return;
}
final Object dtoData = this.dtoRead.read(dto);
if (this.meta.isChild()) {
writeParentObject(dtoData, entity, converters, entityBeanFactory);
return;
}
final Object dtoValue = getDtoValue(dtoData, entity, converters, entityBeanFactory);
if (dtoValue != null) {
if (hasSubEntity) {
assembleSubEntity(dtoValue, entity, converters, entityBeanFactory);
} else {
this.entityWrite.write(entity, dtoValue);
}
} else if (entity != null) {
// if the dtoValue is null the setting only makes sense if the entity bean existed.
this.entityWrite.write(entity, NULL);
}
}
private Object getDtoValue(final Object dtoData, final Object entity, final Map<String, Object> converters,
final BeanFactory entityBeanFactory) throws NotValueConverterException, ValueConverterNotFoundException {
if (usesConverter) {
return getConverter(converters).convertToEntity(dtoData, entity, entityBeanFactory);
}
return dtoData;
}
private void assembleSubEntity(final Object dtoValue, final Object parentEntity,
final Map<String, Object> converters, final BeanFactory entityBeanFactory)
throws BeanFactoryNotFoundException, AnnotationMissingBeanKeyException, BeanFactoryUnableToCreateInstanceException,
AnnotationMissingException, InspectionInvalidDtoInstanceException, InspectionInvalidEntityInstanceException,
InspectionScanningException, UnableToCreateInstanceException, InspectionPropertyNotFoundException,
InspectionBindingNotFoundException, AnnotationMissingBindingException, AnnotationValidatingBindingException,
GeDARuntimeException, AnnotationDuplicateBindingException, NotEntityRetrieverException,
EntityRetrieverNotFoundException, NotValueConverterException, ValueConverterNotFoundException,
CollectionEntityGenericReturnTypeException, DtoToEntityMatcherNotFoundException, NotDtoToEntityMatcherException {
Object dataEntity = this.entityRead.read(parentEntity);
if (dataEntity == null) {
if (entityBeanFactory == null) {
throw new BeanFactoryNotFoundException(
this.meta.getDtoFieldName() + ":" + dtoValue.getClass(), this.meta.getEntityBeanKey(), false);
} else if (this.meta.getEntityBeanKey() == null) {
throw new AnnotationMissingBeanKeyException(this.meta.getDtoFieldName() + ":" + dtoValue.getClass(), false);
}
dataEntity = this.meta.newEntityBean(entityBeanFactory);
this.entityWrite.write(parentEntity, dataEntity);
}
final Assembler assembler = context.newAssembler(dtoValue.getClass(), dataEntity.getClass());
assembler.assembleEntity(dtoValue, dataEntity, converters, entityBeanFactory);
}
@SuppressWarnings("unchecked")
private void writeParentObject(final Object dtoData, final Object entity,
final Map<String, Object> converters, final BeanFactory entityBeanFactory)
throws BeanFactoryNotFoundException, BeanFactoryUnableToCreateInstanceException,
NotEntityRetrieverException, EntityRetrieverNotFoundException {
if (dtoData == null) {
if (entity != null) {
// if the dtoValue is null the setting only makes sense if the entity bean existed.
this.entityWrite.write(entity, NULL);
}
} else {
final Object primaryKey = this.dtoParentKeyRead.read(dtoData);
final Class returnType = this.entityRead.getReturnType();
if (entityBeanFactory == null || this.meta.getEntityBeanKey() == null) {
throw new BeanFactoryNotFoundException(
dtoData.getClass() + ":" + this.meta.getParentEntityPrimaryKeyField(), this.meta.getEntityBeanKey(), false);
}
final Class beanClass = this.meta.newEntityBean(entityBeanFactory).getClass(); // overhead but need to be stateless!!!
final Object entityForPk = getRetriever(converters).retrieveByPrimaryKey(returnType, beanClass, primaryKey);
// if we did not find anything, setting null. Maybe need to throw exception here or maybe it is retriever's job?
this.entityWrite.write(entity, entityForPk);
}
}
private ValueConverter getConverter(final Map<String, Object> converters)
throws NotValueConverterException, ValueConverterNotFoundException {
if (converters != null && !converters.isEmpty() && converters.containsKey(this.meta.getConverterKey())) {
final Object conv = converters.get(this.meta.getConverterKey());
if (conv instanceof ValueConverter) {
return (ValueConverter) conv;
}
throw new NotValueConverterException(
this.meta.getDtoFieldName(), this.meta.getEntityFieldName(), this.meta.getConverterKey());
}
throw new ValueConverterNotFoundException(
this.meta.getDtoFieldName(), this.meta.getEntityFieldName(), this.meta.getConverterKey());
}
private EntityRetriever getRetriever(final Map<String, Object> converters)
throws NotEntityRetrieverException, EntityRetrieverNotFoundException {
if (converters != null && !converters.isEmpty() && converters.containsKey(this.meta.getEntityRetrieverKey())) {
final Object conv = converters.get(this.meta.getEntityRetrieverKey());
if (conv instanceof EntityRetriever) {
return (EntityRetriever) conv;
}
throw new NotEntityRetrieverException(
this.meta.getEntityFieldName(), this.meta.getDtoFieldName(), this.meta.getConverterKey());
}
throw new EntityRetrieverNotFoundException(
this.meta.getEntityFieldName(), this.meta.getDtoFieldName(), this.meta.getEntityRetrieverKey());
}
}