org.betaconceptframework.astroboa.engine.model.lazy.local.LazyComplexCmsPropertyLoader.java Source code

Java tutorial

Introduction

Here is the source code for org.betaconceptframework.astroboa.engine.model.lazy.local.LazyComplexCmsPropertyLoader.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.model.lazy.local;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.PathNotFoundException;
import javax.jcr.Property;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Value;
import javax.jcr.ValueFormatException;
import javax.jcr.version.Version;
import javax.jcr.version.VersionHistory;
import javax.jcr.version.VersionIterator;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.betaconceptframework.astroboa.api.model.BinaryProperty;
import org.betaconceptframework.astroboa.api.model.CmsProperty;
import org.betaconceptframework.astroboa.api.model.CmsRepositoryEntity;
import org.betaconceptframework.astroboa.api.model.ComplexCmsProperty;
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.Topic;
import org.betaconceptframework.astroboa.api.model.ValueType;
import org.betaconceptframework.astroboa.api.model.definition.BinaryPropertyDefinition;
import org.betaconceptframework.astroboa.api.model.definition.CmsPropertyDefinition;
import org.betaconceptframework.astroboa.api.model.definition.ComplexCmsPropertyDefinition;
import org.betaconceptframework.astroboa.api.model.definition.ContentObjectTypeDefinition;
import org.betaconceptframework.astroboa.api.model.definition.SimpleCmsPropertyDefinition;
import org.betaconceptframework.astroboa.api.model.definition.StringPropertyDefinition;
import org.betaconceptframework.astroboa.api.model.exception.CmsException;
import org.betaconceptframework.astroboa.api.model.query.render.RenderProperties;
import org.betaconceptframework.astroboa.context.AstroboaClientContextHolder;
import org.betaconceptframework.astroboa.engine.jcr.renderer.BinaryChannelRenderer;
import org.betaconceptframework.astroboa.engine.jcr.renderer.CmsRepositoryEntityRenderer;
import org.betaconceptframework.astroboa.engine.jcr.renderer.ContentObjectRenderer;
import org.betaconceptframework.astroboa.engine.jcr.renderer.TopicRenderer;
import org.betaconceptframework.astroboa.engine.jcr.util.CmsRepositoryEntityUtils;
import org.betaconceptframework.astroboa.engine.jcr.util.JcrValueUtils;
import org.betaconceptframework.astroboa.engine.jcr.util.VersionUtils;
import org.betaconceptframework.astroboa.model.impl.BinaryPropertyImpl;
import org.betaconceptframework.astroboa.model.impl.BooleanPropertyImpl;
import org.betaconceptframework.astroboa.model.impl.CalendarPropertyImpl;
import org.betaconceptframework.astroboa.model.impl.CmsRepositoryEntityImpl;
import org.betaconceptframework.astroboa.model.impl.ComplexCmsPropertyImpl;
import org.betaconceptframework.astroboa.model.impl.DoublePropertyImpl;
import org.betaconceptframework.astroboa.model.impl.LazyCmsProperty;
import org.betaconceptframework.astroboa.model.impl.LongPropertyImpl;
import org.betaconceptframework.astroboa.model.impl.ObjectReferencePropertyImpl;
import org.betaconceptframework.astroboa.model.impl.StringPropertyImpl;
import org.betaconceptframework.astroboa.model.impl.TopicReferencePropertyImpl;
import org.betaconceptframework.astroboa.model.impl.definition.ComplexCmsPropertyDefinitionImpl;
import org.betaconceptframework.astroboa.model.impl.item.CmsBuiltInItem;
import org.betaconceptframework.astroboa.model.impl.item.CmsReadOnlyItem;
import org.betaconceptframework.astroboa.model.impl.item.JcrBuiltInItem;
import org.betaconceptframework.astroboa.util.DateUtils;
import org.betaconceptframework.astroboa.util.PropertyPath;
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 LazyComplexCmsPropertyLoader {

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

    @Autowired
    private BinaryChannelRenderer binaryChannelRenderer;
    @Autowired
    private CmsRepositoryEntityUtils cmsRepositoryEntityUtils;
    @Autowired
    private CmsRepositoryEntityRenderer cmsRepositoryEntityRenderer;

    @Autowired
    private VersionUtils versionUtils;
    @Autowired
    private TopicRenderer topicRenderer;
    @Autowired
    private ContentObjectRenderer contentObjectRenderer;

    public List<CmsProperty<?, ?>> renderChildProperty(CmsPropertyDefinition currentChildPropertyDefinition,
            String jcrNodeUUIDWhichCorrespondsToParentComplexCmsProperty,
            String jcrNodeUUIDWhichCorrespondsToContentObejct, RenderProperties renderProperties, Session session,
            Map<String, CmsRepositoryEntity> cachedCmsRepositoryEntities) {

        try {
            if (currentChildPropertyDefinition == null) {
                logger.warn(
                        "No cms property definition is provided for parent node UUID {} and content object node UUID {}",
                        jcrNodeUUIDWhichCorrespondsToParentComplexCmsProperty,
                        jcrNodeUUIDWhichCorrespondsToContentObejct);
                return null;
            }

            if (logger.isDebugEnabled()) {
                logger.debug("Lazy render property {}", currentChildPropertyDefinition.getFullPath());
            }

            //Load property container node if a UUID is provided
            //Otherwise a blank template for this child property will be created
            Node propertyContainerNode = null;
            if (StringUtils.isNotBlank(jcrNodeUUIDWhichCorrespondsToParentComplexCmsProperty)) {
                propertyContainerNode = session
                        .getNodeByIdentifier(jcrNodeUUIDWhichCorrespondsToParentComplexCmsProperty);
            }

            if (cachedCmsRepositoryEntities == null) {
                cachedCmsRepositoryEntities = new HashMap<String, CmsRepositoryEntity>();
            }

            if (currentChildPropertyDefinition instanceof ComplexCmsPropertyDefinition)
                return renderComplexProperty(currentChildPropertyDefinition.getName(), propertyContainerNode,
                        currentChildPropertyDefinition);
            else {
                CmsProperty<?, ?> simpleCmsProperty = renderSimpleProperty(currentChildPropertyDefinition.getName(),
                        currentChildPropertyDefinition, propertyContainerNode, session,
                        jcrNodeUUIDWhichCorrespondsToContentObejct, cachedCmsRepositoryEntities, renderProperties);

                List<CmsProperty<?, ?>> childCmsProperties = new ArrayList<CmsProperty<?, ?>>();

                if (simpleCmsProperty != null) {
                    childCmsProperties.add(simpleCmsProperty);
                }

                return childCmsProperties;
            }

        } catch (Exception e) {
            logger.error(
                    "While trying to lazy render child property {} with property container jcr node UUID {} "
                            + " and content object jcr node UUID {}",
                    new Object[] { currentChildPropertyDefinition.getFullPath(),
                            jcrNodeUUIDWhichCorrespondsToParentComplexCmsProperty,
                            jcrNodeUUIDWhichCorrespondsToContentObejct });
            throw new CmsException(e);
        }
    }

    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((AstroboaClientContextHolder.getActiveClientContext() != null
                            ? AstroboaClientContextHolder.getActiveClientContext().getAuthenticationToken()
                            : null));
        }

        return newProperty;
    }

    private CmsProperty<?, ?> renderSimpleProperty(String childPropertyName,
            CmsPropertyDefinition currentChildPropertyDefinition, Node propertyContainerNode, Session session,
            String contentObjectNodeUUID, Map<String, CmsRepositoryEntity> cachedCmsRepositoryEntities,
            RenderProperties renderProperties) throws RepositoryException {

        CmsProperty<?, ?> simpleProperty = createNewCmsProperty(currentChildPropertyDefinition, childPropertyName);

        //Render values
        if (simpleProperty != null) {
            if (currentChildPropertyDefinition instanceof BinaryPropertyDefinition) {
                renderBinaryChannels((BinaryProperty) simpleProperty, propertyContainerNode,
                        cachedCmsRepositoryEntities, session, renderProperties);
            } else {
                renderValueForSimpleProperty((SimpleCmsProperty<?, ?, ?>) simpleProperty, session,
                        contentObjectNodeUUID, propertyContainerNode, cachedCmsRepositoryEntities,
                        renderProperties);
            }
        }

        return simpleProperty;
    }

    private void renderValueForSimpleProperty(SimpleCmsProperty<?, ?, ?> simpleContentObjectProperty,
            Session session, String contentObjectNodeUUID, Node propertyContainerNode,
            Map<String, CmsRepositoryEntity> cachedCmsRepositoryEntities, RenderProperties renderProperties)
            throws RepositoryException {

        final String propertyName = simpleContentObjectProperty.getName();

        //Special case. In order to render versions and hasVersion we need
        //content object to look in VersionHistory
        if ((CmsReadOnlyItem.Versions.getJcrName().equals(propertyName)
                || CmsReadOnlyItem.HasVersion.getJcrName().equals(propertyName))) {

            Node contentObjectNode = null;

            try {
                //This is meaningful only if contentObject UUID is provided
                if (StringUtils.isNotBlank(contentObjectNodeUUID)) {
                    contentObjectNode = session.getNodeByIdentifier(contentObjectNodeUUID);

                    if (contentObjectNode != null) {
                        renderVersionNames(simpleContentObjectProperty, contentObjectNode, session);
                        return;
                    } else {
                        throw new Exception("Null content object node");
                    }
                }
            } catch (Exception e) {
                logger.warn("Could not render property '" + propertyName + "' Property Container Node : "
                        + propertyContainerNode.getPath() + " Content Object Node :"
                        + (contentObjectNode != null ? contentObjectNode.getPath() : " no content object node ")
                        + " Content object node UUID " + contentObjectNodeUUID, e);
            }

        } else if (propertyContainerNode == null || !propertyContainerNode.hasProperty(propertyName)) {
            //Check if property is Mandatory
            SimpleCmsPropertyDefinition<?> propertyDefinition = (SimpleCmsPropertyDefinition<?>) simpleContentObjectProperty
                    .getPropertyDefinition();

            //Issue a warning only if there is a container node
            //In cases where content object is new there is no property container node yet
            //therefore warning is misleading
            if (propertyDefinition.isMandatory()) {

                if (propertyContainerNode != null) {
                    logger.warn("Mandatory property {} does not exist for content object {}",
                            propertyDefinition.getFullPath(), contentObjectNodeUUID);
                }
                //Return default value
                //Property does not exist, it is mandatory and therefore render its 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 (propertyDefinition.getDefaultValue() != null){
                   renderSimpleValue(simpleContentObjectProperty, propertyDefinition.getDefaultValue(),session, cachedCmsRepositoryEntities, locale, renderProperties);
                }
                */
            }

        } else {
            renderSimpleCmsProperty(simpleContentObjectProperty, propertyContainerNode.getProperty(propertyName),
                    cachedCmsRepositoryEntities, session, renderProperties);
        }
    }

    private void renderBinaryChannels(BinaryProperty binaryProperty, Node propertyContainerNode,
            Map<String, CmsRepositoryEntity> cachedCmsRepositoryEntities, Session session,
            RenderProperties renderProperties) throws RepositoryException {

        String propertyName = binaryProperty.getName();

        //Binary channels are sub nodes of property node
        if (propertyContainerNode == null || ((!propertyContainerNode.hasNode(propertyName)
                && !propertyContainerNode.hasProperty(propertyName))) //it may be the case that binary channel is unmanaged, thus it is stored as a jcr property
        ) {
            //Issue a warning only if there is a container node
            //In cases where content object is new there is no property container node yet
            //therefore warning is misleading
            if (binaryProperty.getPropertyDefinition().isMandatory() && propertyContainerNode != null) {
                logger.warn("Mandatory property {} does not exist", propertyName);
            }
        } else {

            if (propertyContainerNode.hasProperty(propertyName)) {
                //Binary Channel is unmanaged. Only relative system paths are stored as values 
                //of a jcr property

                //Check that binary property's definition states that binary property must contain
                //unmanaged binary channels
                if (binaryProperty.getPropertyDefinition() == null
                        || !binaryProperty.getPropertyDefinition().isBinaryChannelUnmanaged()) {
                    logger.warn(
                            "Property {} is not defined as unmanaged binary property but only relative path(s) have been found");
                } else {

                    renderSimpleCmsProperty(binaryProperty, propertyContainerNode.getProperty(propertyName),
                            cachedCmsRepositoryEntities, session, renderProperties);
                }
            } else {
                NodeIterator nodeIter = propertyContainerNode.getNodes(propertyName);

                while (nodeIter.hasNext()) {
                    Node node = nodeIter.nextNode();
                    if (node.isNodeType(CmsBuiltInItem.BinaryChannel.getJcrName()) ||
                    //If it is a frozen Node 
                    //it must have a property named jcr:frozenPrimaryType whose
                    //value must be CmsBuiltInItem.BinaryChannel.getJcrName()
                            (node.isNodeType(JcrBuiltInItem.NtFrozenNode.getJcrName())
                                    && node.hasProperty(JcrBuiltInItem.JcrFrozenPrimaryType.getJcrName())
                                    && node.getProperty(JcrBuiltInItem.JcrFrozenPrimaryType.getJcrName())
                                            .getString().equals(CmsBuiltInItem.BinaryChannel.getJcrName())))
                        binaryProperty.addSimpleTypeValue(binaryChannelRenderer.render(node, false));
                    else
                        logger.warn(
                                "Binary channel {} exists in content object node {} with type other than {} and that is {}",
                                new Object[] { propertyName, propertyContainerNode.getPath(),
                                        CmsBuiltInItem.BinaryChannel.getJcrName(),
                                        node.getPrimaryNodeType().getName() });
                }
            }
        }
    }

    private List<CmsProperty<?, ?>> renderComplexProperty(String childPropertyName, Node propertyContainerNode,
            CmsPropertyDefinition currentChildPropertyDefinition) throws RepositoryException {

        List<CmsProperty<?, ?>> childCmsProperties = new ArrayList<CmsProperty<?, ?>>();

        if (propertyContainerNode == null || !propertyContainerNode.hasNode(childPropertyName)) {
            //No complex property node exist in repository
            //If mandatory create a new complex property and issue a warning
            //Issue a warning only if there is a container node
            //In cases where content object is new there is no property container node yet
            //therefore warning is misleading
            if (currentChildPropertyDefinition.isMandatory() && propertyContainerNode != null) {
                logger.warn("Mandatory property {} does not exist in repository ", childPropertyName);
            }

            //Create an empty property
            childCmsProperties.add(createNewCmsProperty(currentChildPropertyDefinition, childPropertyName));

        } else {
            NodeIterator complexNodes = propertyContainerNode.getNodes(childPropertyName);

            if (complexNodes.getSize() > 1 && !currentChildPropertyDefinition.isMultiple())
                throw new CmsException("ComplexCmsProperty '" + currentChildPropertyDefinition.getName()
                        + "' is single value but there are more " + " than one nodes in "
                        + propertyContainerNode.getPath());

            Map<Integer, CmsProperty<?, ?>> propertiesPerOrder = new TreeMap<Integer, CmsProperty<?, ?>>();

            int unknownIndex = 10000;

            while (complexNodes.hasNext()) {
                //Locate node for property
                Node nodeOfComplexProperty = complexNodes.nextNode();

                //Create new CmsProperty
                CmsProperty<?, ?> newCmsProperty = createNewCmsProperty(currentChildPropertyDefinition,
                        childPropertyName);

                if (newCmsProperty instanceof LazyCmsProperty) {
                    ((LazyCmsProperty) newCmsProperty)
                            .setPropertyContainerNodeUUID(nodeOfComplexProperty.getIdentifier());
                }

                //Render Complex Property Id
                if (!cmsRepositoryEntityUtils.hasCmsIdentifier(nodeOfComplexProperty)) {
                    throw new CmsException("Found no id for complex property " + nodeOfComplexProperty.getPath());
                }

                //Render Id
                cmsRepositoryEntityRenderer.renderCmsRepositoryEntityBasicAttributes(nodeOfComplexProperty,
                        newCmsProperty);

                if (nodeOfComplexProperty.hasProperty(CmsBuiltInItem.Order.getJcrName())) {
                    try {

                        int index = (int) nodeOfComplexProperty.getProperty(CmsBuiltInItem.Order.getJcrName())
                                .getLong() - 1;

                        if (propertiesPerOrder.containsKey(index)) {
                            propertiesPerOrder.put(unknownIndex++, newCmsProperty);
                        } else {
                            propertiesPerOrder.put(index, newCmsProperty);
                        }

                    } catch (Exception e) {
                        logger.warn("Node " + nodeOfComplexProperty.getPath()
                                + " did not have a valid order value and therefore corresponding cms property will be added at the end of the list",
                                e);

                        propertiesPerOrder.put(unknownIndex++, newCmsProperty);
                    }
                } else {
                    propertiesPerOrder.put(unknownIndex++, newCmsProperty);
                }
            }

            childCmsProperties.addAll(propertiesPerOrder.values());

        }

        return childCmsProperties;

    }

    private void renderVersionNames(SimpleCmsProperty<?, ?, ?> simpleProperty, Node contentObjectNode,
            Session session) throws RepositoryException {
        VersionHistory versioningHistory = null;

        //Render is about an archived content object, therefore node uuid is under jcr:frozenUUID
        if (contentObjectNode.hasProperty(JcrBuiltInItem.JcrFrozenUUID.getJcrName()))
            versioningHistory = versionUtils.getVersionHistoryForNode(session,
                    cmsRepositoryEntityUtils.getCmsIdentifier(contentObjectNode));
        else
            versioningHistory = session.getWorkspace().getVersionManager()
                    .getVersionHistory(contentObjectNode.getPath());

        if (versioningHistory != null) {
            VersionIterator versIter = versioningHistory.getAllVersions();
            while (versIter.hasNext()) {
                Version currentVersion = versIter.nextVersion();

                String versionName = currentVersion.getName();
                if (!versionName.equals(JcrBuiltInItem.JcrRootVersion.getJcrName())) {
                    //Version with no successors is the base version
                    Version[] successors = currentVersion.getSuccessors();

                    //RenderHasVersion
                    //Current version does not have successors
                    if (ArrayUtils.isEmpty(successors)) {
                        if (CmsReadOnlyItem.HasVersion.getJcrName().equals(simpleProperty.getName()))
                            renderSimpleValue(simpleProperty, versionName, session, null, null);

                    }

                    //Render versionName
                    if (CmsReadOnlyItem.Versions.getJcrName().equals(simpleProperty.getName())) {
                        renderSimpleValue(simpleProperty, versionName, session, null, null);
                    }
                }
            }
        }
    }

    private void renderSimpleCmsProperty(SimpleCmsProperty<?, ?, ?> simpleProperty, Property property,
            Map<String, CmsRepositoryEntity> cachedCmsRepositoryEntities, Session session,
            RenderProperties renderProperties) throws RepositoryException {

        SimpleCmsPropertyDefinition<?> propertyDefinition = (SimpleCmsPropertyDefinition<?>) simpleProperty
                .getPropertyDefinition();

        //Gather all Value from repository in a list
        //regardless if property is multiple or not
        List<Value> values = new ArrayList<Value>();

        if (propertyDefinition.isMultiple()) {
            //It may be the case that property used to be single value.
            //if so an exception is thrown by JCR. 
            //we must check in order to avoid it
            if (property.getDefinition() != null && !property.getDefinition().isMultiple()) {
                values.add(property.getValue());
            } else {
                values.addAll(Arrays.asList(property.getValues()));
            }
        } else
            values.add(property.getValue());

        setValuesToSimpleProperty(simpleProperty, propertyDefinition.getValueType(), values,
                cachedCmsRepositoryEntities, session, renderProperties);
    }

    private void setValuesToSimpleProperty(SimpleCmsProperty simpleProperty, ValueType definitionValueType,
            List<Value> values, Map<String, CmsRepositoryEntity> cachedCmsRepositoryEntities, Session session,
            RenderProperties renderProperties) throws RepositoryException {
        switch (definitionValueType) {
        case Boolean:
            for (Value value : values) {
                simpleProperty.addSimpleTypeValue(value.getBoolean());
            }
            break;
        case String:
            for (Value value : values) {
                try {
                    simpleProperty.addSimpleTypeValue(value.getString());
                } catch (Exception e) {

                    //Backwards compatibility. There was a bug no check was made
                    //when entering plain string values. If value is invalid because entered
                    //value has more characters than maxLegth, provide maxLength characters of this value 
                    //
                    if (value.getString() != null && simpleProperty.getPropertyDefinition() != null
                            && ((StringPropertyDefinition) simpleProperty.getPropertyDefinition())
                                    .getMaxLength() != null
                            && ((StringPropertyDefinition) simpleProperty.getPropertyDefinition())
                                    .getMaxLength() > 0
                            && ((StringPropertyDefinition) simpleProperty.getPropertyDefinition())
                                    .getMaxLength() < value.getString().length()) {

                        logger.warn("Value " + value.getString() + " contains more characters "
                                + value.getString().length() + " than allowed "
                                + ((StringPropertyDefinition) simpleProperty.getPropertyDefinition()).getMaxLength()
                                + " . Only the first "
                                + ((StringPropertyDefinition) simpleProperty.getPropertyDefinition()).getMaxLength()
                                + " characters will be displayed", e);

                        simpleProperty.addSimpleTypeValue(value.getString().substring(0,
                                ((StringPropertyDefinition) simpleProperty.getPropertyDefinition())
                                        .getMaxLength()));

                    } else {
                        logger.warn("Value " + value.getString()
                                + " is probably invalid. It will not be added to property "
                                + simpleProperty.getFullPath()
                                + " This value will remain in repository until property is resaved with different value(s). This error may happen if "
                                + "value constraints have been applied recently to property.", e);
                    }
                }
            }
            break;
        case Date:
            for (Value value : values) {
                try {
                    simpleProperty.addSimpleTypeValue(value.getDate());
                } catch (Exception e) {
                    logger.warn("Value " + DateUtils.format(value.getDate(), "dd/MM/yyy HH:mm")
                            + " is probably invalid. It will not be added to property "
                            + simpleProperty.getFullPath()
                            + " This value will remain in repository until property is resaved with different value(s). This error may happen if "
                            + "value constraints have been applied recently to property.", e);
                }
            }
            break;
        case Double:
            for (Value value : values) {
                try {
                    simpleProperty.addSimpleTypeValue(value.getDouble());
                } catch (Exception e) {
                    logger.warn("Value " + value.getDouble()
                            + " is probably invalid. It will not be added to property "
                            + simpleProperty.getFullPath()
                            + " This value will remain in repository until property is resaved with different value(s). This error may happen if "
                            + "value constraints have been applied recently to property.", e);
                }
            }
            break;
        case Long:
            for (Value value : values) {
                try {
                    simpleProperty.addSimpleTypeValue(value.getLong());
                } catch (Exception e) {
                    logger.warn("Value " + value.getLong()
                            + " is probably invalid. It will not be added to property "
                            + simpleProperty.getFullPath()
                            + " This value will remain in repository until property is resaved with different value(s). This error may happen if "
                            + "value constraints have been applied recently to property.", e);
                }
            }
            break;
        case ObjectReference:
            renderContentObject((ObjectReferenceProperty) simpleProperty, values, cachedCmsRepositoryEntities,
                    session, renderProperties);
            break;
        case TopicReference:
            renderTopic(values, session, cachedCmsRepositoryEntities, renderProperties, simpleProperty);
            break;
        case Binary:
            for (Value value : values)
                simpleProperty.addSimpleTypeValue(binaryChannelRenderer
                        .renderUnmanagedBinaryChannel(simpleProperty.getName(), value.getString()));
            break;

        default:
            break;
        }
    }

    private void renderTopic(List<Value> values, Session session,
            Map<String, CmsRepositoryEntity> cachedCmsRepositoryEntities, RenderProperties renderProperties,
            SimpleCmsProperty simpleProperty) throws RepositoryException {

        for (Value topicIdAsValue : values) {
            //Do not render Topic if it has already been rendered
            Topic topic = null;
            String topicIdAsString = topicIdAsValue.getString();
            if (cachedCmsRepositoryEntities.containsKey(topicIdAsString))
                topic = (Topic) cachedCmsRepositoryEntities.get(topicIdAsString);
            else {

                try {
                    topic = topicRenderer.renderTopic(topicIdAsString, renderProperties, session,
                            cachedCmsRepositoryEntities);

                    if (!cachedCmsRepositoryEntities.containsKey(topicIdAsString))
                        cachedCmsRepositoryEntities.put(topicIdAsString, topic);
                } catch (Exception e) {
                    logger.warn("Unable to render topic with id " + topicIdAsString
                            + " for content object property " + simpleProperty.getFullPath());
                }

            }

            simpleProperty.addSimpleTypeValue(topic);
        }

    }

    private void renderContentObject(ObjectReferenceProperty contentObjectProperty, List<Value> values,
            Map<String, CmsRepositoryEntity> cachedCmsRepositoryEntities, Session session,
            RenderProperties renderProperties) throws RepositoryException {

        Map<String, ContentObjectTypeDefinition> cachedContentObjectTypeDefinitions = new HashMap<String, ContentObjectTypeDefinition>();

        //Disable Full rendering for ContentObjectReferences 
        boolean fullRenderIsEnabled = renderProperties != null
                && renderProperties.allContentObjectPropertiesAreRendered();

        if (fullRenderIsEnabled) {
            renderProperties.renderAllContentObjectProperties(false);
        }

        for (Value contentObjectIdValue : values) {
            ContentObject contentObject = null;
            String contentObjectIdAsString = contentObjectIdValue.getString();

            if (cachedCmsRepositoryEntities.containsKey(contentObjectIdAsString)) {
                contentObject = (ContentObject) cachedCmsRepositoryEntities.get(contentObjectIdAsString);
            } else {
                Node contentObjectNode = cmsRepositoryEntityUtils.retrieveUniqueNodeForContentObject(session,
                        contentObjectIdAsString);

                if (contentObjectNode == null) {
                    logger.warn(
                            "Content object with id {} does not exist in repository. Value found in property {} and will not be rendered",
                            contentObjectIdAsString, contentObjectProperty.getFullPath());
                } else {

                    contentObject = contentObjectRenderer.render(session, contentObjectNode, renderProperties,
                            cachedContentObjectTypeDefinitions, cachedCmsRepositoryEntities);

                    cachedCmsRepositoryEntities.put(contentObjectIdAsString, contentObject);
                }
            }

            contentObjectProperty.addSimpleTypeValue(contentObject);
        }

        if (fullRenderIsEnabled) {
            renderProperties.renderAllContentObjectProperties(true);
        }
    }

    private void renderSimpleValue(SimpleCmsProperty simpleProperty, Object value, Session session,
            Map<String, CmsRepositoryEntity> cachedCmsRepositoryEntities, RenderProperties renderProperties)
            throws RepositoryException {
        if (value == null)
            simpleProperty.addSimpleTypeValue(null);
        else {
            final Value jcrValue = JcrValueUtils.getJcrValue(value, simpleProperty.getValueType(),
                    session.getValueFactory());

            setValuesToSimpleProperty(simpleProperty, simpleProperty.getValueType(), Arrays.asList(jcrValue),
                    cachedCmsRepositoryEntities, session, renderProperties);

        }
    }

    /*
     * Checks to see if there is a value for the provided property path
     */
    public boolean valueForPropertyExists(String property,
            String jcrNodeUUIDWhichCorrespondsToParentComplexCmsProperty, Session session) {

        PropertyPath propertyPath = new PropertyPath(property);

        if (property == null || propertyPath.getPropertyName() == null) {
            logger.warn(
                    "No property path is provided for parent node UUID {}. Do not know "
                            + "which child property to check",
                    jcrNodeUUIDWhichCorrespondsToParentComplexCmsProperty);
            return false;
        }

        if (StringUtils.isBlank(jcrNodeUUIDWhichCorrespondsToParentComplexCmsProperty)) {
            //Since no parent exists, no need to check for child
            if (logger.isDebugEnabled()) {
                logger.debug(
                        "No id for parent has been provided. No need to check for existence of child property {} ",
                        propertyPath.getFullPath());
            }

            return false;
        }

        if (logger.isDebugEnabled()) {
            logger.debug("Checking property {} existence", propertyPath.getFullPath());
        }

        //Load property container node if a UUID is provided
        //Otherwise a blank template for this child property will be created
        try {
            Node propertyContainerNode = session
                    .getNodeByIdentifier(jcrNodeUUIDWhichCorrespondsToParentComplexCmsProperty);

            return propertyPathContainsValue(propertyPath, propertyContainerNode);

        } catch (Exception e) {
            logger.warn("Could not locate parent node with UUID "
                    + jcrNodeUUIDWhichCorrespondsToParentComplexCmsProperty
                    + ". Cannot check value existence for property " + propertyPath.getFullPath(), e);
            return false;
        }
    }

    private boolean propertyPathContainsValue(PropertyPath propertyPath, Node propertyContainerNode)
            throws PathNotFoundException, ValueFormatException, RepositoryException {

        String propertyName = propertyPath.getPropertyName();
        int index = propertyPath.getPropertyIndex();

        if (propertyPath.getPropertyDescendantPath() != null) {
            //This property is a complex one.
            //Find it and proceed with the rest of the path
            Node nodeRepresentingProperty = findNodeForProperty(propertyContainerNode, propertyName, index);

            return propertyPathContainsValue(new PropertyPath(propertyPath.getPropertyDescendantPath()),
                    nodeRepresentingProperty);
        } else {
            //Final path part. Property is either a Jcr property or a jcr node 
            return valueExists(propertyContainerNode, propertyName, index);
        }
    }

    /**
     * @param propertyContainerNode
     * @param propertyName
     * @param index
     * @return
     * @throws RepositoryException 
     * @throws ValueFormatException 
     * @throws PathNotFoundException 
     */
    private boolean valueExists(Node propertyContainerNode, String propertyName, int index)
            throws PathNotFoundException, ValueFormatException, RepositoryException {

        //Check jcr properties
        if (propertyContainerNode != null && propertyContainerNode.hasProperty(propertyName)) {
            if (index > -1) {
                Property property = propertyContainerNode.getProperty(propertyName);

                boolean multivalue = property.getDefinition().isMultiple();

                int sizeOfValues = 1;

                if (multivalue) {
                    sizeOfValues = property.getValues().length;
                }

                return (index + 1) <= sizeOfValues;

            } else {
                return true;
            }
        } else {
            return findNodeForProperty(propertyContainerNode, propertyName, index) != null;
        }
    }

    /**
     * @param propertyContainerNode
     * @param propertyName
     * @param index
     * @return
     */
    private Node findNodeForProperty(Node parentNode, String propertyName, int index) {

        try {
            if (parentNode == null || !parentNode.hasNode(propertyName)) {
                return null;
            }

            NodeIterator nodesRepresentingProperty = parentNode.getNodes(propertyName);

            if (nodesRepresentingProperty.getSize() == 1) {
                //Only one node exists
                if (index <= 0) {
                    //User has not specified index or index is 0 (zero based)
                    return nodesRepresentingProperty.nextNode();
                } else {
                    //Index provided by the user does not exist
                    if (logger.isDebugEnabled()) {
                        logger.debug("Index {} for property {} in parent property {} does not exist",
                                new Object[] { index, propertyName, parentNode.getPath() });
                    }
                    return null;
                }
            } else {
                //Due to the requirement of keeping complex property's index inside a specific jcr property
                //we have to search all nodes to find a match for the provided index.
                //A negative index is equivalent to 0, that is the first item
                if (index < 0) {
                    index = 0;
                }

                boolean atLeastOneNodeFoundWithOrderProperty = false;

                Node nodeOfComplexPropertyWhosePositionMatchesIndex = null;

                while (nodesRepresentingProperty.hasNext()) {
                    Node nodeOfComplexProperty = nodesRepresentingProperty.nextNode();
                    long position = nodesRepresentingProperty.getPosition();

                    if (nodeOfComplexProperty.hasProperty(CmsBuiltInItem.Order.getJcrName())) {

                        atLeastOneNodeFoundWithOrderProperty = true;

                        int complexPropertyIndex = (int) nodeOfComplexProperty
                                .getProperty(CmsBuiltInItem.Order.getJcrName()).getLong();

                        if (complexPropertyIndex == index) {
                            return nodeOfComplexProperty;
                        }
                    }

                    if ((int) position == index) {
                        nodeOfComplexPropertyWhosePositionMatchesIndex = nodeOfComplexProperty;
                    }
                }

                if (!atLeastOneNodeFoundWithOrderProperty) {
                    return nodeOfComplexPropertyWhosePositionMatchesIndex;
                } else {
                    if (nodeOfComplexPropertyWhosePositionMatchesIndex != null) {
                        //Some or all of the complex nodes contained Order property but none of them matched index.
                        //Nevertheless there is a node whose position matched the index (although its Order property either 
                        //does not exist or does not match the index
                        //Report this as a warning and return value
                        long indexValueOfNodeOfComplexPropertyWhosePositionMatchesIndex = -1;
                        if (nodeOfComplexPropertyWhosePositionMatchesIndex
                                .hasProperty(CmsBuiltInItem.Order.getJcrName())) {
                            indexValueOfNodeOfComplexPropertyWhosePositionMatchesIndex = nodeOfComplexPropertyWhosePositionMatchesIndex
                                    .getProperty(CmsBuiltInItem.Order.getJcrName()).getLong();
                        }

                        PropertyPath requestedPath = new PropertyPath(parentNode.getPath() + "." + propertyName);
                        requestedPath.setPropertyIndex(index);

                        logger.warn(
                                "Requested node for path {}. Found node {} whose position matched requested index {} but it {}.",
                                new Object[] { requestedPath.getFullPath(),
                                        nodeOfComplexPropertyWhosePositionMatchesIndex, index,
                                        (indexValueOfNodeOfComplexPropertyWhosePositionMatchesIndex == -1
                                                ? " does not contain an 'order' property. However this node will be returned"
                                                : " contains an 'order' property with value "
                                                        + indexValueOfNodeOfComplexPropertyWhosePositionMatchesIndex
                                                        + " instead. No node will be returned") });
                        if (indexValueOfNodeOfComplexPropertyWhosePositionMatchesIndex == -1) {
                            return nodeOfComplexPropertyWhosePositionMatchesIndex;
                        }

                    }

                    return null;
                }
            }
        } catch (RepositoryException e) {
            logger.error("", e);
            return null;
        }

    }
}