/* * 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.assembler.extension.Configurable; import com.inspiresoftware.lib.dto.geda.assembler.extension.MethodSynthesizer; import com.inspiresoftware.lib.dto.geda.assembler.meta.CollectionPipeMetadata; import com.inspiresoftware.lib.dto.geda.assembler.meta.FieldPipeMetadata; import com.inspiresoftware.lib.dto.geda.assembler.meta.MapPipeMetadata; import com.inspiresoftware.lib.dto.geda.assembler.meta.PipeMetadata; import com.inspiresoftware.lib.dto.geda.dsl.Registries; import com.inspiresoftware.lib.dto.geda.dsl.Registry; import com.inspiresoftware.lib.dto.geda.exception.*; import java.beans.PropertyDescriptor; import java.lang.ref.Reference; import java.lang.ref.SoftReference; import java.lang.reflect.Field; import java.lang.reflect.Type; import java.util.*; /** * Assemble DTO and Entities depending on the annotations of Dto. * * @author Denis Pavlov * @since 2.0.2 * */ @SuppressWarnings("unchecked") public final class DTOtoEntityAssemblerImpl implements Assembler, AssemblerContext, Configurable { private static final MetadataChainBuilder ANNOTATIONS = new MetadataChainAnnotationBuilder(); private static final PipeBuilder<FieldPipeMetadata> FIELD = new DataPipeBuilder(); private static final PipeBuilder<FieldPipeMetadata> VIRTUAL = new DataVirtualPipeBuilder(); private static final PipeBuilder<CollectionPipeMetadata> COLLECTION = new CollectionPipeBuilder(); private static final PipeBuilder<MapPipeMetadata> MAP = new MapPipeBuilder(); private static final PipeBuilder<PipeMetadata> CHAIN = new DataPipeChainBuilder(); private final Class dtoClass; private final Class entityClass; private final MethodSynthesizer synthesizer; private final Registry dslRegistry; private final Reference<ClassLoader> classLoader; private Pipe[] pipes; DTOtoEntityAssemblerImpl(final Class dto, final Class entity, final ClassLoader classLoader, final MethodSynthesizer synthesizer, final Registry registry) throws InspectionScanningException, UnableToCreateInstanceException, InspectionPropertyNotFoundException, InspectionBindingNotFoundException, AnnotationMissingBindingException, AnnotationValidatingBindingException, GeDARuntimeException, AnnotationDuplicateBindingException { this(dto, entity, classLoader, synthesizer, registry, true); } DTOtoEntityAssemblerImpl(final Class dto, final Class entity, final ClassLoader classLoader, final MethodSynthesizer synthesizer, final Registry registry, final boolean strict) throws InspectionScanningException, UnableToCreateInstanceException, InspectionPropertyNotFoundException, InspectionBindingNotFoundException, AnnotationMissingBindingException, AnnotationValidatingBindingException, GeDARuntimeException, AnnotationDuplicateBindingException { dtoClass = dto; entityClass = entity; this.synthesizer = synthesizer; this.classLoader = new SoftReference<ClassLoader>(classLoader); dslRegistry = registry; final MetadataChainBuilder metaBuilder; if (registry == null) { metaBuilder = ANNOTATIONS; } else { metaBuilder = new MetadataChainDSLBuilder(registry, dtoClass, entityClass); } Class dtoMap = dto; final LinkedList pipes = new LinkedList(); while (dtoMap != null) { // when we reach Object.class this should be null mapRelationMapping(dtoMap, entity, strict, pipes, metaBuilder); final Type type = dtoMap.getGenericSuperclass(); if (type != null) { dtoMap = PropertyInspector.getClassForType(type); } else { dtoMap = null; } } this.pipes = (Pipe[]) pipes.toArray(new Pipe[pipes.size()]); } private void mapRelationMapping(final Class dto, final Class entity, final boolean strict, final List<Pipe> pipes, final MetadataChainBuilder metaBuilder) throws InspectionScanningException, UnableToCreateInstanceException, InspectionPropertyNotFoundException, InspectionBindingNotFoundException, AnnotationMissingBindingException, AnnotationValidatingBindingException, GeDARuntimeException, AnnotationDuplicateBindingException { final boolean isMapOrListEntity = Map.class.isAssignableFrom(entity) || List.class.isAssignableFrom(entity); final PropertyDescriptor[] dtoPropertyDescriptors = PropertyInspector.getPropertyDescriptorsForClass(dto); final PropertyDescriptor[] entityPropertyDescriptors; if (isMapOrListEntity) { entityPropertyDescriptors = null; } else { entityPropertyDescriptors = PropertyInspector.getPropertyDescriptorsForClass(entity); } final Set<String> bindings = new TreeSet<String>(); final Field[] dtoFields = dto.getDeclaredFields(); for (Field dtoField : dtoFields) { final List<PipeMetadata> metas = metaBuilder.build(dtoField); if (metas == null || metas.isEmpty()) { continue; } try { final Pipe pipe = createPipeChain(dtoClass, dtoPropertyDescriptors, entityClass, entityPropertyDescriptors, dtoField, metas, 0, isMapOrListEntity); final String binding = pipe.getBinding(); if (bindings.contains(binding)) { throw new AnnotationDuplicateBindingException(dtoClass.getCanonicalName(), binding); } bindings.add(binding); pipes.add(pipe); } catch (InspectionBindingNotFoundException noField) { if (strict) { // only throw exception if we are in strict mode throw noField; } } } } private Pipe createPipeChain( final Class dto, final PropertyDescriptor[] dtoPropertyDescriptors, final Class entity, final PropertyDescriptor[] entityPropertyDescriptors, final Field dtoField, final List<PipeMetadata> metas, final int index, final boolean isMapOrListEntity) throws InspectionPropertyNotFoundException, InspectionBindingNotFoundException, InspectionScanningException, UnableToCreateInstanceException, AnnotationMissingBindingException, AnnotationValidatingBindingException, GeDARuntimeException { final PipeMetadata meta = metas.get(index); if (index + 1 == metas.size() || isMapOrListEntity) { // build actual pipe for last in chain, maps or lists // (since maps and lists do not have any nested properties, maybe another feature?) if (meta instanceof FieldPipeMetadata) { if (meta.getEntityFieldName().startsWith("#this#")) { // create virtual field pipe return VIRTUAL.build(this, dto, entity, dtoPropertyDescriptors, entityPropertyDescriptors, (FieldPipeMetadata) meta, null); } else { // create field pipe return FIELD.build(this, dto, entity, dtoPropertyDescriptors, entityPropertyDescriptors, (FieldPipeMetadata) meta, null); } } else if (meta instanceof CollectionPipeMetadata) { // create collection return COLLECTION.build(this, dto, entity, dtoPropertyDescriptors, entityPropertyDescriptors, (CollectionPipeMetadata) meta, null); } else if (meta instanceof MapPipeMetadata) { // create map return MAP.build(this, dto, entity, dtoPropertyDescriptors, entityPropertyDescriptors, (MapPipeMetadata) meta, null); } else { throw new GeDARuntimeException("Unknown pipe meta: " + meta.getClass()); } } final PropertyDescriptor nested = PropertyInspector.getEntityPropertyDescriptorForField( dto, entity, meta.getDtoFieldName(), meta.getEntityFieldName(), entityPropertyDescriptors); final PropertyDescriptor[] nestedEntityPropertyDescriptors = PropertyInspector.getPropertyDescriptorsForClassReturnedByGet(nested); // build a chain pipe return CHAIN.build(this, dto, entity, dtoPropertyDescriptors, entityPropertyDescriptors, meta, createPipeChain(dto, dtoPropertyDescriptors, entity, nestedEntityPropertyDescriptors, dtoField, metas, index + 1, isMapOrListEntity) ); } /** * Configure synthesizer. * * @param configuration configuration name * @param value value to set * @return true if configuration was set, false if not set or invalid * @throws com.inspiresoftware.lib.dto.geda.exception.GeDAException in case there are errors */ public boolean configure(final String configuration, final Object value) throws GeDAException { return this.synthesizer.configure(configuration, value); } private BeanFactory resolveBeanFactory(BeanFactory beanFactory) { if (beanFactory == null) { if (dslRegistry != null) { return Registries.beanFactory(dslRegistry); } } return beanFactory; } /** {@inheritDoc} */ public void assembleDto(final Object dto, final Object entity, final Map<String, Object> converters, final BeanFactory dtoBeanFactory) throws InspectionInvalidDtoInstanceException, InspectionInvalidEntityInstanceException, BeanFactoryNotFoundException, BeanFactoryUnableToCreateInstanceException, AnnotationMissingException, NotValueConverterException, ValueConverterNotFoundException, UnableToCreateInstanceException, CollectionEntityGenericReturnTypeException, InspectionScanningException, InspectionPropertyNotFoundException, InspectionBindingNotFoundException, AnnotationMissingBindingException, AnnotationValidatingBindingException, GeDARuntimeException, AnnotationDuplicateBindingException { validateDtoAndEntity(dto, entity); for (Pipe pipe : pipes) { pipe.writeFromEntityToDto(entity, dto, converters, resolveBeanFactory(dtoBeanFactory)); } } /** {@inheritDoc} */ public void assembleDtos(final Collection dtos, final Collection entities, final Map<String, Object> converters, final BeanFactory dtoBeanFactory) throws InvalidDtoCollectionException, UnableToCreateInstanceException, InspectionInvalidDtoInstanceException, InspectionInvalidEntityInstanceException, BeanFactoryNotFoundException, BeanFactoryUnableToCreateInstanceException, AnnotationMissingException, NotValueConverterException, ValueConverterNotFoundException, CollectionEntityGenericReturnTypeException, InspectionScanningException, InspectionPropertyNotFoundException, InspectionBindingNotFoundException, AnnotationMissingBindingException, AnnotationValidatingBindingException, GeDARuntimeException, AnnotationDuplicateBindingException { if (dtos instanceof Collection && dtos.isEmpty() && entities instanceof Collection) { final BeanFactory beanFactory = resolveBeanFactory(dtoBeanFactory); for (Object entity : entities) { try { final Object dto = this.dtoClass.newInstance(); assembleDto(dto, entity, converters, beanFactory); dtos.add(dto); } catch (InstantiationException exp) { throw new UnableToCreateInstanceException(this.dtoClass.getCanonicalName(), "Unable to create dto instance for: " + this.dtoClass.getName(), exp); } catch (IllegalAccessException exp) { throw new UnableToCreateInstanceException(this.dtoClass.getCanonicalName(), "Unable to create dto instance for: " + this.dtoClass.getName(), exp); } } } else { throw new InvalidDtoCollectionException(); } } /** {@inheritDoc} */ public void assembleEntity(final Object dto, final Object entity, final Map<String, Object> converters, final BeanFactory entityBeanFactory) throws InspectionInvalidDtoInstanceException, InspectionInvalidEntityInstanceException, BeanFactoryNotFoundException, BeanFactoryUnableToCreateInstanceException, NotEntityRetrieverException, EntityRetrieverNotFoundException, NotValueConverterException, ValueConverterNotFoundException, AnnotationMissingBeanKeyException, AnnotationMissingException, UnableToCreateInstanceException, CollectionEntityGenericReturnTypeException, InspectionScanningException, InspectionPropertyNotFoundException, InspectionBindingNotFoundException, AnnotationMissingBindingException, AnnotationValidatingBindingException, GeDARuntimeException, AnnotationDuplicateBindingException, DtoToEntityMatcherNotFoundException, NotDtoToEntityMatcherException { validateDtoAndEntity(dto, entity); for (Pipe pipe : pipes) { pipe.writeFromDtoToEntity(entity, dto, converters, resolveBeanFactory(entityBeanFactory)); } } /** {@inheritDoc} */ public void assembleEntities(final Collection dtos, final Collection entities, final Map<String, Object> converters, final BeanFactory entityBeanFactory) throws UnableToCreateInstanceException, InvalidEntityCollectionException, InspectionInvalidDtoInstanceException, InspectionInvalidEntityInstanceException, BeanFactoryNotFoundException, BeanFactoryUnableToCreateInstanceException, NotEntityRetrieverException, EntityRetrieverNotFoundException, NotValueConverterException, ValueConverterNotFoundException, AnnotationMissingBeanKeyException, AnnotationMissingException, CollectionEntityGenericReturnTypeException, InspectionScanningException, InspectionPropertyNotFoundException, InspectionBindingNotFoundException, AnnotationMissingBindingException, AnnotationValidatingBindingException, GeDARuntimeException, AnnotationDuplicateBindingException, DtoToEntityMatcherNotFoundException, NotDtoToEntityMatcherException { if (dtos instanceof Collection && entities instanceof Collection && entities.isEmpty()) { final BeanFactory beanFactory = resolveBeanFactory(entityBeanFactory); for (Object dto : dtos) { try { final Object entity = this.entityClass.newInstance(); assembleEntity(dto, entity, converters, beanFactory); entities.add(entity); } catch (InstantiationException exp) { throw new UnableToCreateInstanceException(this.dtoClass.getCanonicalName(), "Unable to create entity instance for: " + this.dtoClass.getName(), exp); } catch (IllegalAccessException exp) { throw new UnableToCreateInstanceException(this.dtoClass.getCanonicalName(), "Unable to create entity instance for: " + this.dtoClass.getName(), exp); } } } else { throw new InvalidEntityCollectionException(); } } private void validateDtoAndEntity(final Object dto, final Object entity) throws InspectionInvalidDtoInstanceException, InspectionInvalidEntityInstanceException { if (!this.dtoClass.isInstance(dto)) { throw new InspectionInvalidDtoInstanceException(this.dtoClass.getCanonicalName(), dto); } if (!this.entityClass.isInstance(entity)) { throw new InspectionInvalidEntityInstanceException(this.entityClass.getCanonicalName(), entity); } } /** {@inheritDoc} */ public Assembler newAssembler(final Class<?> dto, final Class<?> entity) { if (dslRegistry == null) { return DTOAssembler.newCustomAssembler(dto, entity, getClassLoader(), synthesizer); } return DTOAssembler.newCustomAssembler(dto, entity, getClassLoader(), dslRegistry, synthesizer); } /** {@inheritDoc} */ public MethodSynthesizer getMethodSynthesizer() { return synthesizer; } /** {@inheritDoc} */ public Registry getDslRegistry() { return dslRegistry; } /** {@inheritDoc} */ public ClassLoader getClassLoader() throws GeDARuntimeException { ClassLoader cl = classLoader.get(); if (cl == null) { // Cl was garbage collected - something gone really wrong throw new GeDARuntimeException("Class loader has been gc'ed"); } return cl; } /** {@inheritDoc} */ public void releaseResources() { synthesizer.releaseResources(); if (dslRegistry != null) { dslRegistry.releaseResources(); } } }