org.betaconceptframework.astroboa.engine.jcr.util.PopulateComplexCmsProperty.java Source code

Java tutorial

Introduction

Here is the source code for org.betaconceptframework.astroboa.engine.jcr.util.PopulateComplexCmsProperty.java

Source

/*
 * Copyright (C) 2005-2012 BetaCONCEPT Limited
 *
 * This file is part of Astroboa.
 *
 * Astroboa 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.
 *
 * Astroboa 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 Astroboa.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.betaconceptframework.astroboa.engine.jcr.util;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Value;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.time.DurationFormatUtils;
import org.betaconceptframework.astroboa.api.model.BooleanProperty;
import org.betaconceptframework.astroboa.api.model.CmsProperty;
import org.betaconceptframework.astroboa.api.model.ComplexCmsProperty;
import org.betaconceptframework.astroboa.api.model.ComplexCmsRootProperty;
import org.betaconceptframework.astroboa.api.model.SimpleCmsProperty;
import org.betaconceptframework.astroboa.api.model.ValueType;
import org.betaconceptframework.astroboa.api.model.definition.CmsPropertyDefinition;
import org.betaconceptframework.astroboa.api.model.definition.ComplexCmsPropertyDefinition;
import org.betaconceptframework.astroboa.api.model.definition.SimpleCmsPropertyDefinition;
import org.betaconceptframework.astroboa.api.model.exception.CmsException;
import org.betaconceptframework.astroboa.engine.jcr.PrototypeFactory;
import org.betaconceptframework.astroboa.engine.jcr.dao.ContentDefinitionDao;
import org.betaconceptframework.astroboa.engine.jcr.visitor.ComplexCmsPropertyNodeRemovalVisitor;
import org.betaconceptframework.astroboa.model.impl.CmsPropertyIndexable;
import org.betaconceptframework.astroboa.model.impl.ComplexCmsPropertyImpl;
import org.betaconceptframework.astroboa.model.impl.ComplexCmsRootPropertyImpl;
import org.betaconceptframework.astroboa.model.impl.LazyCmsProperty;
import org.betaconceptframework.astroboa.model.impl.SaveMode;
import org.betaconceptframework.astroboa.model.impl.item.CmsBuiltInItem;
import org.betaconceptframework.astroboa.model.impl.item.CmsReadOnlyItem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * 
 * @author Gregory Chomatas (gchomatas@betaconcept.com)
 * @author Savvas Triantafyllou (striantafyllou@betaconcept.com)
 *
 */
public class PopulateComplexCmsProperty {

    private final Logger logger = LoggerFactory.getLogger(PopulateComplexCmsProperty.class);

    @Autowired
    private PrototypeFactory prototypeFactory;
    @Autowired
    private ContentDefinitionDao contentDefinitionDao;

    private ComplexCmsPropertyDefinition complexPropertyDefinition;
    private ComplexCmsProperty complexProperty;
    private Node complexPropertyNode;

    private Session session;

    private SaveMode saveMode;

    //Collect all exceptions about non existence of mandatory child properties
    //If this complex cms property is optional and no child property has been saved
    //then throw these exceptions
    private List<String> exceptionsForMandatoryProperties = new ArrayList<String>();

    private boolean atLeastOneChildPropertySaved = false;

    private String contentObjectNodeUUID;

    private Context context;

    public void setContext(Context context) {
        this.context = context;
    }

    private boolean complexCmsPropertyNodeRepresentsAContentObjectNode;

    /**
     * @param contentObjectNodeUUID the contentObjectNodeUUID to set
     */
    public void setContentObjectNodeUUID(String contentObjectNodeUUID) {
        this.contentObjectNodeUUID = contentObjectNodeUUID;
    }

    public List<String> getExceptionsForMandatoryProperties() {
        return exceptionsForMandatoryProperties;
    }

    public boolean atLeastOneChildPropertySaved() {
        return atLeastOneChildPropertySaved;
    }

    public void propertyIsPopulated(String propertyName) throws RepositoryException {
        atLeastOneChildPropertySaved = true;
    }

    public void setSession(Session session) {
        this.session = session;
    }

    public void setSaveMode(SaveMode saveMode) {
        this.saveMode = saveMode;
    }

    public void setComplexPropertyDefinition(ComplexCmsPropertyDefinition complexPropertyDefinition) {
        this.complexPropertyDefinition = complexPropertyDefinition;
    }

    public void setComplexProperty(ComplexCmsProperty<?, ?> complexProperty) {
        this.complexProperty = complexProperty;

    }

    public void setComplexPropertyNode(Node complexPropertyNode) {
        this.complexPropertyNode = complexPropertyNode;

        if (this.complexPropertyNode == null) {
            throw new CmsException("Complex cms property node is null");
        }

        try {
            complexCmsPropertyNodeRepresentsAContentObjectNode = complexPropertyNode
                    .isNodeType(CmsBuiltInItem.StructuredContentObject.getJcrName());
        } catch (RepositoryException e) {
            throw new CmsException(e);
        }
    }

    public void populate() throws Exception {

        if (logger.isDebugEnabled()) {
            logger.debug("Starting complex property {} population.", complexProperty.getFullPath());
        }

        checkDefinitionWithProperty();

        //Visit child properties
        populateChildProperties();

        //Visit aspects
        populateAspects();
        if (logger.isDebugEnabled()) {
            logger.debug(
                    "Ended complex property {} population. At least one property is saved : {}. Exception messages for mandatory properties {}",
                    new Object[] { complexProperty.getFullPath(), atLeastOneChildPropertySaved,
                            exceptionsForMandatoryProperties });
        }

    }

    private void populateAspects() throws Exception {

        if (complexPropertyNode.isNodeType(CmsBuiltInItem.StructuredContentObject.getJcrName())
                && complexProperty instanceof ComplexCmsRootProperty) {

            //Populate Aspects
            List<String> newAspects = ((ComplexCmsRootProperty) complexProperty).getAspects();

            boolean newAspectsExist = CollectionUtils.isNotEmpty(newAspects);
            if (newAspectsExist) {
                for (String aspect : newAspects) {
                    //Retrieve aspect definition
                    ComplexCmsPropertyDefinition aspectDefinition = contentDefinitionDao
                            .getAspectDefinition(aspect);

                    if (aspectDefinition == null)
                        throw new CmsException("Aspect " + aspect + " does not have a definition");

                    populateAccordingToChildPropertyDefinition(aspect, aspectDefinition);

                }
            }

            // Check which aspects should be removed
            Value[] existingAspects = null;
            if (complexPropertyNode.hasProperty(CmsBuiltInItem.Aspects.getJcrName())) {
                existingAspects = complexPropertyNode.getProperty(CmsBuiltInItem.Aspects.getJcrName()).getValues();
            }

            Map<String, Node> aspectNodesToBeRemoved = new HashMap<String, Node>();

            if (!ArrayUtils.isEmpty(existingAspects)) {
                for (Value existingAspect : existingAspects) {
                    //If aspect list is empty or it does not contain existing aspect, remove aspect node
                    String existingAspectName = existingAspect.getString();

                    //Remove existing aspect only if it has been removed by the user
                    if (!newAspects.contains(existingAspectName)) {

                        if (((ComplexCmsPropertyImpl) complexProperty)
                                .cmsPropertyHasBeenLoadedAndRemoved(existingAspectName)) {

                            //Node may have already been removed
                            if (complexPropertyNode.hasNode(existingAspectName)) {
                                aspectNodesToBeRemoved.put(existingAspectName,
                                        complexPropertyNode.getNode(existingAspectName));
                            }
                        } else {
                            if (newAspectsExist) {
                                logger.warn("Property " + existingAspectName
                                        + " is not included in the aspect list '" + newAspects
                                        + "' of the content object " + contentObjectNodeUUID
                                        + " but it has not removed properly from the content object. This is probably a bug, however property will not be removed from the repository.");
                            }
                            //Load Aspect again
                            ((ComplexCmsRootPropertyImpl) complexProperty).loadAspectDefinition(existingAspectName);
                        }
                    }
                }
            }

            //Remove deleted aspects
            if (MapUtils.isNotEmpty(aspectNodesToBeRemoved)) {
                ComplexCmsPropertyNodeRemovalVisitor aspectNodeRemovalVisitor = prototypeFactory
                        .newComplexCmsPropertyNodeRemovalVisitor();
                aspectNodeRemovalVisitor.setParentNode(complexPropertyNode, complexPropertyNode);
                aspectNodeRemovalVisitor.setSession(session);

                for (Node aspectNodeToBeRemoved : aspectNodesToBeRemoved.values()) {

                    //Find aspect definition
                    String aspectName = aspectNodeToBeRemoved.getName();

                    ComplexCmsPropertyDefinition aspectDefinition = contentDefinitionDao
                            .getAspectDefinition(aspectName);

                    if (aspectDefinition == null)
                        throw new CmsException("Unable to find definition for aspect " + aspectName
                                + ". Aspect jcr node " + aspectNodeToBeRemoved.getPath()
                                + " will not be removed from content object jcr node");
                    else {
                        aspectNodeRemovalVisitor.loadChildNodesToBeDeleted(aspectName);

                        aspectDefinition.accept(aspectNodeRemovalVisitor);

                        aspectNodeToBeRemoved.remove();
                    }
                }
            }

            //Finally it may be the case that an aspect has been specified but finally
            //no jcr equivalent node exists. For example one provided an aspect but without
            //any value for at least one of its child properties. In that case repository
            //has chosen to delete any previous jcr node for this aspect, or not to create 
            //a jcr node if the aspect was not there at first place.
            List<String> aspectsToBeRemoved = new ArrayList<String>();
            for (String finalAspect : newAspects) {
                if (!complexPropertyNode.hasNode(finalAspect)) {
                    aspectsToBeRemoved.add(finalAspect);
                }
            }

            if (aspectsToBeRemoved.size() > 0) {
                newAspects = (List<String>) CollectionUtils.disjunction(newAspects, aspectsToBeRemoved);

                //Also remove them from complex property
                for (String aspectToBeRemoved : aspectsToBeRemoved) {
                    if (((ComplexCmsRootProperty) complexProperty).hasAspect(aspectToBeRemoved)) {
                        ((ComplexCmsRootProperty) complexProperty).removeChildProperty(aspectToBeRemoved);
                    }
                }
            }

            //Update aspects jcr property with new values
            JcrNodeUtils.addMultiValueProperty(complexPropertyNode, CmsBuiltInItem.Aspects, SaveMode.UPDATE,
                    newAspects, ValueType.String, session.getValueFactory());
        }

    }

    private void populateChildProperties() throws RepositoryException, Exception {
        Map<String, CmsPropertyDefinition> childPropertyDefinitions = complexPropertyDefinition
                .getChildCmsPropertyDefinitions();

        if (MapUtils.isNotEmpty(childPropertyDefinitions)) {
            for (Entry<String, CmsPropertyDefinition> childPropertyDefinitionEntry : childPropertyDefinitions
                    .entrySet()) {
                String childPropertyName = childPropertyDefinitionEntry.getKey();

                //Check that no built in property has been provided for population
                if (complexCmsPropertyNodeRepresentsAContentObjectNode
                        && (CmsBuiltInItem.OwnerCmsIdentifier.getJcrName().equals(childPropertyName)
                                || CmsBuiltInItem.ContentObjectTypeName.getJcrName().equals(childPropertyName)
                                || CmsBuiltInItem.Aspects.getJcrName().equals(childPropertyName))) {
                    logger.debug("Try to populate cms builtin property {} in content object node {}",
                            childPropertyName, complexPropertyNode.getPath());
                } else {
                    populateAccordingToChildPropertyDefinition(childPropertyName,
                            childPropertyDefinitionEntry.getValue());
                }
            }

            if (CollectionUtils.isNotEmpty(exceptionsForMandatoryProperties)) {

                //Special Case 
                clearExceptionMessagesIfTheOnlyPropertiesWhichHaveValuesAreOfTypeBoolean();

                //Throw exception if at least one child property is saved
                //or we reach a root complex definition which is mandatory
                if (CollectionUtils.isNotEmpty(exceptionsForMandatoryProperties) && (atLeastOneChildPropertySaved()
                        || (complexPropertyDefinition.isMandatory() && complexProperty.getParentProperty() != null
                                && complexProperty.getParentProperty() instanceof ComplexCmsRootProperty))) {

                    logger.debug("Exceptions for mandatory properties found {}. Exception message is being built.",
                            exceptionsForMandatoryProperties);

                    StringBuffer sb = new StringBuffer();
                    sb.append("There are some mandatory children of the property " + complexProperty.getFullPath()
                            + " which need to be saved but they do not have any value. "
                            + (atLeastOneChildPropertySaved()
                                    ? "There is at least one child property which can be saved without any problem"
                                    : ""));

                    for (String exceptionMessage : exceptionsForMandatoryProperties) {
                        sb.append("\n");
                        sb.append(exceptionMessage);
                    }

                    throw new CmsException(sb.toString());
                } else {
                    if ((!atLeastOneChildPropertySaved() && !complexPropertyDefinition.isMandatory())
                            || (complexProperty.getParentProperty() != null
                                    && complexProperty.getParentProperty() instanceof ComplexCmsRootProperty)) {

                        //Clear exception messages if 
                        //1. Property is not mandatory and no child property has any value to be saved.
                        //2. we have reached a root property
                        exceptionsForMandatoryProperties.clear();
                    }
                }
            }

        } else {
            logger.warn("Complex content object property {} does not have any child property definitions",
                    complexPropertyDefinition.getName());
        }
    }

    private void clearExceptionMessagesIfTheOnlyPropertiesWhichHaveValuesAreOfTypeBoolean() {
        if (atLeastOneChildPropertySaved()) {
            //Iterate all the loaded simple properties of the complex property.
            //In case the only property which has value is a Boolean Property
            //then clear exception messages and mark this property as empty
            Map<String, List<CmsProperty<?, ?>>> loadedProperties = complexProperty.getChildProperties();

            boolean foundAtLeastOnePropertyWhoseValueMustBeSaved = false;

            for (List<CmsProperty<?, ?>> propertyList : loadedProperties.values()) {

                for (CmsProperty<?, ?> property : propertyList) {
                    if (property instanceof SimpleCmsProperty) {

                        if (((SimpleCmsProperty) property).hasValues()) {
                            if (property.getValueType() != ValueType.Boolean) {
                                foundAtLeastOnePropertyWhoseValueMustBeSaved = true;
                                break;
                            } else {
                                if (property.getPropertyDefinition().isMultiple()
                                        && ((SimpleCmsProperty) property).getSimpleTypeValues().size() > 1) {
                                    foundAtLeastOnePropertyWhoseValueMustBeSaved = true;
                                    break;
                                }

                                if (//Property is a boolean property and its value is not the same with the default value or
                                (((BooleanProperty) property).getPropertyDefinition() != null
                                        && ((BooleanProperty) property).getPropertyDefinition().isSetDefaultValue()
                                        && ((BooleanProperty) property).getPropertyDefinition()
                                                .getDefaultValue() != ((BooleanProperty) property).getFirstValue())
                                        ||
                                        //no default value is set and its value is TRUE
                                        (((BooleanProperty) property).getFirstValue())) {
                                    foundAtLeastOnePropertyWhoseValueMustBeSaved = true;
                                    break;
                                }
                            }
                        }
                    } else if (property.getPropertyDefinition() != null
                            && property.getPropertyDefinition().isMandatory()) {
                        foundAtLeastOnePropertyWhoseValueMustBeSaved = true;
                    }
                }
            }

            if (!foundAtLeastOnePropertyWhoseValueMustBeSaved) {
                atLeastOneChildPropertySaved = false;
                exceptionsForMandatoryProperties.clear();
            }

        }
    }

    private void populateAccordingToChildPropertyDefinition(String childPropertyName,
            CmsPropertyDefinition childPropertyDefinition) throws RepositoryException, Exception {

        //Populate only if child property is not obsolete
        //and is not one of HasVersion or Versions
        if (!childPropertyDefinition.isObsolete()
                && !CmsReadOnlyItem.HasVersion.getJcrName().equals(childPropertyName)
                && !CmsReadOnlyItem.Versions.getJcrName().equals(childPropertyName)) {

            //Populate only if child property is loaded
            if (complexProperty.isChildPropertyLoaded(childPropertyName)) {

                if (ValueType.Complex == childPropertyDefinition.getValueType()) {
                    populateComplexChildProperty(childPropertyName,
                            (ComplexCmsPropertyDefinition) childPropertyDefinition);
                } else {
                    populateSimpleChildProperty(childPropertyName,
                            (SimpleCmsPropertyDefinition) childPropertyDefinition);
                }
            } else {

                //Special case. If object is a new object and property is accessibility then it must be loaded with the default values
                if (SaveMode.INSERT == saveMode && childPropertyDefinition instanceof ComplexCmsPropertyDefinition
                        && StringUtils.equals("accessibility", childPropertyName)
                        && complexProperty instanceof ComplexCmsRootProperty) {

                    ((ComplexCmsPropertyImpl) complexProperty).createNewChildCmsPropertyTemplate(childPropertyName,
                            false);

                    populateComplexChildProperty(childPropertyName,
                            (ComplexCmsPropertyDefinition) childPropertyDefinition);
                }
                //In case where property is a MANDATORY simple property with a default value
                //initialize property and populate it
                else if (childPropertyDefinition instanceof SimpleCmsPropertyDefinition
                        && ((SimpleCmsPropertyDefinition) childPropertyDefinition).isMandatory()
                        && ((SimpleCmsPropertyDefinition) childPropertyDefinition).isSetDefaultValue()) {

                    ((ComplexCmsPropertyImpl) complexProperty).createNewChildCmsPropertyTemplate(childPropertyName,
                            false);

                    populateSimpleChildProperty(childPropertyName,
                            (SimpleCmsPropertyDefinition) childPropertyDefinition);
                }
            }

            //If property is mandatory then it should exist in repository either as a node or as a property
            //depending on its type
            boolean complexPropertyNodeHasChildProperty = false;

            if (ValueType.Binary == childPropertyDefinition.getValueType()
                    || ValueType.Complex == childPropertyDefinition.getValueType()) {
                complexPropertyNodeHasChildProperty = complexPropertyNode.hasNode(childPropertyName);
            } else {
                complexPropertyNodeHasChildProperty = complexPropertyNode.hasProperty(childPropertyName);
            }

            if (!complexPropertyNodeHasChildProperty) {

                if (childPropertyDefinition.isMandatory()) {
                    logger.debug("Mandatory property {} has not been saved to corresponding jcr node {}",
                            childPropertyName, complexPropertyNode.getPath());
                    exceptionsForMandatoryProperties.add("Mandatory property " + childPropertyName
                            + " has not been saved to corresponding jcr node " + complexPropertyNode.getPath());
                }
            } else {
                propertyIsPopulated(childPropertyName);
            }
        }
    }

    private void populateComplexChildProperty(String childPropertyName,
            ComplexCmsPropertyDefinition childPropertyDefinition) throws Exception {

        //Retrieve all complex child properties
        List<CmsProperty<?, ?>> childComplexProperties = new ArrayList<CmsProperty<?, ?>>();
        boolean childComplexCmsPropertyLoaded = false;

        if (((ComplexCmsPropertyImpl) complexProperty).cmsPropertyHasBeenLoadedAndRemoved(childPropertyName)) {
            childComplexCmsPropertyLoaded = true;
        } else if (childPropertyDefinition.isMultiple()) {
            List<CmsProperty<?, ?>> childPropertyList = complexProperty.getChildPropertyList(childPropertyName);

            //A not null list specifies that child complex cms property has been loaded
            if (childPropertyList != null) {

                childComplexCmsPropertyLoaded = true;

                if (CollectionUtils.isNotEmpty(childPropertyList)) {
                    childComplexProperties.addAll(childPropertyList);
                }
            }

        } else {
            CmsProperty<?, ?> childProperty = complexProperty.getChildProperty(childPropertyName);
            if (childProperty != null) {
                childComplexCmsPropertyLoaded = true;
                childComplexProperties.add(childProperty);
            }
        }

        Map<String, Node> existingChildComplexNodes = retrieveExistingChildComplexNodesWithPath(childPropertyName);

        Map<Node, Integer> indicesForNodesToBePersisted = new HashMap<Node, Integer>();

        if (CollectionUtils.isNotEmpty(childComplexProperties)) {

            for (CmsProperty<?, ?> childComplexContentObjectProperty : childComplexProperties) {

                String childComplexContentObjectPropertyId = childComplexContentObjectProperty.getId();

                Node childComplexPropertyNode = null;

                //Complex Cms Property is considered new if no id is found or
                //an id is provided but not found here
                if (StringUtils.isBlank(childComplexContentObjectPropertyId)) {

                    //Special case
                    //if complex child property is single valued, user did not provide any id
                    //and there is one node already existing (that is an update is taking place
                    //use that node in order to update it and do not create a new one. This way
                    //no node removal will take place
                    if (!childPropertyDefinition.isMultiple() && existingChildComplexNodes.size() == 1) {
                        childComplexContentObjectPropertyId = existingChildComplexNodes.keySet().iterator().next();
                        childComplexPropertyNode = existingChildComplexNodes
                                .get(childComplexContentObjectPropertyId);

                        childComplexContentObjectProperty.setId(childComplexContentObjectPropertyId);
                        ((LazyCmsProperty) childComplexContentObjectProperty)
                                .setPropertyContainerNodeUUID(childComplexPropertyNode.getIdentifier());
                        ((LazyCmsProperty) childComplexContentObjectProperty)
                                .setContentObjectNodeUUID(contentObjectNodeUUID);

                    } else {
                        childComplexPropertyNode = createNewNodeForChildComplexCmsProperty(childPropertyName,
                                childPropertyDefinition, existingChildComplexNodes,
                                childComplexContentObjectProperty, false);
                    }
                } else {
                    childComplexPropertyNode = existingChildComplexNodes.get(childComplexContentObjectPropertyId);

                    if (childComplexPropertyNode == null) {
                        //It may a complex cms property with provided id
                        childComplexPropertyNode = createNewNodeForChildComplexCmsProperty(childPropertyName,
                                childPropertyDefinition, existingChildComplexNodes,
                                childComplexContentObjectProperty, true);

                        if (childComplexPropertyNode == null) {
                            throw new CmsException("Complex content object property "
                                    + childComplexContentObjectProperty.getFullPath()
                                    + " does not exist in repository");
                        }
                    }

                }

                //Create a new PopulateTask
                PopulateComplexCmsProperty childComplexCmsPropertyPopulateTask = populateChildComplexCmsProperty(
                        childPropertyDefinition, (ComplexCmsProperty) childComplexContentObjectProperty,
                        childComplexPropertyNode);

                //In case child property has no child on its own saved,  node must be removed as well
                String childComplexPropertyNodeIdentifier = context.getCmsRepositoryEntityUtils()
                        .getCmsIdentifier(childComplexPropertyNode);

                //If no child has been saved remove it 
                if (!childComplexCmsPropertyPopulateTask.atLeastOneChildPropertySaved()) {
                    existingChildComplexNodes.put(childComplexPropertyNodeIdentifier, childComplexPropertyNode);
                    childComplexContentObjectProperty.setId(null);

                    if (childComplexContentObjectProperty instanceof LazyCmsProperty) {
                        ((LazyCmsProperty) childComplexContentObjectProperty).setPropertyContainerNodeUUID(null);
                    }

                    //Tree may contain empty nodes and properties but there are some exception messages
                    //Keep them if child is mandatory
                    if (complexPropertyDefinition.isMandatory() && CollectionUtils.isNotEmpty(
                            childComplexCmsPropertyPopulateTask.getExceptionsForMandatoryProperties())) {
                        this.exceptionsForMandatoryProperties
                                .addAll(childComplexCmsPropertyPopulateTask.getExceptionsForMandatoryProperties());
                    }

                } else {
                    //Remove from cache so that this node will not be deleted
                    if (StringUtils.isNotBlank(childComplexContentObjectPropertyId)) {
                        existingChildComplexNodes.remove(childComplexContentObjectPropertyId);
                    }

                    if (childComplexContentObjectProperty instanceof CmsPropertyIndexable) {
                        indicesForNodesToBePersisted.put(childComplexPropertyNode,
                                ((CmsPropertyIndexable) childComplexContentObjectProperty).getIndex()); //Index is zero based
                    }

                }
            }
        }

        //Remove all nodes that had not been found only if properties have been loaded
        if (!existingChildComplexNodes.isEmpty() && childComplexCmsPropertyLoaded) {
            removeNodesThatCorrespondToUnmatchComplexCmsProperties(existingChildComplexNodes,
                    childPropertyDefinition);
        }

        //only meaningful for multi valued complex properties where we want to keep insertion order
        if (!indicesForNodesToBePersisted.isEmpty() && indicesForNodesToBePersisted.size() > 1) {

            for (Entry<Node, Integer> indexPerNode : indicesForNodesToBePersisted.entrySet()) {
                Node childNode = indexPerNode.getKey();

                Integer newIndex = indexPerNode.getValue();

                childNode.setProperty(CmsBuiltInItem.Order.getJcrName(), newIndex.longValue());

            }
        }

    }

    private Node createNewNodeForChildComplexCmsProperty(String childPropertyName,
            ComplexCmsPropertyDefinition childPropertyDefinition, Map<String, Node> existingChildComplexNodes,
            CmsProperty<?, ?> childComplexContentObjectProperty, boolean useProvidedId) throws RepositoryException {
        Node childComplexPropertyNode;
        //Child Property is new. Check if this property is single-value but there are
        //already more than one nodes with the same name. This case should never happen
        //but perform check anyway
        if (!childPropertyDefinition.isMultiple() && existingChildComplexNodes.size() > 1) {
            StringBuilder pathOfNodes = new StringBuilder("");
            for (Node existingNode : existingChildComplexNodes.values()) {
                pathOfNodes.append("\n" + existingNode.getPath());
            }

            throw new CmsException("Try to add new single value complex property " + childPropertyName + " "
                    + " but there is already at least two jcr node with the same name " + pathOfNodes.toString()
                    + " Parent Node " + complexPropertyNode.getPath());
        }

        //Create new node for complex property
        childComplexPropertyNode = JcrNodeUtils.addNodeForComplexCmsProperty(complexPropertyNode,
                childComplexContentObjectProperty.getName());

        context.getCmsRepositoryEntityUtils().createCmsIdentifier(childComplexPropertyNode,
                childComplexContentObjectProperty, useProvidedId);

        //Also append UUIDs necessary for lazy loading
        ((LazyCmsProperty) childComplexContentObjectProperty)
                .setPropertyContainerNodeUUID(childComplexPropertyNode.getIdentifier());
        ((LazyCmsProperty) childComplexContentObjectProperty).setContentObjectNodeUUID(contentObjectNodeUUID);

        return childComplexPropertyNode;
    }

    private void removeNodesThatCorrespondToUnmatchComplexCmsProperties(
            Map<String, Node> childComplexNodesToBeRemoved, ComplexCmsPropertyDefinition childPropertyDefinition)
            throws Exception {
        if (MapUtils.isNotEmpty(childComplexNodesToBeRemoved)) {
            ComplexCmsPropertyNodeRemovalVisitor childComplexCmsPropertyNodeRemovalVisitor = prototypeFactory
                    .newComplexCmsPropertyNodeRemovalVisitor();
            childComplexCmsPropertyNodeRemovalVisitor.setParentNode(complexPropertyNode, null);
            childComplexCmsPropertyNodeRemovalVisitor.setSession(session);

            for (Node childComplexNodeToBeRemoved : childComplexNodesToBeRemoved.values()) {

                childComplexCmsPropertyNodeRemovalVisitor.addChildNodeToBeDeleted(childComplexNodeToBeRemoved);

                childPropertyDefinition.accept(childComplexCmsPropertyNodeRemovalVisitor);

                childComplexNodeToBeRemoved.remove();
            }
        }
    }

    private PopulateComplexCmsProperty populateChildComplexCmsProperty(
            ComplexCmsPropertyDefinition childPropertyDefinition,
            ComplexCmsProperty childComplexContentObjectProperty, Node childPropertyNode) throws Exception {

        PopulateComplexCmsProperty populateTask = prototypeFactory.newPopulateComplexCmsProperty();

        populateTask.setComplexProperty(childComplexContentObjectProperty);
        populateTask.setComplexPropertyDefinition(childPropertyDefinition);
        populateTask.setSaveMode(saveMode);
        populateTask.setSession(session);
        populateTask.setComplexPropertyNode(childPropertyNode);
        populateTask.setContentObjectNodeUUID(contentObjectNodeUUID);
        populateTask.setContext(context);
        populateTask.populate();

        return populateTask;
    }

    private Map<String, Node> retrieveExistingChildComplexNodesWithPath(String childPropertyName)
            throws RepositoryException {
        Map<String, Node> existingNodes = new HashMap<String, Node>();

        NodeIterator nodes = complexPropertyNode.getNodes(childPropertyName);
        if (complexPropertyNode.hasNode(childPropertyName)) {
            while (nodes.hasNext()) {
                Node nextNode = nodes.nextNode();
                if (context.getCmsRepositoryEntityUtils().hasCmsIdentifier(nextNode)) {
                    existingNodes.put(context.getCmsRepositoryEntityUtils().getCmsIdentifier(nextNode), nextNode);
                } else {
                    throw new CmsException(
                            "Jcr node " + nextNode.getPath() + " does not have an identifier as expected");
                }
            }
        }

        return existingNodes;

    }

    private void populateSimpleChildProperty(String childPropertyName,
            SimpleCmsPropertyDefinition childPropertyDefinition) throws Exception {

        long start = System.currentTimeMillis();

        //Get Simple Property
        CmsProperty<?, ?> simpleCmsProperty = complexProperty.getChildProperty(childPropertyName);

        if (simpleCmsProperty == null) {
            //Normally this should never happen
            throw new CmsException("Complex Content Object Property " + complexPropertyNode.getPath()
                    + " does not contain mandatory property " + childPropertyName
                    + ". Method ComplexCmsProperty#isChildPropertyLoaded "
                    + "returned 'true' but method ComplexCmsProperty#getChildProperty return null...");
        } else {
            PopulateSimpleCmsProperty populateSimpleProperty = prototypeFactory.newPopulateSimpleCmsProperty();
            populateSimpleProperty.setCmsPropertyToBeSaved((SimpleCmsProperty) simpleCmsProperty);
            populateSimpleProperty.setPropertyContainerNode(complexPropertyNode);
            populateSimpleProperty.setPropertyDefinition(childPropertyDefinition);
            populateSimpleProperty.setPropertyHasBeenLoadedAndRemoved(((ComplexCmsPropertyImpl) complexProperty)
                    .cmsPropertyHasBeenLoadedAndRemoved(childPropertyName));
            populateSimpleProperty.setSaveMode(saveMode);
            populateSimpleProperty.setContext(context);
            populateSimpleProperty.setSession(session);

            populateSimpleProperty.populate();

            logger.debug("Simple child Property {} populated in  {}", childPropertyDefinition.getFullPath(),
                    DurationFormatUtils.formatDuration(System.currentTimeMillis() - start, "HH:mm:ss.SSSSSS"));

        }
    }

    private void checkDefinitionWithProperty() {

        if (!complexProperty.getName().equals(complexPropertyDefinition.getName())) {
            throw new CmsException("Content object property name " + complexProperty.getName()
                    + " does not match with definition name " + complexPropertyDefinition.getName());
        }

    }
}