org.betaconceptframework.astroboa.model.impl.ComplexCmsPropertyImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.betaconceptframework.astroboa.model.impl.ComplexCmsPropertyImpl.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.model.impl;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.StringUtils;
import org.betaconceptframework.astroboa.api.model.BinaryChannel;
import org.betaconceptframework.astroboa.api.model.CalendarProperty;
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.ContentObject;
import org.betaconceptframework.astroboa.api.model.ObjectReferenceProperty;
import org.betaconceptframework.astroboa.api.model.SimpleCmsProperty;
import org.betaconceptframework.astroboa.api.model.StringProperty;
import org.betaconceptframework.astroboa.api.model.Topic;
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.Localization;
import org.betaconceptframework.astroboa.api.model.definition.SimpleCmsPropertyDefinition;
import org.betaconceptframework.astroboa.api.model.exception.CmsException;
import org.betaconceptframework.astroboa.api.model.io.ResourceRepresentationType;
import org.betaconceptframework.astroboa.api.model.query.render.RenderProperties;
import org.betaconceptframework.astroboa.context.AstroboaClientContextHolder;
import org.betaconceptframework.astroboa.model.impl.definition.ComplexCmsPropertyDefinitionImpl;
import org.betaconceptframework.astroboa.model.lazy.LazyLoader;
import org.betaconceptframework.astroboa.util.CmsConstants;
import org.betaconceptframework.astroboa.util.DateUtils;
import org.betaconceptframework.astroboa.util.PropertyPath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author Gregory Chomatas (gchomatas@betaconcept.com)
 * @author Savvas Triantafyllou (striantafyllou@betaconcept.com)
 * 
 */
public class ComplexCmsPropertyImpl<D extends ComplexCmsPropertyDefinition, P extends ComplexCmsProperty<? extends ComplexCmsPropertyDefinition, ? extends ComplexCmsProperty<?, ?>>>
        extends CmsPropertyImpl<D, P>
        implements ComplexCmsProperty<D, P>, LazyCmsProperty, CmsPropertyIndexable, Serializable {

    /**
     * 
     */
    private static final long serialVersionUID = 2226744130618674841L;

    private Map<String, List<CmsProperty<?, ?>>> properties;
    private int index;

    /* When saving content object properties back to JCR repository, all 
    * properties which contain no values are removed. Due to lazy loading mechanism
    * properties are loaded only when requested. 
    * 
    * Thus, when populating a property to repository and methods getChildProperty or 
    * getChildProeprtyList return null or empty list we cannot tell whether the specific
    * property has not been loaded at all or has been loaded and then removed.
    * 
    * This list contains complex cms property names which have been loaded and then 
    * removed.
     * 
    * 
    */
    private List<String> cmsPropertyNamesWhichHaveBeenLoadedAndRemoved = new ArrayList<String>();

    //properties used while rendering child properties
    //This is the UUID of the jcr node which represents this complex cms property
    private String propertyContainerNodeUUID;

    //This is the UUID of the jcr node which represents the content object where this property belongs
    private String contentObjectNodeUUID;

    //These are the render properties used to render this property.
    //These settings (if any) will be used to render its children
    private RenderProperties renderProperties;

    public ComplexCmsPropertyImpl() {

        super();

        properties = new TreeMap<String, List<CmsProperty<?, ?>>>();

    }

    @Override
    public void setPropertyDefinition(D propertyDefinition) {
        super.setPropertyDefinition(propertyDefinition);

        if (this.propertyDefinition != null) {

            if (this.propertyDefinition.getValueType() != ValueType.Complex)
                throw new CmsException("ComplexCmsProperty " + this.getFullPath()
                        + " Incompatible value types. Definition " + this.propertyDefinition.getValueType()
                        + " , Complex Cms Property : " + getValueType());
        }

    }

    public void setIndex(int index) {
        this.index = index;

        resetPaths();

    }

    /**
     * Returns the index of this property in its parent list
     * This is defined during property creation
     * @return
     */
    public int getIndex() {
        return index;
    }

    public Map<String, List<CmsProperty<?, ?>>> getChildProperties() {

        return properties;
    }

    public List<CmsProperty<?, ?>> getChildPropertyList(String propertyPath) {
        if (StringUtils.isBlank(propertyPath)) {
            return null;
        }

        //Property path should not contain index in the last property
        if (propertyPath.endsWith("]")) {
            throw new CmsException("ComplexCmsProperty " + this.getFullPath() + " Property Path " + propertyPath
                    + " contains index. "
                    + "Use ComplexCmsProperty.getChildProperty() or ContentObject.getCmsProperty() instead");
        }

        CmsPropertyDefinition childPropertyDefinition = getChildPropertyDefinition(propertyPath);

        if (childPropertyDefinition == null) {
            return null;
        }

        if (childPropertyDefinition.getValueType() != ValueType.Complex || !childPropertyDefinition.isMultiple()) {
            throw new CmsException("Property " + propertyPath
                    + " is not a multi value complex property.Use method ComplexCmsProperty.getChildProperty() or "
                    + "ContentObject.getCmsProperty() instead");
        }

        Object property = getProperty(propertyPath, true);
        if (property == null)
            return null;
        else
            return (List<CmsProperty<?, ?>>) property;
    }

    /**
     * Return Content Object Property for the specified path. Path has the form
     * of Profile.subject[1] Where not specified (e.g. Profile) index 0 is
     * considered
     * 
     * @param propertyPath
     * @return
     */
    public CmsProperty<?, ?> getChildProperty(String propertyPath) {

        if (StringUtils.isBlank(propertyPath))
            return null;

        return (CmsProperty<?, ?>) getProperty(propertyPath, false);
    }

    public boolean isChildPropertyLoaded(String childPropertyPath) {

        return isChildLoaded(childPropertyPath);

    }

    private boolean isChildLoaded(String childPropertyPath) {

        //Check if any child is loaded
        if (properties == null || MapUtils.isEmpty(properties)) {
            return false;
        }

        if (childPropertyPath == null) {
            return false;
        }

        PropertyPath path = new PropertyPath(childPropertyPath);

        String childPropertyName = path.getPropertyName();
        int childPropertyIndex = path.getPropertyIndex();

        if (!properties.containsKey(childPropertyName)) {
            return false;
        }

        List<CmsProperty<?, ?>> childProperties = properties.get(childPropertyName);

        //This complex cms property contains property with childPropertyName
        //Check to see if must go any further

        //Go further if there is more in path
        if (path.getPropertyDescendantPath() != null) {
            //Get Child Definition
            CmsPropertyDefinition childPropertyDefinition = getChildPropertyDefinition(childPropertyName);

            if (childPropertyDefinition == null
                    || !(childPropertyDefinition instanceof ComplexCmsPropertyDefinition))
                throw new CmsException(
                        "ComplexCmsProperty " + this.getFullPath() + " Child Property " + childPropertyName
                                + " is not a complex cms property. Unable to check the rest of property path "
                                + path.getPropertyDescendantPath());

            //If property is multiple but no index is provided then throw an exception
            if (childPropertyIndex == PropertyPath.NO_INDEX) {
                path.setPropertyIndex(0);
            }

            //Check if index is inside bounds
            if (path.isInListIndex(childProperties.size())) {
                //Continue with descendant path
                return ((ComplexCmsProperty<?, ?>) childProperties.get(path.getPropertyIndex()))
                        .isChildPropertyLoaded(path.getPropertyDescendantPath());
            } else {
                return false;
            }
        } else {
            if (childProperties.size() == 0) {
                //Probably have been loaded but removed
                //In this case we assume that properties are loaded
                //otherwise values will be reloaded
                return true;
            }

            //Reached the end of path. Check for index
            if (childPropertyIndex == PropertyPath.NO_INDEX) {
                path.setPropertyIndex(0);
            }

            //Check if index provided is between list bounds
            return path.isInListIndex(childProperties.size());
        }
    }

    public boolean isChildPropertyDefined(String propertyPath) {
        boolean isPropertyDefined = false;

        if (getPropertyDefinition() != null)
            isPropertyDefined = getPropertyDefinition().hasChildCmsPropertyDefinition(propertyPath);

        return isPropertyDefined;

    }

    public ValueType getValueType() {
        return ValueType.Complex;
    }

    private CmsProperty createNewCmsProperty(CmsPropertyDefinition propertyDefinition, String propertyName) {
        CmsProperty newProperty = newCmsProperty(propertyDefinition.getValueType());

        if (newProperty instanceof SimpleCmsProperty)
            ((SimpleCmsProperty) newProperty)
                    .setPropertyDefinition((SimpleCmsPropertyDefinition) propertyDefinition);
        else if (newProperty instanceof ComplexCmsProperty) {
            //In case definition refers to its parent
            //all its children must be initialized
            ((ComplexCmsPropertyDefinitionImpl) propertyDefinition)
                    .checkIfRecursiveAndCloneParentChildDefinitions();
            ((ComplexCmsProperty) newProperty)
                    .setPropertyDefinition((ComplexCmsPropertyDefinition) propertyDefinition);
        }

        return newProperty;
    }

    private CmsProperty<?, ?> newCmsProperty(ValueType valueType) {

        CmsProperty newProperty = null;

        switch (valueType) {
        case Binary:
            newProperty = new BinaryPropertyImpl();
            break;
        case Boolean:
            newProperty = new BooleanPropertyImpl();
            break;
        case Date:
            newProperty = new CalendarPropertyImpl();
            break;
        case Complex: {
            newProperty = new ComplexCmsPropertyImpl();
            break;
        }
        case ObjectReference:
            newProperty = new ObjectReferencePropertyImpl();
            break;
        case Double:
            newProperty = new DoublePropertyImpl();
            break;
        case Long:
            newProperty = new LongPropertyImpl();
            break;
        case String:
            newProperty = new StringPropertyImpl();
            break;
        case TopicReference:
            newProperty = new TopicReferencePropertyImpl();
            break;

        default:
            return null;
        }

        if (newProperty != null) {
            ((CmsRepositoryEntityImpl) newProperty).setAuthenticationToken(authenticationToken);
        }

        return newProperty;
    }

    public CmsProperty<?, ?> createNewChildCmsPropertyTemplate(String propertyPath,
            boolean createPropertyOnlyIfComplexAndMultiple) {

        try {
            final Logger logger = LoggerFactory.getLogger(getClass());

            logger.debug("Creating new property template for property {}", propertyPath);

            //Get the first property and its index from property path
            PropertyPath path = new PropertyPath(propertyPath);

            String firstPropertyNameInPath = path.getPropertyName();
            int firstPropertyIndexInPath = path.getPropertyIndex();
            String firstPropertyPathWithIndex = path.getPropertyNameWithIndex();

            String restPropertyPath = path.getPropertyDescendantPath();
            boolean shouldContinue = StringUtils.isNotBlank(restPropertyPath);

            //Obtain first property definition. if none is found then an exception is thrown
            CmsPropertyDefinition firstPropertyDefinition = getChildPropertyDefinition(firstPropertyNameInPath);

            if (firstPropertyDefinition == null) {
                logger.debug(
                        "Found no definition for property {} therefore property template for property {} can not be created",
                        firstPropertyNameInPath, propertyPath);
                return null;
            }

            //Check if first property is loaded
            if (!properties.containsKey(firstPropertyNameInPath)) {

                //First property in path does not exist. Create it only if there is no index defined
                //or index is 0 which refer to the first property
                if (firstPropertyIndexInPath != PropertyPath.NO_INDEX && firstPropertyIndexInPath != 0) {
                    throw new CmsException("ComplexCmsProperty " + this.getFullPath() + " New property '"
                            + propertyPath + "' could not be created under property '" + this.getFullPath()
                            + "'. Intermediate property '" + firstPropertyNameInPath + "' path contains index '"
                            + firstPropertyIndexInPath + "'");
                } else {
                    //Create new property
                    CmsProperty<?, ?> newCmsProperty = createNewCmsProperty(firstPropertyDefinition,
                            firstPropertyNameInPath);
                    addNewProperty(firstPropertyNameInPath, firstPropertyDefinition, newCmsProperty);

                    if (shouldContinue) {
                        logger.debug("Created template for property {} and continue for property {}",
                                firstPropertyNameInPath, restPropertyPath);
                        return continueInAddingANewCmsPropertyTemplate(propertyPath, firstPropertyNameInPath,
                                restPropertyPath, newCmsProperty);
                    } else {
                        //Reached the end of the path.
                        logger.debug("Created template for property {} and reached the end of the path",
                                firstPropertyNameInPath);
                        return newCmsProperty;
                    }
                }
            } else {

                //First property is loaded.

                if (shouldContinue) {

                    logger.debug("Property {} exists. Continue building template for property {}",
                            firstPropertyNameInPath, restPropertyPath);

                    //Must find the first property and then continue cms property creation
                    //under that property
                    CmsProperty<?, ?> firstProperty = (CmsProperty<?, ?>) getProperty(firstPropertyPathWithIndex,
                            false);

                    return continueInAddingANewCmsPropertyTemplate(propertyPath, firstPropertyNameInPath,
                            restPropertyPath, firstProperty);
                } else {
                    //Should not continue.
                    //Create a new template only if definition is a complex definition
                    //and a multiple one or property has been loaded , removed and recreated without being saved first 
                    //because at this point a cms property already exists
                    if (cmsPropertyNamesWhichHaveBeenLoadedAndRemoved.contains(firstPropertyNameInPath)
                            || !createPropertyOnlyIfComplexAndMultiple
                            || (firstPropertyDefinition instanceof ComplexCmsPropertyDefinition
                                    && firstPropertyDefinition.isMultiple())

                    ) {

                        logger.debug(
                                "Property {} is a multiple complex property. A new property instance will be created",
                                firstPropertyNameInPath);

                        CmsProperty<?, ?> newCmsProperty = createNewCmsProperty(firstPropertyDefinition,
                                firstPropertyNameInPath);
                        addNewProperty(firstPropertyNameInPath, firstPropertyDefinition, newCmsProperty);

                        return newCmsProperty;
                    } else {
                        throw new CmsException("Unable to create a new template for property '" + propertyPath
                                + "'. Property is " + firstPropertyDefinition.getValueType()
                                + (firstPropertyDefinition.isMultiple() ? " multiple " : " single valued")
                                + " and already exists.");
                    }
                }

            }
        } catch (CmsException e) {
            throw e;
        } catch (Throwable e) {
            throw new CmsException(e);
        }
    }

    private CmsProperty<?, ?> continueInAddingANewCmsPropertyTemplate(String propertyPath,
            String firstPropertyNameInPath, String restPropertyPath, CmsProperty<?, ?> intermediateCmsProperty) {

        if (!(intermediateCmsProperty instanceof ComplexCmsProperty<?, ?>)) {
            throw new CmsException("ComplexCmsProperty " + this.getFullPath() + " New property '" + propertyPath
                    + "' could not be created under property '" + this.getFullPath() + "'. Intermediate property '"
                    + firstPropertyNameInPath + "' is not a complex cms property");
        } else {
            return ((ComplexCmsPropertyImpl<?, ?>) intermediateCmsProperty)
                    .createNewChildCmsPropertyTemplate(restPropertyPath, false);
        }
    }

    public void addNewProperty(String propertyName, CmsPropertyDefinition propertyDefinition,
            CmsProperty newProperty) throws Exception {

        //Add new CmsProperty template
        if (!properties.containsKey(propertyName))
            properties.put(propertyName, new ArrayList<CmsProperty<?, ?>>());

        //Check that new property could be created according to its definition
        //Get all properties already created for this definition
        List<CmsProperty<?, ?>> propertiesAlreadyCreated = properties.get(propertyName);

        //Perform check only if there is at least one property
        if (!CollectionUtils.isEmpty(propertiesAlreadyCreated) && !propertyDefinition.isMultiple()) {
            throw new CmsException(
                    "ComplexCmsProperty " + this.getFullPath() + " Attempt to create a new property '"
                            + propertyName + "' under complex cms property '" + this.getFullPath() + "'"
                            + " but this new property is single value and another property already exists.");
        }

        //Check id done. Add property
        propertiesAlreadyCreated.add(newProperty);

        //Set size in case this is a complex cms property
        if (newProperty instanceof CmsPropertyIndexable) {
            //CmsProperty is added always at the end of the list
            ((CmsPropertyIndexable) newProperty).setIndex(properties.get(propertyName).size() - 1);
        }

        if (newProperty instanceof LazyCmsProperty) {
            ((LazyCmsProperty) newProperty).setRenderProperties(renderProperties);
            ((LazyCmsProperty) newProperty).setContentObjectNodeUUID(contentObjectNodeUUID);

        }

        newProperty.setParent(this);

        //Set definition if not already there
        if (newProperty.getPropertyDefinition() == null) {
            if (newProperty instanceof SimpleCmsProperty) {
                ((SimpleCmsProperty) newProperty)
                        .setPropertyDefinition((SimpleCmsPropertyDefinition) propertyDefinition);
            } else if (newProperty instanceof ComplexCmsProperty) {
                //In case definition refers to its parent
                //all its children must be initialized
                ((ComplexCmsPropertyDefinitionImpl) propertyDefinition)
                        .checkIfRecursiveAndCloneParentChildDefinitions();
                ((ComplexCmsProperty) newProperty)
                        .setPropertyDefinition((ComplexCmsPropertyDefinition) propertyDefinition);
            }
        }

        //Set default value
        /*
         * Default value is not set at all during read
         * It is automatically set upon save or update
         * only when property is mandatory
         if (   newProperty instanceof SimpleCmsProperty &&
        ((SimpleCmsProperty)newProperty).hasNoValues() && 
        newProperty.getPropertyDefinition() != null && 
        newProperty.getPropertyDefinition() instanceof SimpleCmsPropertyDefinition){
               
           Object defaultValue = ((SimpleCmsPropertyDefinition)newProperty.getPropertyDefinition()).getDefaultValue();
               
           if (defaultValue != null){
        ((SimpleCmsProperty)newProperty).addSimpleTypeValue(defaultValue);
           }
         }
        */

        //Notify list
        if (cmsPropertyNamesWhichHaveBeenLoadedAndRemoved.contains(propertyName))
            cmsPropertyNamesWhichHaveBeenLoadedAndRemoved.remove(propertyName);

    }

    protected CmsPropertyDefinition getChildPropertyDefinition(String childPropertyName) {
        if (getPropertyDefinition() != null) {

            CmsPropertyDefinition childCmsPropertyDefinition = getPropertyDefinition()
                    .getChildCmsPropertyDefinition(childPropertyName);

            if (childCmsPropertyDefinition == null) {
                LoggerFactory.getLogger(getClass()).warn("Found no definition for child property {} in property {}",
                        childPropertyName, this.getFullPath());
                return null;
            }

            return childCmsPropertyDefinition;
        }

        throw new CmsException("Property " + this.getFullPath() + " is not complex.");
    }

    private synchronized Object getProperty(String propertyPath, boolean asList) {

        try {
            final Logger logger = LoggerFactory.getLogger(getClass());

            logger.debug("Requesting property {} {}", propertyPath, asList ? " as list " : "");

            PropertyPath path = new PropertyPath(propertyPath);

            String childPropertyName = path.getPropertyName();

            logger.debug("First property in path {} ", childPropertyName);

            if (childPropertyName == null) {
                logger.warn("Returning null as property name was null in provided path {}", propertyPath);
                return null;
            }

            boolean propertyHasBeenLoadedAndRemoved = cmsPropertyNamesWhichHaveBeenLoadedAndRemoved
                    .contains(childPropertyName)
                    && (path.getPropertyIndex() == 0 || path.getPropertyIndex() == PropertyPath.NO_INDEX)
                    && properties.containsKey(childPropertyName)
                    && CollectionUtils.isEmpty(properties.get(childPropertyName));

            if (properties == null || //Map has not been initialized 
                    (!properties.containsKey(childPropertyName)) || //Property has not been loaded at all
                    propertyHasBeenLoadedAndRemoved // Property has been removed  and user wants to load it again
            ) {

                //In cases where no UUID is provided for this property
                //there is no need to call Lazy Loader as it is equivalent
                //with calling addChildCmsProperty
                if (StringUtils.isBlank(getPropertyContainerUUID())) {
                    logger.debug(
                            "Property {}'s parent {} is a new complex property, therefore there is no need to go to repository to lazy load the former. A"
                                    + " new property will be created right away.",
                            childPropertyName, getName());

                    CmsPropertyDefinition childDefinition = getChildPropertyDefinition(childPropertyName);

                    //Extra check
                    if (childDefinition == null) {
                        logger.error("Found no definition for property {}",
                                PropertyPath.createFullPropertyPath(this.getFullPath(), childPropertyName));
                        return null;
                    }

                    throwExceptionIfPropertyIsComplexAndIndexIsOutOfBounds(path, childDefinition);

                    createNewChildCmsPropertyTemplate(childPropertyName, false);
                }

                //This is also the case for removed complex cms properties
                //without save taking place. Since user has removed property but did not save content object
                //and then requested property again, lazy loading must not take place as the 'removed' value
                //will be loaded.
                else if (propertyHasBeenLoadedAndRemoved) {
                    logger.debug(
                            "Property {} has been lazy loaded, has been removed using method ContentObject.removeCmsProperty or "
                                    + " method ComplexCmsProperty.removeChildProperty, content object was not saved and now user wants it back."
                                    + "Lazy loading will nto take place but rather a new property instance will be created as it is assumed that user "
                                    + "removed old instance and wants to create a new one ",
                            childPropertyName);

                    throwExceptionIfPropertyIsComplexAndIndexIsOutOfBounds(path, null);

                    createNewChildCmsPropertyTemplate(childPropertyName, false);
                } else {

                    LazyLoader lazyLoader = getLazyLoader();
                    if (lazyLoader == null) {
                        logger.error("ComplexCmsProperty " + this.getFullPath() + " Want to render child property "
                                + childPropertyName + " but lazy loader was not found. Parent path "
                                + this.getFullPath() + ".");
                        return null;
                    } else {

                        logger.debug("Lazy loading all instances for property {}", childPropertyName);

                        List<CmsProperty<?, ?>> childCmsProperties = lazyLoader.loadChildCmsProperty(
                                childPropertyName, getPropertyDefinition().getFullPath(),
                                getPropertyContainerUUID(), getContentObjectNodeUUID(), getRenderProperties(),
                                authenticationToken);

                        //Add new property
                        if (CollectionUtils.isNotEmpty(childCmsProperties)) {

                            CmsPropertyDefinition childDefinition = getChildPropertyDefinition(childPropertyName);

                            //Extra check
                            if (childDefinition == null) {
                                logger.error(
                                        "Found no definition for property {} although a property has been successfully loaded."
                                                + " This exception is caused probably when the following case take place : \n"
                                                + " Property {} is an aspect, values for this aspect have been successfully saved in the past but now for some \n"
                                                + " reason when content object is rendered, definition for this aspect is not available. Check outcome \n"
                                                + " of method contentObject.getComplexCmsRootProperty().getAspects() to see if property name {} appears. If not \n"
                                                + " then there is a bug in the procedure of saving aspect properties of a content object\n",
                                        new Object[] { PropertyPath.createFullPropertyPath(this.getFullPath(),
                                                childPropertyName), childPropertyName, childPropertyName });

                                return null;
                            }

                            for (CmsProperty childProperty : childCmsProperties) {
                                logger.debug("Registering new property for name {}", childPropertyName);

                                addNewProperty(childPropertyName, childDefinition, childProperty);
                            }
                        }
                    }
                }
            }

            if (properties.containsKey(childPropertyName)) {

                //Child Property Exists
                int childPropertyIndex = path.getPropertyIndex();

                //Get all child properties under childPropertyName
                List<CmsProperty<?, ?>> propertiesMatch = properties.get(childPropertyName);

                final String propertyDescendantPath = path.getPropertyDescendantPath();

                //Reached the end of the path ?
                if (propertyDescendantPath == null) {

                    //GetChilPropertyList() is called
                    if (asList) {

                        //No index is provided. Return all properties found for childPropertyName
                        if (childPropertyIndex == PropertyPath.NO_INDEX) {
                            logger.debug("Returning all instances for multiple complex property {}",
                                    childPropertyName);
                            return propertiesMatch;
                        } else {
                            // PropertyPath contains index (, ..profile[1]) but this is not permitted
                            // in getChildPropertyList(). Normally this code won't be reached
                            // See check performed in getChildPropertyList()
                            throw new CmsException("Found index in property " + path.getPropertyNameWithIndex()
                                    + ". Call getChildProperty() instead or getAllChildProperties() but without index in the last property.");
                        }

                    }
                    //GetChildProperty() is called
                    else {
                        //No index is provided. Default is 0.
                        if (childPropertyIndex == PropertyPath.NO_INDEX) {
                            path.setPropertyIndex(0);
                        }

                        //Check if index provided is between list bounds
                        if (path.isInListIndex(propertiesMatch.size())) {
                            int index = path.getPropertyIndex();

                            logger.debug("Returning property {} for index {}", childPropertyName, index);

                            return propertiesMatch.get(index);
                        } else {
                            //Special case.If index is greater than property list size by one then add a new property
                            //Method List.size() is not zero based whereas provided index is zero based.
                            if (childPropertyIndex == propertiesMatch.size()) {
                                //At this point, property has been loaded and user has provided an index
                                //which is greater than number of properties loaded, by one.
                                //If property is a simple property then user has wrongly provided,
                                //for example property 'title' is simple string thus title[1] is not accepted
                                //The only accepted case is that property is multiple and complex
                                CmsPropertyDefinition childPropertyDefinition = getChildPropertyDefinition(
                                        childPropertyName);

                                if (childPropertyDefinition.isMultiple()
                                        && childPropertyDefinition.getValueType() == ValueType.Complex) {
                                    return createNewChildCmsPropertyTemplate(childPropertyName, false);
                                } else {
                                    throw new CmsException("Property path " + path.getPropertyNameWithIndex() + " "
                                            + "contains index information which is not accepted because the property is of type "
                                            + childPropertyDefinition.getValueType() + "and it accepts "
                                            + (childPropertyDefinition.isMultiple() ? " multiple values"
                                                    : " only one value")
                                            + ". Try again without the index.");
                                }
                            } else {
                                throw new CmsException("Cannot get property '" + childPropertyName
                                        + "'. No such property found at index " + path.getPropertyIndex());
                            }
                        }
                    }
                }
                // We are in the middle of the path
                else {
                    //Get Child Definition
                    CmsPropertyDefinition childPropertyDefinition = getChildPropertyDefinition(childPropertyName);

                    if (childPropertyDefinition == null
                            || !(childPropertyDefinition instanceof ComplexCmsPropertyDefinition)) {
                        throw new CmsException("Child property " + path.getPropertyName()
                                + " is null or a simple cms property. Unable to continue with path "
                                + propertyDescendantPath);
                    }

                    //Child property path has no index
                    //Default is 0
                    if (childPropertyIndex == PropertyPath.NO_INDEX) {
                        //Lack of index means that index defaults to 0
                        path.setPropertyIndex(0);
                    }

                    //Check if index is inside bounds
                    if (path.isInListIndex(propertiesMatch.size())) {
                        logger.debug("Continue loading rest of property path {} under property {}",
                                propertyDescendantPath, childPropertyName);

                        //Continue with descendant path
                        return ((ComplexCmsPropertyImpl<?, ?>) propertiesMatch.get(path.getPropertyIndex()))
                                .getProperty(propertyDescendantPath, asList);

                        //if (asList)
                        //return ((ComplexCmsProperty<?,?>)propertiesMatch.get(path.getPropertyIndex())).getChildPropertyList(path.getPropertyDescendantPath());
                        //else
                        //return ((ComplexCmsProperty<?,?>)propertiesMatch.get(path.getPropertyIndex())).getChildProperty(path.getPropertyDescendantPath());

                    }
                    /* Special case: If index is greater than property list size by one then add a new property*/
                    else if (childPropertyIndex == propertiesMatch.size()) {
                        childPropertyDefinition = getChildPropertyDefinition(childPropertyName);

                        if (childPropertyDefinition.isMultiple()
                                && childPropertyDefinition.getValueType() == ValueType.Complex) {
                            ComplexCmsPropertyImpl<?, ?> childProperty = (ComplexCmsPropertyImpl<?, ?>) createNewChildCmsPropertyTemplate(
                                    childPropertyName, false);
                            if (path.getPropertyDescendantPath() != null) {
                                return childProperty.getProperty(path.getPropertyDescendantPath(), asList);
                            } else {
                                return childProperty;
                            }
                        } else {
                            throw new CmsException("Property path " + path.getPropertyNameWithIndex() + " "
                                    + "which corresponds to a simple property. In this case index is not allowed.");
                        }
                    } else {
                        throw new CmsException("Cannot get property " + propertyPath + "'. No property "
                                + childPropertyName + " found at index " + path.getPropertyIndex());
                    }
                }
            }

            return null;
        } catch (Throwable t) {
            LoggerFactory.getLogger(getClass()).error("", t);
            return null;
        }

    }

    private void throwExceptionIfPropertyIsComplexAndIndexIsOutOfBounds(PropertyPath path,
            CmsPropertyDefinition childDefinition) {
        if ((childDefinition == null
                || childDefinition.getValueType() == ValueType.Complex && childDefinition.isMultiple()) &&

                path.getPropertyIndex() > 0) {
            //Create a new property template only if index is greater than 0 then throw an exception
            throw new CmsException("Cannot create property " + path.getPropertyNameWithIndex()
                    + ". Property is a multiple complex property and no instance for index "
                    + (path.getPropertyIndex() - 1) + " has been created");

        }
    }

    public boolean removeChildProperty(String propertyPath) {

        PropertyPath path = new PropertyPath(propertyPath);

        String childPropertyName = path.getPropertyName();
        int childPropertyIndex = path.getPropertyIndex();

        if (childPropertyName == null) {
            return false;
        }

        if (properties != null) {

            if (!properties.containsKey(childPropertyName)) {
                //Property is not loaded.
                CmsPropertyDefinition childPropertyDefinition = getChildPropertyDefinition(propertyPath);

                if (childPropertyDefinition == null) {
                    return false;
                }

                //Load property first
                getProperty(propertyPath, childPropertyDefinition.getValueType() == ValueType.Complex
                        && childPropertyDefinition.isMultiple());
                return removeChildProperty(propertyPath);
            } else {

                CmsPropertyDefinition childPropertyDefinition = getChildPropertyDefinition(childPropertyName);

                if (childPropertyDefinition == null) {
                    return false;
                }

                //All CmsProperty instances found under childPropertyName
                List<CmsProperty<?, ?>> propertiesMatch = properties.get(childPropertyName);

                if (path.getPropertyDescendantPath() == null) {

                    if (childPropertyIndex == PropertyPath.NO_INDEX) {
                        //Remove all properties but do not remove
                        //entry with property name. This way
                        //property has been marked for removal
                        propertiesMatch.clear();

                        cmsPropertyNamesWhichHaveBeenLoadedAndRemoved.add(childPropertyName);

                        return true;
                    } else if (path.isInListIndex(propertiesMatch.size())) {

                        if (childPropertyDefinition.getValueType() == ValueType.Complex) {
                            //ComplexCmsProperty. Remove it from list and force others to renew their index
                            boolean successfulRemoval = (propertiesMatch.remove(childPropertyIndex) != null);
                            if (successfulRemoval) {
                                //Must notify all remained CmsProperties in list to change their index
                                int i = 0;
                                for (CmsProperty<?, ?> complexCmsProperty : propertiesMatch) {
                                    if (complexCmsProperty instanceof CmsPropertyIndexable) {
                                        ((CmsPropertyIndexable) complexCmsProperty).setIndex(i);
                                    }
                                    ((CmsPropertyImpl<?, ?>) complexCmsProperty).resetPaths();

                                    i++;
                                }
                            }
                            return successfulRemoval;

                        } else {

                            CmsProperty<?, ?> simpleCmsProperty = propertiesMatch.get(childPropertyIndex);

                            if (simpleCmsProperty != null) {
                                ((SimpleCmsProperty<?, ?, ?>) simpleCmsProperty).removeValues();
                            }
                        }
                    } else {
                        throw new CmsException("Cannot remove property " + propertyPath + " No property  "
                                + childPropertyName + " found at given index " + childPropertyIndex);

                    }
                } else {
                    if (childPropertyIndex == PropertyPath.NO_INDEX) {
                        if (childPropertyDefinition.isMultiple()
                                && childPropertyDefinition instanceof ComplexCmsPropertyDefinition) {
                            throw new CmsException("Must specifiy index for child property " + childPropertyName);
                        }

                        //Property is single value. Then set index to 0
                        return ((ComplexCmsProperty<?, ?>) propertiesMatch.get(0))
                                .removeChildProperty(path.getPropertyDescendantPath());
                    } else if (path.isInListIndex(propertiesMatch.size()))
                        return ((ComplexCmsProperty<?, ?>) propertiesMatch.get(childPropertyIndex))
                                .removeChildProperty(path.getPropertyDescendantPath());
                    else
                        // Do not know where to go
                        throw new CmsException("Cannot remove property " + propertyPath
                                + "'. No property found at index " + path.getPropertyIndex());
                }
            }
        }

        return false;
    }

    public boolean atLeastOneChilPropertyIsLoaded(String propertyPath) {
        return isChildLoaded(propertyPath);
    }

    public List<CmsProperty<?, ?>> getAllChildProperties(String propertyPath) {
        if (StringUtils.isBlank(propertyPath))
            return null;

        Object property = getProperty(propertyPath, true);
        if (property == null)
            return new ArrayList<CmsProperty<?, ?>>();
        else
            return (List<CmsProperty<?, ?>>) property;

    }

    public boolean cmsPropertyHasBeenLoadedAndRemoved(String childPropertyName) {
        return cmsPropertyNamesWhichHaveBeenLoadedAndRemoved != null
                && cmsPropertyNamesWhichHaveBeenLoadedAndRemoved.contains(childPropertyName);
    }

    public void setContentObjectNodeUUID(String contentObjectNodeUUID) {
        this.contentObjectNodeUUID = contentObjectNodeUUID;

    }

    /*
     * If not synchronized,  FindBugs create the following bug report
     * The fields of this class appear to be accessed inconsistently with respect to synchronization.  
     * This bug report indicates that the bug pattern detector judged that
     * The class contains a mix of locked and unlocked accesses,
     * At least one locked access was performed by one of the class's own methods, and
     * The number of unsynchronized field accesses (reads and writes) was no more than one third of all accesses, with writes being weighed twice as high as reads
     * A typical bug matching this bug pattern is forgetting to synchronize one of the methods in a class that is intended to be thread-safe.
     * You can select the nodes labeled "Unsynchronized access" to show the code locations where the detector believed that a field was accessed without synchronization.
     * Note that there are various sources of inaccuracy in this detector; for example, the detector cannot statically detect all situations in which a lock is held.  Also, even when the detector is accurate in distinguishing locked vs. unlocked accesses, the code in question may still be correct.The fields of this class appear to be accessed inconsistently with respect to synchronization.  This bug report indicates that the bug pattern detector judged that
     * The class contains a mix of locked and unlocked accesses,
     * At least one locked access was performed by one of the class's own methods, and
     * The number of unsynchronized field accesses (reads and writes) was no more than one third of all accesses, with writes being weighed twice as high as reads
     * A typical bug matching this bug pattern is forgetting to synchronize one of the methods in a class that is intended to be thread-safe.
     * You can select the nodes labeled "Unsynchronized access" to show the code locations where the detector believed that a field was accessed without synchronization.
     * Note that there are various sources of inaccuracy in this detector; for example, the detector cannot statically detect all situations in which a lock is held.  Also, even when the detector is accurate in distinguishing locked vs. unlocked accesses, the code in question may still be correct.
     * 
     */
    public synchronized void setPropertyContainerNodeUUID(String propertyContainerNodeUUID) {
        this.propertyContainerNodeUUID = propertyContainerNodeUUID;

    }

    public void setRenderProperties(RenderProperties renderProperties) {
        this.renderProperties = renderProperties;

    }

    public String getContentObjectNodeUUID() {
        return contentObjectNodeUUID;
    }

    public synchronized String getPropertyContainerUUID() {
        return propertyContainerNodeUUID;
    }

    public RenderProperties getRenderProperties() {
        return renderProperties;
    }

    //Override deserialization process to inject 
    //and contentService
    private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {

        //Deserialize bean normally
        ois.defaultReadObject();

        //Inject lazyLoader
        LazyLoader lazyLoader = getLazyLoader();

        if (lazyLoader != null) {

            //In case this is an instance of ComplexCmsRootProperty
            //its definition will be injected when readObject of this class runs
            //right after the end of this method
            if (!(this instanceof ComplexCmsRootProperty)) {
                lazyLoader.activateClientContextForAuthenticationToken(authenticationToken);
                setPropertyDefinition((D) lazyLoader.getDefinitionService().getCmsDefinition(
                        fullPropertyDefinitionPath, ResourceRepresentationType.DEFINITION_INSTANCE, false));
            }
        }
    }

    protected LazyLoader getLazyLoader() {
        return AstroboaClientContextHolder.getLazyLoaderForClient(authenticationToken);
    }

    @Override
    public String getPropertyLabel(String locale) {

        try {
            String propertyPathWhoseValueCorrespondsToLabel = getPropertyDefinition()
                    .getPropertyPathsWhoseValuesCanBeUsedAsALabel();

            if (StringUtils.isBlank(propertyPathWhoseValueCorrespondsToLabel)) {
                return null;
            }

            if (StringUtils.isBlank(locale)) {
                locale = Locale.ENGLISH.toString();
            }

            if (propertyPathWhoseValueCorrespondsToLabel.contains("{locale}")) {
                propertyPathWhoseValueCorrespondsToLabel = propertyPathWhoseValueCorrespondsToLabel
                        .replaceAll("\\{locale\\}", locale);
            }

            StringBuilder sb = new StringBuilder();

            String[] propertyPaths = propertyPathWhoseValueCorrespondsToLabel.split(",");

            boolean addComma = false;
            for (String propertyPath : propertyPaths) {

                String labelForPath = retrieveLabelForPath(locale, propertyPath.trim());
                if (StringUtils.isNotBlank(labelForPath)) {

                    if (addComma) {
                        sb.append(" , ");
                    } else {
                        addComma = true;
                    }

                    sb.append(labelForPath);
                }
            }

            return sb.toString();

        } catch (Exception e) {
            //Since we cannot have logger as Log4j is not serializable
            //we print stack trace to output
            e.printStackTrace();
            return null;
        }
    }

    private String retrieveLabelForPath(String locale, String propertyPathWhoseValueCorrespondsToLabel) {
        //Get property
        CmsProperty<?, ?> property = null;

        String[] paths = PropertyPath.splitPath(propertyPathWhoseValueCorrespondsToLabel);

        //For each of the path provided retrieve child property
        for (String path : paths) {
            if (property == null) {
                //Make sure that value exists, either inside this instance
                // or in repository. This way no new property will be created
                if (hasValueForChildProperty(path)) {
                    property = getChildProperty(path);
                } else {
                    //No point in continue. No value for property
                    property = null;
                    break;
                }
            } else {
                if (property.getValueType() == null) {
                    LoggerFactory.getLogger(getClass())
                            .warn("Property path " + propertyPathWhoseValueCorrespondsToLabel
                                    + " which is responsible to " + " provide label is invalid. Reached path "
                                    + property.getFullPath() + " but cannot continue to path " + path
                                    + " as this path corresponds to a " + " property with a null value type");
                    property = null;
                    break;
                } else {
                    if (property.getValueType() == ValueType.ObjectReference) {
                        if (((ObjectReferenceProperty) property).hasNoValues()) {
                            LoggerFactory.getLogger(getClass()).debug("Property path {} which is responsible to "
                                    + " provide label is invalid. Reached path {} but cannot continue to path {} as this path corresponds to a "
                                    + " content object property which has no values",
                                    new Object[] { propertyPathWhoseValueCorrespondsToLabel, property.getFullPath(),
                                            path });
                            return null;
                        } else {
                            property = ((ObjectReferenceProperty) property).getFirstValue().getCmsProperty(path);
                        }
                    } else if (property.getValueType() == ValueType.Complex) {
                        property = ((ComplexCmsProperty) property).getChildProperty(path);
                    } else {
                        LoggerFactory.getLogger(getClass())
                                .warn("Property path " + propertyPathWhoseValueCorrespondsToLabel
                                        + " which is responsible to " + " provide label is invalid. Reached path "
                                        + property.getFullPath() + " but cannot continue to path " + path
                                        + " as this path corresponds to a "
                                        + " property of value type other than ContentType or Complex : "
                                        + property.getValueType());
                        return null;
                    }
                }
            }
        }

        if (property == null || property.getValueType() == null || property.getValueType() == ValueType.ContentType
                || property.getValueType() == ValueType.Complex
                || ((SimpleCmsProperty<?, ?, ?>) property).hasNoValues()) {
            return null;
        }

        Object value = ((SimpleCmsProperty<?, ?, ?>) property).getFirstValue();

        if (value == null) {
            return null;
        }

        switch (property.getValueType()) {
        case Boolean:
            return ((Boolean) value).toString();

        case Date:
            if (((CalendarProperty) property).getPropertyDefinition() != null
                    && !((CalendarProperty) property).getPropertyDefinition().isDateTime()) {
                return DateUtils.format(((Calendar) value), "dd/MM/yyyy");
            }
            return DateUtils.format(((Calendar) value), "dd/MM/yyyy HH:mm");

        case Long:
            return ((Long) value).toString();

        case Double:
            return ((Double) value).toString();

        case String:
            return (String) value;

        case Binary:
            if (((BinaryChannel) value).getSourceFilename() != null) {
                return ((BinaryChannel) value).getSourceFilename();
            } else {
                return ((BinaryChannel) value).getName();
            }

        case ObjectReference:
            if (((ContentObject) value).getComplexCmsRootProperty().isChildPropertyDefined("profile.title")) {
                StringProperty profileTitle = (StringProperty) ((ContentObject) value)
                        .getCmsProperty("profile.title");

                if (profileTitle != null && !profileTitle.hasNoValues()) {
                    return profileTitle.getSimpleTypeValue();
                }
            }

            //No profile.title found
            String localizedLabelForLocale = retrieveLocalizedLabelForLocale(locale,
                    ((ContentObject) value).getTypeDefinition().getDisplayName());

            if (StringUtils.isEmpty(localizedLabelForLocale)) {
                return ((ContentObject) value).getSystemName();
            }

            return localizedLabelForLocale;

        case TopicReference: {

            String topicLocalizedLabelForLocale = ((Topic) value).getLocalizedLabelForLocale(locale);

            if (StringUtils.isEmpty(topicLocalizedLabelForLocale)) {
                return ((Topic) value).getName();
            }

            return topicLocalizedLabelForLocale;
        }

        default:
            return null;
        }
    }

    private String retrieveLocalizedLabelForLocale(String locale, Localization localization) {
        if (localization == null || !localization.hasLocalizedLabels()) {
            return null;
        }

        String localizedLabelForLocale = localization.getLocalizedLabelForLocale(locale);

        if (StringUtils.isBlank(localizedLabelForLocale)
                && !StringUtils.equals(Locale.ENGLISH.toString(), locale)) {
            //Fall back for English
            localizedLabelForLocale = localization.getLocalizedLabelForLocale(Locale.ENGLISH.toString());
        }

        return localizedLabelForLocale;
    }

    public void clearCmsPropertyNameWhichHaveBeenLoadedAndRemovedList() {

        //Clear current list
        if (CollectionUtils.isNotEmpty(cmsPropertyNamesWhichHaveBeenLoadedAndRemoved)) {
            //All clear map with values
            for (String cmsPropertyNameWhichHaveBeenLoadedAndRemoved : cmsPropertyNamesWhichHaveBeenLoadedAndRemoved) {
                if (cmsPropertyNameWhichHaveBeenLoadedAndRemoved == null) {
                    continue;
                }

                if (properties.containsKey(cmsPropertyNameWhichHaveBeenLoadedAndRemoved)
                        && CollectionUtils.isEmpty(properties.get(cmsPropertyNameWhichHaveBeenLoadedAndRemoved))) {
                    properties.remove(cmsPropertyNameWhichHaveBeenLoadedAndRemoved);
                }
            }

            cmsPropertyNamesWhichHaveBeenLoadedAndRemoved.clear();
        }

        //Clear list for all loaded children
        for (List<CmsProperty<?, ?>> childCmsProperties : properties.values()) {
            for (CmsProperty<?, ?> childProperty : childCmsProperties) {
                if (childProperty instanceof ComplexCmsPropertyImpl) {
                    ((ComplexCmsPropertyImpl<?, ?>) childProperty)
                            .clearCmsPropertyNameWhichHaveBeenLoadedAndRemovedList();
                } else {
                    break;
                }
            }
        }

    }

    @Override
    public CmsProperty<?, ?> createNewValueForMulitpleComplexCmsProperty(String relativePath) {

        //Other values of complex property already exist
        if (isChildLoaded(relativePath)) {
            //Retrieve list with existed complex properties for provided path
            List<CmsProperty<?, ?>> propertyList = getChildPropertyList(relativePath);

            if (propertyList == null) {
                //Something went wrong and list could not be initialized
                return null;
            } else {
                //Size of list is not zero based
                int propertyIndex = propertyList.size();

                return getChildProperty(relativePath + "[" + propertyIndex + "]");

            }
        } else {
            //Property has not been loaded. Calling getChildPropertyList will
            //cause a new complex property to be created if none is found in the 
            //repository
            List<CmsProperty<?, ?>> propertyList = getChildPropertyList(relativePath);

            if (propertyList == null) {
                //Something went wrong and list could not be initialized
                return null;
            } else {
                if (propertyList.size() == 1) {
                    //There is one complex property in the list. If it has no Id
                    //then it has just been created
                    if (propertyList.get(0).getId() == null) {
                        //Return newly created complex property
                        return propertyList.get(0);
                    }
                }

                return getChildProperty(relativePath + "[" + propertyList.size() + "]");

            }
        }
    }

    @Override
    public boolean swapChildPropertyValues(String relativePropertyPath, int from, int to) {

        if (relativePropertyPath == null || relativePropertyPath.endsWith(CmsConstants.RIGHT_BRACKET)) {
            return false;
        }

        if (from == to || from < 0 || to < 0) {
            return false;
        }

        //Check its definition
        CmsPropertyDefinition childPropertyDefinition = getChildPropertyDefinition(relativePropertyPath);

        if (childPropertyDefinition == null || !childPropertyDefinition.isMultiple()) {
            return false;
        }

        if (ValueType.Complex == childPropertyDefinition.getValueType()) {
            List<CmsProperty<?, ?>> propertyList = getChildPropertyList(relativePropertyPath);

            try {
                Collections.swap(propertyList, from, to);
                //Now fix indices
                for (CmsProperty<?, ?> childProperty : propertyList) {
                    if (childProperty instanceof CmsPropertyIndexable) {
                        ((CmsPropertyIndexable) childProperty).setIndex(propertyList.indexOf(childProperty));
                    }
                }

                return true;
            } catch (Exception e) {
                LoggerFactory.getLogger(getClass()).error("", e);
                return false;
            }
        } else {
            //Property is simple value
            CmsProperty<?, ?> property = getChildProperty(relativePropertyPath);

            if (property instanceof SimpleCmsProperty) {
                return ((SimpleCmsProperty) property).swapValues(from, to);
            } else {
                LoggerFactory.getLogger(getClass()).warn(
                        "Property {}'s definition is value type {} but property instance is of type {}",
                        new Object[] { relativePropertyPath, childPropertyDefinition.getValueType(),
                                property.getClass().getName() });
                return false;
            }
        }
    }

    @Override
    public boolean changePositionOfChildPropertyValue(String relativePropertyPath, int from, int to) {

        if (relativePropertyPath == null || relativePropertyPath.endsWith(CmsConstants.RIGHT_BRACKET)) {
            return false;
        }

        if (from == to || from < 0 || to < 0) {
            return false;
        }

        //Check its definition
        CmsPropertyDefinition childPropertyDefinition = getChildPropertyDefinition(relativePropertyPath);

        if (childPropertyDefinition == null || !childPropertyDefinition.isMultiple()) {
            return false;
        }

        if (ValueType.Complex == childPropertyDefinition.getValueType()) {
            List<CmsProperty<?, ?>> propertyList = getChildPropertyList(relativePropertyPath);

            if (from > propertyList.size() - 1 || to > propertyList.size()) {
                return false;
            }

            try {
                CmsProperty<?, ?> propertyToMove = propertyList.get(from);
                propertyList.add(to, propertyToMove);
                if (from > to) {
                    from++;
                }
                propertyList.remove(from);

                //Now fix indices
                for (CmsProperty<?, ?> childProperty : propertyList) {
                    if (childProperty instanceof CmsPropertyIndexable) {
                        ((CmsPropertyIndexable) childProperty).setIndex(propertyList.indexOf(childProperty));
                    }
                }

                return true;
            } catch (Exception e) {
                LoggerFactory.getLogger(getClass()).error("", e);
                return false;
            }
        } else {
            //Property is simple value
            CmsProperty<?, ?> property = getChildProperty(relativePropertyPath);

            if (property instanceof SimpleCmsProperty) {
                return ((SimpleCmsProperty<?, ?, ?>) property).changePositionOfValue(from, to);
            } else {
                LoggerFactory.getLogger(getClass()).warn(
                        "Property {}'s definition is value type {} but property instance is of type {}",
                        new Object[] { relativePropertyPath, childPropertyDefinition.getValueType(),
                                property.getClass().getName() });
                return false;
            }
        }
    }

    /**
     * 
     */
    public void clean() {

        if (properties != null) {
            for (List<CmsProperty<?, ?>> propList : properties.values()) {

                for (CmsProperty<?, ?> property : propList) {
                    ((CmsPropertyImpl) property).clean();
                }
            }
        }
        cmsPropertyNamesWhichHaveBeenLoadedAndRemoved.clear();

        setContentObjectNodeUUID(null);
        setPropertyContainerNodeUUID(null);

        setId(null);

    }

    public boolean hasValueForChildProperty(String propertyPath) {

        try {
            final Logger logger = LoggerFactory.getLogger(getClass());

            logger.debug("Checking existance for value of property {} ", propertyPath);

            PropertyPath path = new PropertyPath(propertyPath);

            String childPropertyName = path.getPropertyName();

            logger.debug("First property in path {} ", childPropertyName);

            if (childPropertyName == null) {
                logger.warn("Returning null as property name was null in provided path {}", propertyPath);
                return false;
            }

            boolean propertyHasBeenLoadedAndRemoved = cmsPropertyNamesWhichHaveBeenLoadedAndRemoved
                    .contains(childPropertyName)
                    && (path.getPropertyIndex() == 0 || path.getPropertyIndex() == PropertyPath.NO_INDEX)
                    && properties.containsKey(childPropertyName)
                    && CollectionUtils.isEmpty(properties.get(childPropertyName));

            if (properties == null || //Map has not been initialized 
                    (!properties.containsKey(childPropertyName)) || //Property has not been loaded at all
                    propertyHasBeenLoadedAndRemoved // Property has been removed  and user wants to load it again
            ) {

                //In cases where no UUID is provided for this property
                //there is no need to call Lazy Loader as it is equivalent
                //with calling addChildCmsProperty
                if (StringUtils.isBlank(getPropertyContainerUUID())) {
                    logger.debug(
                            "Property {}'s parent {} is a new complex property, therefore there is no need to go to repository to lazy load the former."
                                    + "No value exists for that property",
                            childPropertyName, getName());
                    return false;
                }

                //This is also the case for removed complex cms properties
                //without save taking place. Since user has removed property but did not save content object
                //and then checks for property value existence, lazy loading must not take place as the 'removed' value
                //will be loaded.
                else if (propertyHasBeenLoadedAndRemoved) {
                    logger.debug(
                            "Property {} has been lazy loaded, has been removed using method ContentObject.removeCmsProperty or "
                                    + " method ComplexCmsProperty.removeChildProperty, content object was not saved and now user checks for value existence."
                                    + "Lazy loading will nto take place and false is returned ",
                            childPropertyName);
                    return false;
                } else {

                    LazyLoader lazyLoader = getLazyLoader();
                    if (lazyLoader == null) {
                        logger.error("ComplexCmsProperty " + this.getFullPath()
                                + " Want to check for child property " + childPropertyName
                                + " but lazy loader was not found. Parent path " + this.getFullPath() + ".");
                        return false;
                    } else {

                        logger.debug("Check for property {}", childPropertyName);

                        return lazyLoader.hasValueForProperty(propertyPath, getPropertyContainerUUID(),
                                authenticationToken);
                    }
                }
            }

            if (properties.containsKey(childPropertyName)) {

                //Child Property Exists
                int childPropertyIndex = path.getPropertyIndex();

                //Get all child properties under childPropertyName
                List<CmsProperty<?, ?>> propertiesMatch = properties.get(childPropertyName);

                final String propertyDescendantPath = path.getPropertyDescendantPath();

                //Reached the end of the path ?
                if (propertyDescendantPath == null) {

                    //No index is provided. Default is 0.
                    if (childPropertyIndex == PropertyPath.NO_INDEX) {
                        path.setPropertyIndex(0);
                    }

                    //Check if index provided is between list bounds
                    if (path.isInListIndex(propertiesMatch.size())) {

                        int index = path.getPropertyIndex();

                        CmsProperty<?, ?> property = propertiesMatch.get(index);

                        if (property != null) {

                            logger.debug("Property {} for index {} exists.Examine if value exists",
                                    childPropertyName, index);

                            if (property.getValueType() == ValueType.Complex) {
                                return true;
                            } else {
                                if (property instanceof SimpleCmsProperty) {
                                    return ((SimpleCmsProperty) property).hasValues();
                                } else {
                                    logger.warn(
                                            "Property {} for index {} is of type {} which does not extend SimpleCmsProperty",
                                            new Object[] { childPropertyName, index, property.getValueType() });
                                    return false;
                                }
                            }
                        } else {
                            logger.warn(
                                    "Property {} for index {} has a null value. A CmsProperty instance should exist instead.",
                                    childPropertyName, path.getPropertyIndex());
                            return false;
                        }
                    } else {
                        return false;
                    }
                }
                // We are in the middle of the path
                else {
                    //Get Child Definition
                    CmsPropertyDefinition childPropertyDefinition = getChildPropertyDefinition(childPropertyName);

                    if (childPropertyDefinition == null
                            || !(childPropertyDefinition instanceof ComplexCmsPropertyDefinition)) {
                        throw new CmsException("Child property " + path.getPropertyName()
                                + " is null or a simple cms property. Unable to continue with path "
                                + propertyDescendantPath);
                    }

                    //Child property path has no index
                    //Default is 0
                    if (childPropertyIndex == PropertyPath.NO_INDEX) {
                        //Lack of index means that index defaults to 0
                        path.setPropertyIndex(0);
                    }

                    //Check if index is inside bounds
                    if (path.isInListIndex(propertiesMatch.size())) {
                        logger.debug(
                                "Continue chekcing value existence for the rest of property path {} under property {}",
                                propertyDescendantPath, childPropertyName);

                        //Continue with descendant path
                        return ((ComplexCmsPropertyImpl<?, ?>) propertiesMatch.get(path.getPropertyIndex()))
                                .hasValueForChildProperty(propertyDescendantPath);
                    } else {
                        return false;
                    }
                }
            }

            return false;
        } catch (Throwable t) {
            LoggerFactory.getLogger(getClass()).error("", t);
            return false;
        }

    }

    @Override
    public void resetPaths() {

        super.resetPaths();

        if (properties != null && !properties.isEmpty()) {
            for (List<CmsProperty<?, ?>> props : properties.values()) {

                for (CmsProperty<?, ?> prop : props) {
                    ((CmsPropertyImpl) prop).resetPaths();
                }
            }
        }

    }

    public List<String> getPathsOfPropertiesMarkedForRemoval() {
        if (cmsPropertyNamesWhichHaveBeenLoadedAndRemoved == null
                || cmsPropertyNamesWhichHaveBeenLoadedAndRemoved.isEmpty())
            return new ArrayList<String>();

        return Collections.unmodifiableList(cmsPropertyNamesWhichHaveBeenLoadedAndRemoved);

    }

}