org.broadleafcommerce.openadmin.server.service.persistence.module.MapStructurePersistenceModule.java Source code

Java tutorial

Introduction

Here is the source code for org.broadleafcommerce.openadmin.server.service.persistence.module.MapStructurePersistenceModule.java

Source

/*
 * #%L
 * BroadleafCommerce Open Admin Platform
 * %%
 * Copyright (C) 2009 - 2013 Broadleaf Commerce
 * %%
 * 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.
 * #L%
 */
package org.broadleafcommerce.openadmin.server.service.persistence.module;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.broadleafcommerce.common.exception.SecurityServiceException;
import org.broadleafcommerce.common.exception.ServiceException;
import org.broadleafcommerce.common.money.Money;
import org.broadleafcommerce.common.presentation.client.OperationType;
import org.broadleafcommerce.common.presentation.client.PersistencePerspectiveItemType;
import org.broadleafcommerce.common.sandbox.SandBoxHelper;
import org.broadleafcommerce.common.value.ValueAssignable;
import org.broadleafcommerce.common.web.BroadleafRequestContext;
import org.broadleafcommerce.openadmin.dto.BasicFieldMetadata;
import org.broadleafcommerce.openadmin.dto.CriteriaTransferObject;
import org.broadleafcommerce.openadmin.dto.DynamicResultSet;
import org.broadleafcommerce.openadmin.dto.Entity;
import org.broadleafcommerce.openadmin.dto.FieldMetadata;
import org.broadleafcommerce.openadmin.dto.ForeignKey;
import org.broadleafcommerce.openadmin.dto.MapStructure;
import org.broadleafcommerce.openadmin.dto.MergedPropertyType;
import org.broadleafcommerce.openadmin.dto.PersistencePackage;
import org.broadleafcommerce.openadmin.dto.PersistencePerspective;
import org.broadleafcommerce.openadmin.dto.Property;
import org.broadleafcommerce.openadmin.dto.SimpleValueMapStructure;
import org.broadleafcommerce.openadmin.server.service.persistence.module.criteria.FilterMapping;
import org.broadleafcommerce.openadmin.server.service.persistence.validation.RequiredPropertyValidator;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.type.StringType;
import org.hibernate.type.Type;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;

/**
 * 
 * @author jfischer
 *
 */
@Component("blMapStructurePersistenceModule")
@Scope("prototype")
public class MapStructurePersistenceModule extends BasicPersistenceModule {

    @Resource(name = "blSandBoxHelper")
    protected SandBoxHelper sandBoxHelper;

    private static final Log LOG = LogFactory.getLog(MapStructurePersistenceModule.class);

    @Override
    public boolean isCompatible(OperationType operationType) {
        return OperationType.MAP.equals(operationType);
    }

    @Override
    public void extractProperties(Class<?>[] inheritanceLine,
            Map<MergedPropertyType, Map<String, FieldMetadata>> mergedProperties, List<Property> properties)
            throws NumberFormatException {
        if (mergedProperties.get(MergedPropertyType.MAPSTRUCTUREKEY) != null) {
            extractPropertiesFromMetadata(inheritanceLine, mergedProperties.get(MergedPropertyType.MAPSTRUCTUREKEY),
                    properties, false, MergedPropertyType.MAPSTRUCTUREKEY);
        }
        if (mergedProperties.get(MergedPropertyType.MAPSTRUCTUREVALUE) != null) {
            extractPropertiesFromMetadata(inheritanceLine,
                    mergedProperties.get(MergedPropertyType.MAPSTRUCTUREVALUE), properties, false,
                    MergedPropertyType.MAPSTRUCTUREVALUE);
        }
    }

    @Override
    public void updateMergedProperties(PersistencePackage persistencePackage,
            Map<MergedPropertyType, Map<String, FieldMetadata>> allMergedProperties) throws ServiceException {
        String ceilingEntityFullyQualifiedClassname = persistencePackage.getCeilingEntityFullyQualifiedClassname();
        try {
            PersistencePerspective persistencePerspective = persistencePackage.getPersistencePerspective();
            MapStructure mapStructure = (MapStructure) persistencePerspective.getPersistencePerspectiveItems()
                    .get(PersistencePerspectiveItemType.MAPSTRUCTURE);
            if (mapStructure != null) {
                PersistentClass persistentClass = persistenceManager.getDynamicEntityDao()
                        .getPersistentClass(mapStructure.getKeyClassName());
                Map<String, FieldMetadata> keyMergedProperties;
                if (persistentClass == null) {
                    keyMergedProperties = persistenceManager.getDynamicEntityDao().getPropertiesForPrimitiveClass(
                            mapStructure.getKeyPropertyName(), mapStructure.getKeyPropertyFriendlyName(),
                            Class.forName(mapStructure.getKeyClassName()),
                            Class.forName(ceilingEntityFullyQualifiedClassname),
                            MergedPropertyType.MAPSTRUCTUREKEY);
                } else {
                    keyMergedProperties = persistenceManager.getDynamicEntityDao().getMergedProperties(
                            mapStructure.getKeyClassName(),
                            new Class[] { Class.forName(mapStructure.getKeyClassName()) }, null, new String[] {},
                            new ForeignKey[] {}, MergedPropertyType.MAPSTRUCTUREKEY,
                            persistencePerspective.getPopulateToOneFields(),
                            persistencePerspective.getIncludeFields(), persistencePerspective.getExcludeFields(),
                            persistencePerspective.getConfigurationKey(), "");
                }
                allMergedProperties.put(MergedPropertyType.MAPSTRUCTUREKEY, keyMergedProperties);

                persistentClass = persistenceManager.getDynamicEntityDao()
                        .getPersistentClass(mapStructure.getValueClassName());
                Map<String, FieldMetadata> valueMergedProperties;
                if (persistentClass == null) {
                    if (!SimpleValueMapStructure.class.isAssignableFrom(mapStructure.getClass())) {
                        throw new IllegalStateException(
                                "The map structure was determined to not be a simple value, but the system was unable to identify the entity designated for the map structure value("
                                        + mapStructure.getValueClassName() + ")");
                    }
                    valueMergedProperties = persistenceManager.getDynamicEntityDao().getPropertiesForPrimitiveClass(
                            ((SimpleValueMapStructure) mapStructure).getValuePropertyName(),
                            ((SimpleValueMapStructure) mapStructure).getValuePropertyFriendlyName(),
                            Class.forName(mapStructure.getValueClassName()),
                            Class.forName(ceilingEntityFullyQualifiedClassname),
                            MergedPropertyType.MAPSTRUCTUREVALUE);
                } else {
                    valueMergedProperties = persistenceManager.getDynamicEntityDao().getMergedProperties(
                            mapStructure.getValueClassName(),
                            new Class[] { Class.forName(mapStructure.getValueClassName()) }, null, new String[] {},
                            new ForeignKey[] {}, MergedPropertyType.MAPSTRUCTUREVALUE,
                            persistencePerspective.getPopulateToOneFields(),
                            persistencePerspective.getIncludeFields(), persistencePerspective.getExcludeFields(),
                            persistencePerspective.getConfigurationKey(), "");
                }
                allMergedProperties.put(MergedPropertyType.MAPSTRUCTUREVALUE, valueMergedProperties);
            }
        } catch (Exception e) {
            throw new ServiceException("Unable to fetch results for " + ceilingEntityFullyQualifiedClassname, e);
        }
    }

    @Override
    public Entity add(PersistencePackage persistencePackage) throws ServiceException {
        String[] customCriteria = persistencePackage.getCustomCriteria();
        if (customCriteria != null && customCriteria.length > 0) {
            LOG.warn(
                    "custom persistence handlers and custom criteria not supported for add types other than BASIC");
        }
        PersistencePerspective persistencePerspective = persistencePackage.getPersistencePerspective();
        Entity entity = persistencePackage.getEntity();
        MapStructure mapStructure = (MapStructure) persistencePerspective.getPersistencePerspectiveItems()
                .get(PersistencePerspectiveItemType.MAPSTRUCTURE);
        if (!mapStructure.getMutable()) {
            throw new SecurityServiceException("Field not mutable");
        }
        try {
            Map<String, FieldMetadata> ceilingMergedProperties = getSimpleMergedProperties(entity.getType()[0],
                    persistencePerspective);
            String mapKey = entity.findProperty(mapStructure.getKeyPropertyName()).getValue();
            if (StringUtils.isEmpty(mapKey)) {
                entity.addValidationError(mapStructure.getKeyPropertyName(),
                        RequiredPropertyValidator.ERROR_MESSAGE);
                LOG.debug("No key property passed in for map, failing validation");
            }

            if (ceilingMergedProperties
                    .containsKey(mapStructure.getMapProperty() + FieldManager.MAPFIELDSEPARATOR + mapKey)) {
                throw new ServiceException("\"" + mapKey + "\" is a reserved property name.");
            }

            Serializable instance = persistenceManager.getDynamicEntityDao().retrieve(
                    Class.forName(entity.getType()[0]), Long.valueOf(entity.findProperty("symbolicId").getValue()));

            Assert.isTrue(instance != null, "Entity not found");

            FieldManager fieldManager = getFieldManager();
            Map map = (Map) fieldManager.getFieldValue(instance, mapStructure.getMapProperty());

            if (map.containsKey(mapKey)) {
                entity.addValidationError(mapStructure.getKeyPropertyName(), "keyExistsValidationError");
            }

            if (StringUtils.isNotBlank(mapStructure.getMapKeyValueProperty())) {
                Property p = entity.findProperty("key");
                Property newP = new Property();
                newP.setName(mapStructure.getMapKeyValueProperty());
                newP.setValue(p.getValue());
                newP.setIsDirty(p.getIsDirty());
                entity.addProperty(newP);
            }

            PersistentClass persistentClass = persistenceManager.getDynamicEntityDao()
                    .getPersistentClass(mapStructure.getValueClassName());
            Map<String, FieldMetadata> valueUnfilteredMergedProperties;
            if (persistentClass == null) {
                valueUnfilteredMergedProperties = persistenceManager.getDynamicEntityDao()
                        .getPropertiesForPrimitiveClass(
                                ((SimpleValueMapStructure) mapStructure).getValuePropertyName(),
                                ((SimpleValueMapStructure) mapStructure).getValuePropertyFriendlyName(),
                                Class.forName(mapStructure.getValueClassName()), Class.forName(entity.getType()[0]),
                                MergedPropertyType.MAPSTRUCTUREVALUE);
            } else {
                valueUnfilteredMergedProperties = persistenceManager.getDynamicEntityDao().getMergedProperties(
                        mapStructure.getValueClassName(),
                        new Class[] { Class.forName(mapStructure.getValueClassName()) }, null, new String[] {},
                        new ForeignKey[] {}, MergedPropertyType.MAPSTRUCTUREVALUE,
                        persistencePerspective.getPopulateToOneFields(), persistencePerspective.getIncludeFields(),
                        persistencePerspective.getExcludeFields(), persistencePerspective.getConfigurationKey(),
                        "");
            }
            Map<String, FieldMetadata> valueMergedProperties = filterOutCollectionMetadata(
                    valueUnfilteredMergedProperties);

            if (persistentClass != null) {
                Serializable valueInstance = (Serializable) Class.forName(mapStructure.getValueClassName())
                        .newInstance();
                valueInstance = createPopulatedInstance(valueInstance, entity, valueMergedProperties, false);
                if (valueInstance instanceof ValueAssignable) {
                    //This is likely a OneToMany map (see productAttributes) whose map key is actually the name field from
                    //the mapped entity.
                    ((ValueAssignable) valueInstance)
                            .setName(entity.findProperty(mapStructure.getKeyPropertyName()).getValue());
                }
                if (mapStructure.getManyToField() != null) {
                    //Need to fulfill a bi-directional association back to the parent entity
                    fieldManager.setFieldValue(valueInstance, mapStructure.getManyToField(), instance);
                }
                valueInstance = persistenceManager.getDynamicEntityDao().persist(valueInstance);
                /*
                 * TODO this map manipulation code currently assumes the key value is a String. This should be widened to accept
                 * additional types of primitive objects.
                 */
                map.put(mapKey, valueInstance);
            } else {
                String propertyName = ((SimpleValueMapStructure) mapStructure).getValuePropertyName();
                String value = entity.findProperty(propertyName).getValue();
                Object convertedPrimitive = convertPrimitiveBasedOnType(propertyName, value, valueMergedProperties);
                map.put(mapKey, convertedPrimitive);
            }

            Entity[] responses = getMapRecords(instance, mapStructure, ceilingMergedProperties,
                    valueMergedProperties, entity.findProperty("symbolicId"));
            for (Entity response : responses) {
                if (response.findProperty(mapStructure.getKeyPropertyName()).getValue().equals(persistencePackage
                        .getEntity().findProperty(mapStructure.getKeyPropertyName()).getValue())) {
                    return response;
                }
            }
            return responses[0];
        } catch (Exception e) {
            throw new ServiceException("Problem updating entity : " + e.getMessage(), e);
        }
    }

    protected Object convertPrimitiveBasedOnType(String valuePropertyName, String value,
            Map<String, FieldMetadata> valueMergedProperties) throws ParseException {
        switch (((BasicFieldMetadata) valueMergedProperties.get(valuePropertyName)).getFieldType()) {
        case BOOLEAN:
            return Boolean.parseBoolean(value);
        case DATE:
            return getSimpleDateFormatter().parse(value);
        case DECIMAL:
            return new BigDecimal(value);
        case MONEY:
            return new Money(value);
        case INTEGER:
            return Integer.parseInt(value);
        default:
            return value;
        }
    }

    @Override
    public Entity update(PersistencePackage persistencePackage) throws ServiceException {
        String[] customCriteria = persistencePackage.getCustomCriteria();
        if (customCriteria != null && customCriteria.length > 0) {
            LOG.warn(
                    "custom persistence handlers and custom criteria not supported for update types other than BASIC");
        }
        PersistencePerspective persistencePerspective = persistencePackage.getPersistencePerspective();
        Entity entity = persistencePackage.getEntity();
        MapStructure mapStructure = (MapStructure) persistencePerspective.getPersistencePerspectiveItems()
                .get(PersistencePerspectiveItemType.MAPSTRUCTURE);
        if (!mapStructure.getMutable()) {
            throw new SecurityServiceException("Field not mutable");
        }
        try {
            Map<String, FieldMetadata> ceilingMergedProperties = getSimpleMergedProperties(entity.getType()[0],
                    persistencePerspective);
            String mapKey = entity.findProperty(mapStructure.getKeyPropertyName()).getValue();
            if (ceilingMergedProperties
                    .containsKey(mapStructure.getMapProperty() + FieldManager.MAPFIELDSEPARATOR + mapKey)) {
                throw new ServiceException("\"" + mapKey + "\" is a reserved property name.");
            }

            Serializable instance = persistenceManager.getDynamicEntityDao().retrieve(
                    Class.forName(entity.getType()[0]), Long.valueOf(entity.findProperty("symbolicId").getValue()));

            Assert.isTrue(instance != null, "Entity not found");

            FieldManager fieldManager = getFieldManager();
            Map map = (Map) fieldManager.getFieldValue(instance, mapStructure.getMapProperty());

            PersistentClass persistentClass = persistenceManager.getDynamicEntityDao()
                    .getPersistentClass(mapStructure.getValueClassName());
            Map<String, FieldMetadata> valueUnfilteredMergedProperties;
            if (persistentClass == null) {
                valueUnfilteredMergedProperties = persistenceManager.getDynamicEntityDao()
                        .getPropertiesForPrimitiveClass(
                                ((SimpleValueMapStructure) mapStructure).getValuePropertyName(),
                                ((SimpleValueMapStructure) mapStructure).getValuePropertyFriendlyName(),
                                Class.forName(mapStructure.getValueClassName()), Class.forName(entity.getType()[0]),
                                MergedPropertyType.MAPSTRUCTUREVALUE);
            } else {
                valueUnfilteredMergedProperties = persistenceManager.getDynamicEntityDao().getMergedProperties(
                        mapStructure.getValueClassName(),
                        new Class[] { Class.forName(mapStructure.getValueClassName()) }, null, new String[] {},
                        new ForeignKey[] {}, MergedPropertyType.MAPSTRUCTUREVALUE,
                        persistencePerspective.getPopulateToOneFields(), persistencePerspective.getIncludeFields(),
                        persistencePerspective.getExcludeFields(), persistencePerspective.getConfigurationKey(),
                        "");
            }
            Map<String, FieldMetadata> valueMergedProperties = filterOutCollectionMetadata(
                    valueUnfilteredMergedProperties);

            if (StringUtils.isEmpty(mapKey)) {
                entity.addValidationError(mapStructure.getKeyPropertyName(),
                        RequiredPropertyValidator.ERROR_MESSAGE);
                LOG.debug("No key property passed in for map, failing validation");
            }

            populate: {
                if (persistentClass != null) {
                    Serializable valueInstance = (Serializable) map.get(entity.findProperty("priorKey").getValue());

                    if (valueInstance == null) {
                        valueInstance = procureSandBoxMapValue(mapStructure, entity);
                        if (valueInstance == null) {
                            break populate;
                        }
                    }

                    if (map.get(mapKey) != null && !map.get(mapKey).equals(valueInstance)) {
                        entity.addValidationError(mapStructure.getKeyPropertyName(), "keyExistsValidationError");
                    }

                    if (StringUtils.isNotBlank(mapStructure.getMapKeyValueProperty())) {
                        Property p = entity.findProperty("key");
                        Property newP = new Property();
                        newP.setName(mapStructure.getMapKeyValueProperty());
                        newP.setValue(p.getValue());
                        newP.setIsDirty(p.getIsDirty());
                        entity.addProperty(newP);
                    }

                    //allow validation on other properties in order to show key validation errors along with all the other properties
                    //validation errors
                    valueInstance = createPopulatedInstance(valueInstance, entity, valueMergedProperties, false);

                    if (StringUtils.isNotEmpty(mapKey) && !entity.isValidationFailure()) {
                        if (!entity.findProperty("priorKey").getValue().equals(mapKey)) {
                            map.remove(entity.findProperty("priorKey").getValue());
                        }
                        /*
                         * TODO this map manipulation code currently assumes the key value is a String. This should be widened to accept
                         * additional types of primitive objects.
                         */
                        map.put(entity.findProperty(mapStructure.getKeyPropertyName()).getValue(), valueInstance);
                    }
                } else {
                    if (StringUtils.isNotEmpty(mapKey) && !entity.isValidationFailure()) {
                        map.put(entity.findProperty(mapStructure.getKeyPropertyName()).getValue(),
                                entity.findProperty(((SimpleValueMapStructure) mapStructure).getValuePropertyName())
                                        .getValue());
                    }
                }
            }

            instance = persistenceManager.getDynamicEntityDao().merge(instance);

            Entity[] responses = getMapRecords(instance, mapStructure, ceilingMergedProperties,
                    valueMergedProperties, entity.findProperty("symbolicId"));
            for (Entity response : responses) {
                if (response.findProperty(mapStructure.getKeyPropertyName()).getValue().equals(persistencePackage
                        .getEntity().findProperty(mapStructure.getKeyPropertyName()).getValue())) {
                    return response;
                }
            }
            //could be empty if reverting a sandbox item that has experienced a deletion. make sure to at least return an empty instance of Entity.
            return ArrayUtils.isEmpty(responses) ? new Entity() : responses[0];
        } catch (Exception e) {
            throw new ServiceException("Problem updating entity : " + e.getMessage(), e);
        }
    }

    @Override
    public void remove(PersistencePackage persistencePackage) throws ServiceException {
        String[] customCriteria = persistencePackage.getCustomCriteria();
        if (customCriteria != null && customCriteria.length > 0) {
            LOG.warn(
                    "custom persistence handlers and custom criteria not supported for remove types other than BASIC");
        }
        PersistencePerspective persistencePerspective = persistencePackage.getPersistencePerspective();
        Entity entity = persistencePackage.getEntity();
        MapStructure mapStructure = (MapStructure) persistencePerspective.getPersistencePerspectiveItems()
                .get(PersistencePerspectiveItemType.MAPSTRUCTURE);
        if (!mapStructure.getMutable()) {
            throw new SecurityServiceException("Field not mutable");
        }
        try {
            Map<String, FieldMetadata> ceilingMergedProperties = getSimpleMergedProperties(entity.getType()[0],
                    persistencePerspective);
            String mapKey = entity.findProperty(mapStructure.getKeyPropertyName()).getValue();
            if (ceilingMergedProperties
                    .containsKey(mapStructure.getMapProperty() + FieldManager.MAPFIELDSEPARATOR + mapKey)) {
                throw new ServiceException("\"" + mapKey + "\" is a reserved property name.");
            }

            Serializable instance = persistenceManager.getDynamicEntityDao().retrieve(
                    Class.forName(entity.getType()[0]), Long.valueOf(entity.findProperty("symbolicId").getValue()));

            Assert.isTrue(instance != null, "Entity not found");

            FieldManager fieldManager = getFieldManager();
            Map map = (Map) fieldManager.getFieldValue(instance, mapStructure.getMapProperty());

            Object value = map.remove(entity.findProperty("priorKey").getValue());
            if (mapStructure.getDeleteValueEntity()) {
                persistenceManager.getDynamicEntityDao().remove((Serializable) value);
            }
        } catch (Exception e) {
            throw new ServiceException("Problem removing entity : " + e.getMessage(), e);
        }
    }

    @Override
    public DynamicResultSet fetch(PersistencePackage persistencePackage, CriteriaTransferObject cto)
            throws ServiceException {
        Entity[] payload;
        int totalRecords;
        String ceilingEntityFullyQualifiedClassname = persistencePackage.getCeilingEntityFullyQualifiedClassname();
        if (StringUtils.isEmpty(persistencePackage.getFetchTypeFullyQualifiedClassname())) {
            persistencePackage.setFetchTypeFullyQualifiedClassname(ceilingEntityFullyQualifiedClassname);
        }
        try {
            PersistencePerspective persistencePerspective = persistencePackage.getPersistencePerspective();
            Class<?>[] entities = persistenceManager.getPolymorphicEntities(ceilingEntityFullyQualifiedClassname);
            Map<String, FieldMetadata> mergedProperties = persistenceManager.getDynamicEntityDao()
                    .getMergedProperties(ceilingEntityFullyQualifiedClassname, entities,
                            (ForeignKey) persistencePerspective.getPersistencePerspectiveItems()
                                    .get(PersistencePerspectiveItemType.FOREIGNKEY),
                            persistencePerspective.getAdditionalNonPersistentProperties(),
                            persistencePerspective.getAdditionalForeignKeys(), MergedPropertyType.PRIMARY,
                            persistencePerspective.getPopulateToOneFields(),
                            persistencePerspective.getIncludeFields(), persistencePerspective.getExcludeFields(),
                            persistencePerspective.getConfigurationKey(), "");
            MapStructure mapStructure = (MapStructure) persistencePerspective.getPersistencePerspectiveItems()
                    .get(PersistencePerspectiveItemType.MAPSTRUCTURE);

            PersistentClass persistentClass = persistenceManager.getDynamicEntityDao()
                    .getPersistentClass(mapStructure.getValueClassName());
            Map<String, FieldMetadata> valueUnfilteredMergedProperties;
            if (persistentClass == null) {
                valueUnfilteredMergedProperties = persistenceManager.getDynamicEntityDao()
                        .getPropertiesForPrimitiveClass(
                                ((SimpleValueMapStructure) mapStructure).getValuePropertyName(),
                                ((SimpleValueMapStructure) mapStructure).getValuePropertyFriendlyName(),
                                Class.forName(mapStructure.getValueClassName()),
                                Class.forName(ceilingEntityFullyQualifiedClassname),
                                MergedPropertyType.MAPSTRUCTUREVALUE);
            } else {
                valueUnfilteredMergedProperties = persistenceManager.getDynamicEntityDao().getMergedProperties(
                        mapStructure.getValueClassName(),
                        new Class[] { Class.forName(mapStructure.getValueClassName()) }, null, new String[] {},
                        new ForeignKey[] {}, MergedPropertyType.MAPSTRUCTUREVALUE, false, new String[] {},
                        new String[] {}, null, "");
            }
            Map<String, FieldMetadata> valueMergedProperties = filterOutCollectionMetadata(
                    valueUnfilteredMergedProperties);

            List<FilterMapping> filterMappings = getFilterMappings(persistencePerspective, cto,
                    persistencePackage.getFetchTypeFullyQualifiedClassname(), mergedProperties);

            if (CollectionUtils.isNotEmpty(cto.getAdditionalFilterMappings())) {
                filterMappings.addAll(cto.getAdditionalFilterMappings());
            }

            totalRecords = getTotalRecords(persistencePackage.getFetchTypeFullyQualifiedClassname(),
                    filterMappings);
            if (totalRecords > 1) {
                throw new ServiceException(
                        "Queries to retrieve an entity containing a MapStructure must return only 1 entity. Your query returned ("
                                + totalRecords + ") values.");
            }
            List<Serializable> records = getPersistentRecords(
                    persistencePackage.getFetchTypeFullyQualifiedClassname(), filterMappings, cto.getFirstResult(),
                    cto.getMaxResults());
            Map<String, FieldMetadata> ceilingMergedProperties = getSimpleMergedProperties(
                    ceilingEntityFullyQualifiedClassname, persistencePerspective);
            payload = getMapRecords(records.get(0), mapStructure, ceilingMergedProperties, valueMergedProperties,
                    null);
        } catch (Exception e) {
            throw new ServiceException("Unable to fetch results for " + ceilingEntityFullyQualifiedClassname, e);
        }

        DynamicResultSet results = new DynamicResultSet(null, payload, payload.length);

        return results;
    }

    protected Serializable procureSandBoxMapValue(MapStructure mapStructure, Entity entity) {
        try {
            Serializable valueInstance = null;
            //this is probably a sync from another sandbox where they've updated a map item for which we've updated the key in our own sandbox
            //(i.e. the map entry key was changed by us in our sandbox, so our map does not have the requested key)
            Class<?> valueClass = Class.forName(mapStructure.getValueClassName());
            Map<String, Object> idMetadata = getPersistenceManager().getDynamicEntityDao()
                    .getIdMetadata(valueClass);
            String idProperty = (String) idMetadata.get("name");
            Property prop = entity.findProperty(idProperty);
            if (prop != null) {
                Serializable identifier;
                if (!(((Type) idMetadata.get("type")) instanceof StringType)) {
                    identifier = Long.parseLong(prop.getValue());
                } else {
                    identifier = prop.getValue();
                }
                valueInstance = (Serializable) getPersistenceManager().getDynamicEntityDao().find(valueClass,
                        identifier);
                BroadleafRequestContext context = BroadleafRequestContext.getBroadleafRequestContext();
                if (sandBoxHelper.isSandBoxable(valueInstance.getClass().getName()) && context != null
                        && !context.isProductionSandBox()) {
                    if (sandBoxHelper.isPromote() && !sandBoxHelper.isReject()) {
                        //if this is a prod record (i.e. the destination map has deleted our record), then duplicate our value
                        //so it's available in this sandbox
                        valueInstance = getPersistenceManager().getDynamicEntityDao().merge(valueInstance);
                    } else {
                        valueInstance = null;
                    }
                }
            }
            return valueInstance;
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    protected Entity[] getMapRecords(Serializable record, MapStructure mapStructure,
            Map<String, FieldMetadata> ceilingMergedProperties, Map<String, FieldMetadata> valueMergedProperties,
            Property symbolicIdProperty)
            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException, SecurityException,
            IllegalArgumentException, ClassNotFoundException, NoSuchFieldException {
        //compile a list of mapKeys that were used as mapFields
        List<String> mapFieldKeys = new ArrayList<String>();
        String mapProperty = mapStructure.getMapProperty();
        for (Map.Entry<String, FieldMetadata> entry : ceilingMergedProperties.entrySet()) {
            if (entry.getKey().startsWith(mapProperty + FieldManager.MAPFIELDSEPARATOR)) {
                mapFieldKeys.add(entry.getKey().substring(entry.getKey().indexOf(FieldManager.MAPFIELDSEPARATOR)
                        + FieldManager.MAPFIELDSEPARATOR.length(), entry.getKey().length()));
            }
        }
        Collections.sort(mapFieldKeys);

        FieldManager fieldManager = getFieldManager();
        Map map;
        try {
            map = (Map) fieldManager.getFieldValue(record, mapProperty);
        } catch (FieldNotAvailableException e) {
            throw new IllegalArgumentException(e);
        }
        List<Entity> entities = new ArrayList<Entity>(map.size());
        for (Object key : map.keySet()) {
            if (key instanceof String && mapFieldKeys.contains(key)) {
                continue;
            }
            entities.add(getMapRecord(record.getClass().getName(), (Serializable) map.get(key), mapStructure,
                    valueMergedProperties, symbolicIdProperty, key));
        }

        return entities.toArray(new Entity[entities.size()]);
    }

    protected Entity getMapRecord(String ceilingClass, Serializable valueInstance, MapStructure mapStructure,
            Map<String, FieldMetadata> valueMergedProperties, Property symbolicIdProperty, Object key) {
        Entity entityItem = new Entity();
        entityItem.setType(new String[] { ceilingClass });
        List<Property> props = new ArrayList<Property>();

        Property propertyItem = new Property();
        propertyItem.setName(mapStructure.getKeyPropertyName());
        props.add(propertyItem);
        String strVal;
        if (Date.class.isAssignableFrom(key.getClass())) {
            strVal = getSimpleDateFormatter().format((Date) key);
        } else if (Timestamp.class.isAssignableFrom(key.getClass())) {
            strVal = getSimpleDateFormatter().format(new Date(((Timestamp) key).getTime()));
        } else if (Calendar.class.isAssignableFrom(key.getClass())) {
            strVal = getSimpleDateFormatter().format(((Calendar) key).getTime());
        } else if (Double.class.isAssignableFrom(key.getClass())) {
            strVal = getDecimalFormatter().format(key);
        } else if (BigDecimal.class.isAssignableFrom(key.getClass())) {
            strVal = getDecimalFormatter().format(key);
        } else {
            strVal = key.toString();
        }
        propertyItem.setValue(strVal);

        extractPropertiesFromPersistentEntity(valueMergedProperties, valueInstance, props);
        if (symbolicIdProperty != null) {
            props.add(symbolicIdProperty);
        }

        Property[] properties = new Property[props.size()];
        properties = props.toArray(properties);
        entityItem.setProperties(properties);

        return entityItem;
    }
}