/* * 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; } } }