/* * 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 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Astroboa. If not, see <http://www.gnu.org/licenses/>. */ package org.betaconceptframework.astroboa.engine.jcr.io; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collections; import java.util.Comparator; import java.util.Deque; import java.util.GregorianCalendar; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.PathNotFoundException; import javax.jcr.Property; import javax.jcr.PropertyIterator; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.xml.datatype.DatatypeConfigurationException; import javax.xml.datatype.DatatypeConstants; import javax.xml.datatype.DatatypeFactory; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.StringUtils; import org.betaconceptframework.astroboa.api.model.BetaConceptNamespaceConstants; import org.betaconceptframework.astroboa.api.model.ContentObject; import org.betaconceptframework.astroboa.api.model.Space; import org.betaconceptframework.astroboa.api.model.Taxonomy; 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.CalendarPropertyDefinition; import org.betaconceptframework.astroboa.api.model.definition.CmsDefinition; 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.LocalizableCmsDefinition; import org.betaconceptframework.astroboa.api.model.definition.SimpleCmsPropertyDefinition; import org.betaconceptframework.astroboa.api.model.exception.CmsException; import org.betaconceptframework.astroboa.api.model.io.FetchLevel; import org.betaconceptframework.astroboa.api.model.io.SerializationConfiguration; import org.betaconceptframework.astroboa.api.model.io.SerializationReport; import org.betaconceptframework.astroboa.commons.comparator.PropertyRepresentingXmlAttributeComparator; import org.betaconceptframework.astroboa.context.AstroboaClientContextHolder; import org.betaconceptframework.astroboa.engine.jcr.io.SerializationBean.CmsEntityType; import org.betaconceptframework.astroboa.engine.jcr.io.contenthandler.ExportContentHandler; import org.betaconceptframework.astroboa.engine.jcr.io.contenthandler.JsonExportContentHandler; import org.betaconceptframework.astroboa.engine.jcr.io.contenthandler.XmlExportContentHandler; import org.betaconceptframework.astroboa.engine.jcr.util.CmsRepositoryEntityUtils; import org.betaconceptframework.astroboa.engine.jcr.util.JackrabbitDependentUtils; import org.betaconceptframework.astroboa.engine.jcr.util.JcrNodeUtils; import org.betaconceptframework.astroboa.model.impl.BinaryChannelImpl; import org.betaconceptframework.astroboa.model.impl.definition.ComplexCmsPropertyDefinitionImpl; import org.betaconceptframework.astroboa.model.impl.definition.SimpleCmsPropertyDefinitionImpl; import org.betaconceptframework.astroboa.model.impl.io.SerializationReportImpl; import org.betaconceptframework.astroboa.model.impl.item.CmsBuiltInItem; import org.betaconceptframework.astroboa.model.impl.item.ItemUtils; import org.betaconceptframework.astroboa.model.impl.item.JcrBuiltInItem; import org.betaconceptframework.astroboa.model.impl.item.JcrNamespaceConstants; import org.betaconceptframework.astroboa.model.jaxb.MarshalUtils; import org.betaconceptframework.astroboa.service.dao.DefinitionServiceDao; import org.betaconceptframework.astroboa.util.CmsConstants; import org.betaconceptframework.astroboa.util.PropertyPath; import org.betaconceptframework.astroboa.util.ResourceApiURLUtils; import org.betaconceptframework.astroboa.util.UrlProperties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xml.sax.helpers.AttributesImpl; /** * Class responsible to serialize JCR nodes according to * Astroboa content model. * * It mimics the behavior of JCR exporter but it traverses * jcr nodes and properties according to Astroboa content model. * * It uses {@link XmlExportContentHandler} to directly write xml content * instead of first constructing attributes list and then passing them * to ContentHandler * * @author Gregory Chomatas (gchomatas@betaconcept.com) * @author Savvas Triantafyllou (striantafyllou@betaconcept.com) * */ public class Serializer { private Logger logger = LoggerFactory.getLogger(getClass()); private ExportContentHandler exportContentHandler; private CmsRepositoryEntityUtils cmsRepositoryEntityUtils; private List<String> processedCmsRepositoryEntityIdentifiers = new ArrayList<String>(); private final static String BCCMS_PREFIX_WITH_SEMICOLON = BetaConceptNamespaceConstants.ASTROBOA_PREFIX+CmsConstants.QNAME_PREFIX_SEPARATOR; private static final String NT_PREFIX_WITH_SEMI_COLON = JcrNamespaceConstants.NT_PREFIX+CmsConstants.QNAME_PREFIX_SEPARATOR; private static final String JCR_MIX_PREFIX_WITH_SEMI_COLON = JcrNamespaceConstants.MIX_PREFIX+CmsConstants.QNAME_PREFIX_SEPARATOR; private static final String JCR_PREFIX_WITH_SEMI_COLON = JcrNamespaceConstants.JCR_PREFIX+CmsConstants.QNAME_PREFIX_SEPARATOR; private DatatypeFactory df ; private Map<String, String> prefixesPerType; private SerializationReport serializationReport; //private String qNameOfEntityCurrentlySerialized = null; private Session session; private DefinitionServiceDao definitionServiceDao; private Deque<LocalizableCmsDefinition> parentPropertyDefinitionQueue = new ArrayDeque<LocalizableCmsDefinition>(); private List<String> propertyPathsWhoseValuesWillBeIncludedInTheSerialization = new ArrayList<String>(); private List<String> propertyPathsWhoseValuesWillBeIncludedInTheSerializationOfObjectReferences = Arrays.asList("profile.title"); private boolean objectReferenceIsSerialized = false; private AttributesImpl rootElementAttributes; private boolean useTheSameNameForAllObjects; private enum NodeType{ Taxonomy, ToBeIgnored, ContentObject, Other, Topic, Space } private SerializationConfiguration serializationConfiguration; private Comparator<CmsPropertyDefinition> cmsPropertyDefinitionComparator = new PropertyRepresentingXmlAttributeComparator(); public Serializer(OutputStream out, CmsRepositoryEntityUtils cmsRepositoryEntityUtils, Session session, SerializationConfiguration serializationConfiguration) throws Exception{ this.serializationConfiguration = serializationConfiguration; if (this.serializationConfiguration == null){ //Default value to avoid NPE this.serializationConfiguration = SerializationConfiguration.repository().build(); } createNewExportContentHandler(out); this.cmsRepositoryEntityUtils = cmsRepositoryEntityUtils; this.session = session; if (this.session == null){ throw new CmsException("Cannot initialize serializer because no JCR session has been provided"); } if (df == null){ try { df = DatatypeFactory.newInstance(); } catch (DatatypeConfigurationException e) { throw new CmsException(e); } } } private void createNewExportContentHandler(OutputStream out) throws IOException { if (serializationConfiguration.isXMLRepresentationTypeEnabled()){ exportContentHandler = new XmlExportContentHandler(out,serializationConfiguration.prettyPrint()); } else if (serializationConfiguration.isJSONRepresentationTypeEnabled()){ exportContentHandler = new JsonExportContentHandler(out, true, serializationConfiguration.prettyPrint()); } else{ logger.warn("Resource Representation {} is not valid within export context. Export to XML is chosen", serializationConfiguration.getResourceRepresentationType()); exportContentHandler = new XmlExportContentHandler(out,serializationConfiguration.prettyPrint()); } } /** * Node to be serialized represents a root node, usually root node of all taxonomies, or root node of all users * or root node of all content objects and this method will serialize all its children nodes. * @param node * @throws Exception */ public void serializeChildrenOfRootNode(Node node) throws Exception{ serializeNode(node, false, FetchLevel.FULL, false, false); } /** * Node to be serialized represents a resource item in a collection * @param node * @param shouldVisitChildren(fetchLevel) * @param nodeRepresentsResourceCollectionItem * @throws Exception */ public void serializeResourceCollectionItem(Node node, FetchLevel fetchLevel, boolean nodeRepresentsRootElement) throws Exception{ serializeNode(node, false, fetchLevel, true, nodeRepresentsRootElement); } public void end() throws Exception { exportContentHandler.end(); } public void start(AttributesImpl rootElementAttributes) throws Exception { exportContentHandler.start(); this.rootElementAttributes = rootElementAttributes; } public void start() throws Exception { exportContentHandler.start(); } public void serializeNode(Node node, boolean nodeRepresentsCmsProperty, FetchLevel fetchLevel, boolean nodeRepresentsResourceCollectionItem, boolean nodeRepresentsRootElement) throws Exception { if (logger.isDebugEnabled()){ logger.debug("Serializing node {}", node.getPath()); } NodeType nodeType = determineNodeType(node); switch (nodeType) { case ToBeIgnored: serializeChildNodes(node, false, fetchLevel) ; break; case ContentObject: serializeContentObjectNode(node, nodeRepresentsResourceCollectionItem); break; case Taxonomy: serializeTaxonomyNode(node, fetchLevel, nodeRepresentsResourceCollectionItem, nodeRepresentsRootElement); break; case Topic: serializeTopicNode(node, fetchLevel, true, nodeRepresentsResourceCollectionItem, nodeRepresentsRootElement, false); break; case Space: serializeSpaceNode(node, fetchLevel, nodeRepresentsResourceCollectionItem, nodeRepresentsRootElement, false); break; default: serializeCustomNode(node, nodeRepresentsCmsProperty, false); break; } } public void serializeSpaceNode(Node node, FetchLevel fetchLevel, boolean nodeRepresentsResourceCollectionItem, boolean nodeRepresentsRootElement, boolean parentSpaceNode) throws Exception{ String spaceQName = CmsBuiltInItem.Space.getLocalPart(); if (CmsBuiltInItem.OrganizationSpace.getJcrName().equals(node.getName())){ spaceQName = CmsBuiltInItem.OrganizationSpace.getLocalPart(); } if (nodeRepresentsRootElement){ spaceQName = BetaConceptNamespaceConstants.ASTROBOA_MODEL_DEFINITION_PREFIX +":"+spaceQName; } else if (nodeRepresentsResourceCollectionItem){ if (outputIsJSON()){ //When serializing a collection of resources //the name of the element which represents the space //is CmsConstants.RESOURCE when output is JSON spaceQName = CmsConstants.RESOURCE; } else{ //Node represents a space which appears //as a root child of a resource representation element. //In XML corresponding element should be prefixed //as its parent (resourceCollection) does not share the //same namespace spaceQName = BetaConceptNamespaceConstants.ASTROBOA_MODEL_DEFINITION_PREFIX +":"+ spaceQName; } } else if (parentSpaceNode){ spaceQName = CmsConstants.PARENT_SPACE; } String spaceCmsIdentifier = retrieveCmsIdentifier(node); if (cmsIdentifierAlreadyProcessed(spaceCmsIdentifier)){ serializeCmsRepositoryEntityIdentifierForAnAlreadySerializedEntity(spaceQName, spaceCmsIdentifier); if (node.hasProperty(CmsBuiltInItem.Name.getJcrName())){ writeAttribute(CmsBuiltInItem.Name.getLocalPart(), node.getProperty(CmsBuiltInItem.Name.getJcrName()).getString()); } addUrlForEntityRepresentedByNode(node); processLocalization(node); closeEntity(spaceQName); } else{ startedNewEntitySerialization(spaceQName); openEntityWithAttribute(spaceQName, CmsBuiltInItem.CmsIdentifier.getLocalPart(), spaceCmsIdentifier); markCmsIdentifierProcessed(spaceCmsIdentifier); serializeBuiltInProperties(node, parentSpaceNode); if (! parentSpaceNode && node.getParent().isNodeType(CmsBuiltInItem.Space.getJcrName())){ serializeSpaceNode(node.getParent(), FetchLevel.ENTITY, false, false, true); } if (! parentSpaceNode && shouldVisitChildren(fetchLevel) && node.hasNode(CmsBuiltInItem.Space.getJcrName())){ NodeIterator childSpaceNodes = node.getNodes(CmsBuiltInItem.Space.getJcrName()); if (childSpaceNodes.getSize() > 0){ FetchLevel visitDepthForChildSpaces = fetchLevel == FetchLevel.FULL ? FetchLevel.FULL : FetchLevel.ENTITY; openEntityWithNoAttributes(CmsConstants.CHILD_SPACES); while (childSpaceNodes.hasNext()){ serializeSpaceNode(childSpaceNodes.nextNode(), visitDepthForChildSpaces , false, false, false); } closeEntity(CmsConstants.CHILD_SPACES); } } closeEntity(spaceQName); finishedEntitySerialization(CmsEntityType.SPACE, spaceQName); } } public void serializeTopicNode(Node node, FetchLevel fetchLevel, boolean serializeTaxonomyNode, boolean nodeRepresentsResourceCollectionItem, boolean nodeRepresentsRootElement, boolean parentTopicNode) throws Exception{ String topicCmsIdentifier = retrieveCmsIdentifier(node); String topicQName = CmsBuiltInItem.Topic.getLocalPart(); if (nodeRepresentsRootElement){ topicQName = BetaConceptNamespaceConstants.ASTROBOA_MODEL_DEFINITION_PREFIX +":"+ CmsBuiltInItem.Topic.getLocalPart(); } else if (nodeRepresentsResourceCollectionItem){ if (outputIsJSON()){ //When serializing a collection of resources //the name of the element which represents the topic //is CmsConstants.RESOURCE when output is JSON topicQName = CmsConstants.RESOURCE; } else{ //Node represents a topic which appears //as a root child of a resource representation element. //In XML corresponding element should be prefixed //as its parent (resourceCollection) does not share the //same namespace topicQName = BetaConceptNamespaceConstants.ASTROBOA_MODEL_DEFINITION_PREFIX +":"+ CmsBuiltInItem.Topic.getLocalPart(); } } else if (parentTopicNode){ topicQName = CmsConstants.PARENT_TOPIC; } if (cmsIdentifierAlreadyProcessed(topicCmsIdentifier)){ serializeCmsRepositoryEntityIdentifierForAnAlreadySerializedEntity(topicQName, topicCmsIdentifier); serializeBasicTopicInformation(node); if (CmsConstants.PARENT_TOPIC == topicQName){ addOwnerAsElement(node); } closeEntity(topicQName); } else{ startedNewEntitySerialization(topicQName); openEntityWithAttribute(topicQName, CmsBuiltInItem.CmsIdentifier.getLocalPart(), topicCmsIdentifier); markCmsIdentifierProcessed(topicCmsIdentifier); serializeBuiltInProperties(node, parentTopicNode); if (serializeTaxonomyNode){ try{ Node taxonomyNode = JcrNodeUtils.getTaxonomyJcrNode(node.getParent(), false); if (taxonomyNode != null){ serializeTaxonomyNode(taxonomyNode, FetchLevel.ENTITY, false, false); } else{ logger.warn("Unable to serialize taxonomy for topic {}",node.getPath()); } } catch(Exception e){ logger.warn("Unable to serialize taxonomy for topic "+ node.getPath(), e); } } //Serialize Parent Node if parent node is not a taxonomy if (! parentTopicNode && ! node.getParent().isNodeType(CmsBuiltInItem.Taxonomy.getJcrName())){ serializeTopicNode(node.getParent(), FetchLevel.ENTITY, false, false, false, true); } if (! parentTopicNode && shouldVisitChildren(fetchLevel) && node.hasNode(CmsBuiltInItem.Topic.getJcrName())){ NodeIterator childTopicNodes = node.getNodes(CmsBuiltInItem.Topic.getJcrName()); if (childTopicNodes.getSize() > 0){ FetchLevel visitDepthForChildTopics = fetchLevel == FetchLevel.FULL ? FetchLevel.FULL : FetchLevel.ENTITY; openEntityWithNoAttributes(CmsConstants.CHILD_TOPICS); while (childTopicNodes.hasNext()){ serializeTopicNode(childTopicNodes.nextNode(), visitDepthForChildTopics, serializeTaxonomyNode, false, false, false); } closeEntity(CmsConstants.CHILD_TOPICS); } } closeEntity(topicQName); finishedEntitySerialization(CmsEntityType.TOPIC, topicQName); } } private void informContentHandlerWhetherEntityIsAnArray( boolean entityRepresentsAnArray) throws Exception { if (entityRepresentsAnArray && outputIsJSON()){ //This is an indication to JSON Export Content Handler that this object should be exported //as an array writeAttribute(CmsConstants.EXPORT_AS_AN_ARRAY_INSTRUCTION, "true"); } } private boolean shouldVisitChildren(FetchLevel fetchLevel) { return fetchLevel != null && fetchLevel != FetchLevel.ENTITY; } private boolean outputIsJSON() { return serializationConfiguration.isJSONRepresentationTypeEnabled(); } private void serializeChildNodes(Node node, boolean childNodesAreCmsProperties, FetchLevel fetchLevel) throws Exception { final NodeIterator childNodes = node.getNodes(); while (childNodes.hasNext()){ serializeNode(childNodes.nextNode(), childNodesAreCmsProperties, fetchLevel, false, false); } } private void serializeAspects(Node contentObjectJcrNode) throws Exception{ if (contentObjectJcrNode.hasProperty(CmsBuiltInItem.Aspects.getJcrName())){ Value[] aspects = contentObjectJcrNode.getProperty(CmsBuiltInItem.Aspects.getJcrName()).getValues(); for (Value aspect : aspects){ if (contentObjectJcrNode.hasNode(aspect.getString())){ serializeCustomNode(contentObjectJcrNode.getNode(aspect.getString()), true, true); } } } } private void serializeCustomNode(Node node, boolean nodeRepresentsCmsProperty, boolean nodeRepresentsAnAspect) throws Exception { final String nodeName = node.getName(); if (nodeRepresentsCmsProperty && ! shouldSerializeProperty(nodeName)){ return ; } String qName = processQName(nodeName); String nodeCmsIdentifier = retrieveCmsIdentifier(node); boolean removeDefinitionFromParentPropertyDefinitionQueue = false; if (cmsIdentifierAlreadyProcessed(nodeCmsIdentifier)){ serializeCmsRepositoryEntityIdentifierForAnAlreadySerializedEntity(qName, nodeCmsIdentifier); if (nodeRepresentsCmsProperty){ addToParentPropertyDefinitionQueueDefinitionForName(nodeName,false); removeDefinitionFromParentPropertyDefinitionQueue = true; } closeEntity(qName); } else{ if (! nodeRepresentsCmsProperty && qName.endsWith("repositoryUser")){ startedNewEntitySerialization(qName); } boolean exportCommonAttributes = ! nodeRepresentsCmsProperty || propertyDefinitionDefinesCommonAttributes(nodeName); if (nodeCmsIdentifier != null && exportCommonAttributes){ openEntityWithAttribute(qName, CmsBuiltInItem.CmsIdentifier.getLocalPart(), nodeCmsIdentifier); } else{ openEntityWithNoAttributes(qName); } if (nodeRepresentsAnAspect){ addXsiTypeAttribute(retrievePrefixedQName(qName)); } if (nodeRepresentsCmsProperty){ boolean multiple = propertyCanHaveMultiplevalues(nodeName); informContentHandlerWhetherEntityIsAnArray(multiple); if (node.isNodeType(CmsBuiltInItem.BinaryChannel.getJcrName())){ serializeBinaryChannelNode(node, multiple, nodeName); } else{ serializeChildCmsProperties(node); } } else{ serializeBuiltInProperties(node, false); serializeChildNodes(node, nodeRepresentsCmsProperty, FetchLevel.FULL); } closeEntity(qName); if (! nodeRepresentsCmsProperty && qName.endsWith("repositoryUser")){ finishedEntitySerialization(CmsEntityType.REPOSITORY_USER, qName); } } if (removeDefinitionFromParentPropertyDefinitionQueue){ removeTheHeadDefinitionFromParentPropertyDefinitionQueue(); } } private boolean propertyCanHaveMultiplevalues(String name) throws Exception { LocalizableCmsDefinition cmsDefinition = retrieveCmsDefinition(name,false); if (cmsDefinition == null){ logger.warn("Could not find definition for property {}. Cannot decide whether this property can have multiple values or not. Serialization will continue and consider this property as a single-value property", name); return false; } return cmsDefinition instanceof CmsPropertyDefinition && ((CmsPropertyDefinition)cmsDefinition).isMultiple(); } private boolean propertyDefinitionDefinesCommonAttributes(String name) throws Exception { LocalizableCmsDefinition cmsDefinition = retrieveCmsDefinition(name,false); if (cmsDefinition == null){ logger.warn("Could not find definition for property {}. Cannot decide whether this property defines common attributes or not. Serialization will continue and consider that this property does not define any common attribute"); return false; } return cmsDefinition.getValueType() == ValueType.Binary || (cmsDefinition.getValueType() == ValueType.Complex && ((ComplexCmsPropertyDefinitionImpl)cmsDefinition).commonAttributesAreDefined()); } private void serializeBinaryChannelNode(Node node, boolean multiple, String propertyName) throws Exception { Node contentObjectNode = retrieveContentObjectNodeFromNode(node); String mimeType = null; String sourceFilename = null; if (node.hasProperty(JcrBuiltInItem.JcrEncoding.getJcrName())){ writeAttribute(CmsBuiltInItem.Encoding.getLocalPart(), node.getProperty(JcrBuiltInItem.JcrEncoding.getJcrName()).getString()); } if (node.hasProperty(JcrBuiltInItem.JcrMimeType.getJcrName())){ mimeType = node.getProperty(JcrBuiltInItem.JcrMimeType.getJcrName()).getString(); writeAttribute(CmsBuiltInItem.MimeType.getLocalPart(), mimeType); } if (node.hasProperty(JcrBuiltInItem.JcrLastModified.getJcrName())){ String dateTime = convertCalendarToXMLFormat(node.getProperty(JcrBuiltInItem.JcrLastModified.getJcrName()).getDate(), true); writeAttribute("lastModificationDate", dateTime); } if (node.hasProperty(CmsBuiltInItem.SourceFileName.getJcrName())){ sourceFilename = node.getProperty(CmsBuiltInItem.SourceFileName.getJcrName()).getString(); writeAttribute(CmsBuiltInItem.SourceFileName.getLocalPart(), sourceFilename); } String url = createResourceApiURLForBinaryChannel(contentObjectNode, node, multiple, propertyName); writeAttribute(CmsConstants.URL_ATTRIBUTE_NAME, url); if (serializationConfiguration.serializeBinaryContent() && node.hasProperty(JcrBuiltInItem.JcrData.getJcrName())){ exportContentHandler.closeOpenElement(); openEntityWithNoAttributes("content"); exportContentHandler.closeOpenElement(); serializeBinaryValue(node.getProperty(JcrBuiltInItem.JcrData.getJcrName()).getValue()); closeEntity("content"); } } private String createResourceApiURLForBinaryChannel(Node contentObjectNode, Node binaryChannelNode, boolean multiple, String propertyName) throws Exception { //Create a fake BinaryChannel and use its method BinaryChannelImpl binaryChannel = new BinaryChannelImpl(); if (binaryChannelNode.hasProperty(CmsBuiltInItem.CmsIdentifier.getJcrName())){ binaryChannel.setId(binaryChannelNode.getProperty(CmsBuiltInItem.CmsIdentifier.getJcrName()).getString()); } binaryChannel.setAuthenticationToken(AstroboaClientContextHolder.getActiveAuthenticationToken()); binaryChannel.setRepositoryId(AstroboaClientContextHolder.getActiveRepositoryId()); if (contentObjectNode.hasProperty(CmsBuiltInItem.CmsIdentifier.getJcrName())){ binaryChannel.setContentObjectId(contentObjectNode.getProperty(CmsBuiltInItem.CmsIdentifier.getJcrName()).getString()); } if (contentObjectNode.hasProperty(CmsBuiltInItem.SystemName.getJcrName())){ binaryChannel.setContentObjectSystemName(contentObjectNode.getProperty(CmsBuiltInItem.SystemName.getJcrName()).getString()); } binaryChannel.setBinaryPropertyPermanentPath(retrievePermanentPathForBinaryNode(binaryChannelNode, propertyName)); if (multiple){ binaryChannel.binaryPropertyIsMultiValued(); } return binaryChannel.buildResourceApiURL(null, null, null, null, null, false, false); } private String retrievePermanentPathForBinaryNode(Node binaryChannelNode, String propertyName) throws Exception { String permanentPath = propertyName; LocalizableCmsDefinition binaryPropertyDefinition = retrieveCmsDefinition(propertyName,false); if (binaryPropertyDefinition == null || ! (binaryPropertyDefinition instanceof BinaryPropertyDefinition)){ logger.warn("Could not find definition for property {}. " + " Permanent path will be calculated based on binary channel node path"); } Node complexPropertyNode = binaryChannelNode.getParent(); CmsDefinition complexPropertyDefinition = (binaryPropertyDefinition != null && binaryPropertyDefinition instanceof BinaryPropertyDefinition) ?((BinaryPropertyDefinition)binaryPropertyDefinition).getParentDefinition() : null; boolean complexPropertyDefinitionIsMultiple = complexPropertyDefinition != null && complexPropertyDefinition instanceof CmsPropertyDefinition && ((CmsPropertyDefinition)complexPropertyDefinition).isMultiple(); while (complexPropertyNode != null && ! complexPropertyNode.isNodeType(CmsBuiltInItem.StructuredContentObject.getJcrName())){ if (! complexPropertyDefinitionIsMultiple){ permanentPath = complexPropertyNode.getName()+ CmsConstants.PERIOD_DELIM+ permanentPath; } else{ //Must provide identifier or index if (complexPropertyNode.hasProperty(CmsBuiltInItem.CmsIdentifier.getJcrName())){ permanentPath = complexPropertyNode.getName()+CmsConstants.LEFT_BRACKET+ complexPropertyNode.getProperty(CmsBuiltInItem.CmsIdentifier.getJcrName()).getString() + CmsConstants.RIGHT_BRACKET+ CmsConstants.PERIOD_DELIM+ permanentPath; } else{ //Get its index. Check for order property long index = 0; if (complexPropertyNode.hasProperty(CmsBuiltInItem.Order.getJcrName())){ index = complexPropertyNode.getProperty(CmsBuiltInItem.Order.getJcrName()).getLong(); } else { index = complexPropertyNode.getIndex(); } if (index > 0){ permanentPath = complexPropertyNode.getName()+CmsConstants.LEFT_BRACKET+ index + CmsConstants.RIGHT_BRACKET+ CmsConstants.PERIOD_DELIM+ permanentPath; } else{ permanentPath = complexPropertyNode.getName()+ CmsConstants.PERIOD_DELIM+ permanentPath; } } } complexPropertyNode = complexPropertyNode.getParent(); complexPropertyDefinition = (complexPropertyDefinition != null && complexPropertyDefinition instanceof CmsPropertyDefinition) ?((CmsPropertyDefinition)complexPropertyDefinition).getParentDefinition() : null; complexPropertyDefinitionIsMultiple = complexPropertyDefinition != null && complexPropertyDefinition instanceof CmsPropertyDefinition && ((CmsPropertyDefinition)complexPropertyDefinition).isMultiple(); } return permanentPath; } private Node retrieveContentObjectNodeFromNode(Node node) throws RepositoryException { Node contentObjectNode = node; while(contentObjectNode!= null && !contentObjectNode.isNodeType(CmsBuiltInItem.StructuredContentObject.getJcrName())){ contentObjectNode = contentObjectNode.getParent(); if (contentObjectNode == null || contentObjectNode.isNodeType(CmsBuiltInItem.SYSTEM.getJcrName())){ return null; } } return contentObjectNode; } private void serializeBinaryValue(Value value) throws Exception, ValueFormatException, RepositoryException, PathNotFoundException { String content = JackrabbitDependentUtils.serializeBinaryValue(value); exportContentHandler.writeContent(content.toCharArray(), 0, content.length()); } /* * Checks whether property should be serialized or not */ private boolean shouldSerializeProperty(String propertyName) { //Get property path String propertyPath = retrieveFullPathForProperty(propertyName); if (objectReferenceIsSerialized){ return MarshalUtils.propertyShouldBeMarshalled(propertyPathsWhoseValuesWillBeIncludedInTheSerializationOfObjectReferences, propertyName, propertyPath); } else{ return MarshalUtils.propertyShouldBeMarshalled(propertyPathsWhoseValuesWillBeIncludedInTheSerialization, propertyName, propertyPath); } } //Method responsible to serialize properties of // a complex cms property in the order specified in XSD //Child properties can either be a jcr property or a node private void serializeChildCmsProperties(Node node) throws Exception{ if (node == null){ logger.warn("Could not serialize a null jcr node"); return; } addToParentPropertyDefinitionQueueDefinitionForName(node.getName(), node.isNodeType(CmsBuiltInItem.StructuredContentObject.getJcrName())); Map<String, CmsPropertyDefinition> orderedChildCmsPropertyNames = retrieveChildPropertyDefinitionsPerName(node); if (orderedChildCmsPropertyNames != null && ! orderedChildCmsPropertyNames.isEmpty()){ long numberOfJcrPropertiesLeftToProcess = node.getProperties().getSize(); long numberOfJcrNodesLeftToProcess = node.getNodes().getSize(); String objectPath = createObjectPath(node); for (Entry<String, CmsPropertyDefinition> cmsPropertyDefinitionEntry : orderedChildCmsPropertyNames.entrySet()){ if (numberOfJcrNodesLeftToProcess == 0 && numberOfJcrPropertiesLeftToProcess == 0){ //No more jcr items left, thus no more properties exist for this node. //No point in iterating the rest of Cms Properties break; } String cmsPropertyName = cmsPropertyDefinitionEntry.getKey(); //TODO : Properties which have not been saved but have default value, //should exist in the serialization? This is the case when serialization is done through // xml() and json() methods.These methods use Astroboa API which renders a property //and provides the default value if this property is not found in repository //This case stands only for optional properties with default values if (cmsPropertyDefinitionEntry.getValue().getValueType() != ValueType.Complex && cmsPropertyDefinitionEntry.getValue().getValueType() != ValueType.Binary && numberOfJcrPropertiesLeftToProcess > 0){ if (node.hasProperty(cmsPropertyName)){ serializeProperty(node.getProperty(cmsPropertyName), objectPath); numberOfJcrPropertiesLeftToProcess--; } } else if (numberOfJcrNodesLeftToProcess > 0){ if (node.hasNode(cmsPropertyName)){ NodeIterator childNodes = node.getNodes(cmsPropertyName); if (childNodes.getSize() == 1){ serializeCustomNode(childNodes.nextNode(), true, false); numberOfJcrNodesLeftToProcess--; } else{ Map<Integer,Node> nodesPerOrder = new TreeMap<Integer, Node>(); int unknownIndex = 10000; while(childNodes.hasNext()){ Node childNode = childNodes.nextNode(); if (childNode.hasProperty(CmsBuiltInItem.Order.getJcrName())){ try{ int index = (int)childNode.getProperty(CmsBuiltInItem.Order.getJcrName()).getLong() - 1; if (nodesPerOrder.containsKey(index)){ nodesPerOrder.put(unknownIndex++,childNode); } else{ nodesPerOrder.put(index,childNode); } } catch(Exception e){ logger.warn("Node "+childNode.getPath()+" did not have a valid order value and therefore corresponding cms property will be added at the end of the list", e); nodesPerOrder.put(unknownIndex++,childNode); } } else{ nodesPerOrder.put(unknownIndex++,childNode); } } for (Node child : nodesPerOrder.values()){ serializeCustomNode(child, true, false); numberOfJcrNodesLeftToProcess--; } } } } } } else{ // Could not serialize properties in order. Follow the default procedure serializeProperties(node); serializeChildNodes(node, true, FetchLevel.FULL); } removeTheHeadDefinitionFromParentPropertyDefinitionQueue(); } private String createObjectPath(Node node) throws RepositoryException, PathNotFoundException { String objectPath = node.getPath(); if (node.isNodeType(CmsBuiltInItem.StructuredContentObject.getJcrName()) && node.hasProperty(CmsBuiltInItem.SystemName.getJcrName())){ objectPath+="[systemName="+node.getProperty(CmsBuiltInItem.SystemName.getJcrName())+"]"; } return objectPath; } private Map<String, CmsPropertyDefinition> retrieveChildPropertyDefinitionsPerName(Node node) throws Exception { LocalizableCmsDefinition currentDefinition = parentPropertyDefinitionQueue.peek(); if (currentDefinition != null){ Map<String, CmsPropertyDefinition> definitions = new LinkedHashMap<String, CmsPropertyDefinition>(); List<CmsPropertyDefinition> listOfDefinitionEntries = new ArrayList<CmsPropertyDefinition>(); if (currentDefinition instanceof ComplexCmsPropertyDefinition){ listOfDefinitionEntries.addAll(((ComplexCmsPropertyDefinition)currentDefinition).getChildCmsPropertyDefinitions().values()); } else if (currentDefinition instanceof ContentObjectTypeDefinition){ listOfDefinitionEntries.addAll(((ContentObjectTypeDefinition)currentDefinition).getPropertyDefinitions().values()); } //Sort entries Collections.sort(listOfDefinitionEntries, cmsPropertyDefinitionComparator); for (CmsPropertyDefinition definition : listOfDefinitionEntries){ definitions.put(definition.getName(), definition); } return definitions; } return null; } private void removeTheHeadDefinitionFromParentPropertyDefinitionQueue(){ parentPropertyDefinitionQueue.poll(); } private void addToParentPropertyDefinitionQueueDefinitionForName(String name, boolean nameRefersToAContentType) throws Exception{ LocalizableCmsDefinition cmsPropertyDefinition = retrieveCmsDefinition(name, nameRefersToAContentType); if (cmsPropertyDefinition != null){ parentPropertyDefinitionQueue.push(cmsPropertyDefinition); } } private LocalizableCmsDefinition retrieveCmsDefinition(String name, boolean nameRefersToAContentType) throws Exception { LocalizableCmsDefinition currentDefinition = parentPropertyDefinitionQueue.peek(); LocalizableCmsDefinition cmsPropertyDefinition = null; if (currentDefinition != null && ! nameRefersToAContentType){ if (currentDefinition instanceof ComplexCmsPropertyDefinition){ cmsPropertyDefinition = ((ComplexCmsPropertyDefinition)currentDefinition).getChildCmsPropertyDefinition(name); } else if (currentDefinition instanceof ContentObjectTypeDefinition){ cmsPropertyDefinition = ((ContentObjectTypeDefinition)currentDefinition).getCmsPropertyDefinition(name); } } else{ if (!nameRefersToAContentType){ cmsPropertyDefinition = definitionServiceDao.getCmsPropertyDefinition(name); } if (cmsPropertyDefinition == null){ cmsPropertyDefinition = definitionServiceDao.getContentObjectTypeDefinition(name); } } return cmsPropertyDefinition; } protected String processQName(String qName) { if (qName != null && qName.startsWith(BCCMS_PREFIX_WITH_SEMICOLON)){ return qName.replaceFirst(BCCMS_PREFIX_WITH_SEMICOLON, ""); } return qName; } private void processLocalization(Node node) throws Exception { if (node.hasNode(CmsBuiltInItem.Localization.getJcrName())){ Node localizationJcrNode = node.getNode(CmsBuiltInItem.Localization.getJcrName()); PropertyIterator locales = localizationJcrNode.getProperties(ItemUtils.createNewBetaConceptItem(CmsConstants.ANY_NAME).getJcrName()); if (locales.getSize() > 0){ openEntityWithNoAttributes(CmsBuiltInItem.Localization.getLocalPart()); if (outputIsJSON()){ serializeLocalizationForJSONFormat(locales); } else{ serializeLocalizationForXMLFormat(locales); } closeEntity(CmsBuiltInItem.Localization.getLocalPart()); } } } private void serializeLocalizationForXMLFormat(PropertyIterator locales) throws RepositoryException, ValueFormatException, Exception { while ( locales.hasNext() ){ Property localeProperty = locales.nextProperty(); String locale = localeProperty.getName().replaceAll(BetaConceptNamespaceConstants.ASTROBOA_PREFIX+":", ""); String localizedLabel = localeProperty.getString(); openEntityWithAttribute(CmsConstants.LOCALIZED_LABEL_ELEMENT_NAME, CmsConstants.LANG_ATTRIBUTE_NAME_WITH_PREFIX, locale); char[] charArray = localizedLabel.toCharArray(); writeElementContent(charArray); closeEntity(CmsConstants.LOCALIZED_LABEL_ELEMENT_NAME); } } private void serializeLocalizationForJSONFormat(PropertyIterator locales) throws RepositoryException, ValueFormatException, Exception { openEntityWithNoAttributes(CmsConstants.LOCALIZED_LABEL_ELEMENT_NAME); while ( locales.hasNext() ){ Property localeProperty = locales.nextProperty(); String locale = localeProperty.getName().replaceAll(BetaConceptNamespaceConstants.ASTROBOA_PREFIX+":", ""); String localizedLabel = localeProperty.getString(); writeAttribute(locale, localizedLabel); } closeEntity(CmsConstants.LOCALIZED_LABEL_ELEMENT_NAME); } public void serializeTaxonomyNode(Node node, FetchLevel fetchLevel, boolean nodeRepresentsResourceCollectionItem, boolean nodeRepresentsRootElement) throws Exception{ String taxonomyCmsIdentifier = retrieveCmsIdentifier(node); String taxonomyQName = CmsBuiltInItem.Taxonomy.getLocalPart(); if (nodeRepresentsRootElement){ taxonomyQName = BetaConceptNamespaceConstants.ASTROBOA_MODEL_DEFINITION_PREFIX +":"+ CmsBuiltInItem.Taxonomy.getLocalPart(); } if (nodeRepresentsResourceCollectionItem){ if (outputIsJSON()){ //When serializing a collection of resources //the name of the element which represents the taxonomy //is CmsConstants.RESOURCE when output is JSON taxonomyQName = CmsConstants.RESOURCE; } else{ //Node represents a taxonomy which appears //as a root child of a resource representation element. //In XML corresponding element should be prefixed //as its parent (resourceCollection) does not share the //same namespace taxonomyQName = BetaConceptNamespaceConstants.ASTROBOA_MODEL_DEFINITION_PREFIX +":"+ CmsBuiltInItem.Taxonomy.getLocalPart(); } } if (cmsIdentifierAlreadyProcessed(taxonomyCmsIdentifier)){ serializeCmsRepositoryEntityIdentifierForAnAlreadySerializedEntity(taxonomyQName, taxonomyCmsIdentifier); //Taxonomy name is the name of the node. Very SPECIAL CASE writeAttribute(CmsBuiltInItem.Name.getLocalPart(), node.getName()); addUrlForEntityRepresentedByNode(node); processLocalization(node); closeEntity(taxonomyQName); } else{ startedNewEntitySerialization(taxonomyQName); openEntityWithAttribute(taxonomyQName, CmsBuiltInItem.CmsIdentifier.getLocalPart(), taxonomyCmsIdentifier); markCmsIdentifierProcessed(taxonomyCmsIdentifier); //Taxonomy name is the name of the node. Very SPECIAL CASE writeAttribute(CmsBuiltInItem.Name.getLocalPart(), node.getName()); serializeBuiltInProperties(node, false); if (shouldVisitChildren(fetchLevel) && node.hasNode(CmsBuiltInItem.Topic.getJcrName())){ NodeIterator rootTopicNodes = node.getNodes(CmsBuiltInItem.Topic.getJcrName()); if (rootTopicNodes.getSize() > 0){ openEntityWithNoAttributes(CmsConstants.ROOT_TOPICS); FetchLevel visitDepthForRootTopics = fetchLevel == FetchLevel.FULL ? FetchLevel.FULL : FetchLevel.ENTITY; while (rootTopicNodes.hasNext()){ serializeTopicNode(rootTopicNodes.nextNode(), visitDepthForRootTopics, true, false, false, false); } closeEntity(CmsConstants.ROOT_TOPICS); } } closeEntity(taxonomyQName); finishedEntitySerialization(CmsEntityType.TAXONOMY, taxonomyQName); } } public void serializeContentObjectNode(Node node, boolean nodeRepresentsResourceCollectionItem) throws Exception{ String cmsIdentifier = retrieveCmsIdentifier(node); String contentObjectQName = retrievePrefixedQName(node.getName()); if (nodeRepresentsResourceCollectionItem){ //When serializing a collection of resources //the name of the element which represents the content object //is CmsConstants.RESOURCE when output is JSON or when //user has requested that all xml elements which represent the content object //will be named after the same name, which is the name "resource" if (outputIsJSON()||useTheSameNameForAllObjects){ contentObjectQName = CmsConstants.RESOURCE; } } if (cmsIdentifierAlreadyProcessed(cmsIdentifier)){ serializeCmsRepositoryEntityIdentifierForAnAlreadySerializedEntity(contentObjectQName, cmsIdentifier); serializeBasicContentObjectInformation(cmsIdentifier, node); closeEntity(contentObjectQName); } else{ startedNewEntitySerialization(contentObjectQName); openEntityWithAttribute(contentObjectQName, CmsBuiltInItem.CmsIdentifier.getLocalPart(), cmsIdentifier); markCmsIdentifierProcessed(cmsIdentifier); //addXsiTypeAttribute(retrievePrefixedQName(contentObjectQName)); serializeBuiltInProperties(node, false); serializeChildCmsProperties(node); serializeAspects(node); closeEntity(contentObjectQName); finishedEntitySerialization(CmsEntityType.OBJECT, contentObjectQName); } } private void finishedEntitySerialization(CmsEntityType cmsEntity, String qName) { //if (StringUtils.equals(qNameOfEntityCurrentlySerialized, qName)){ increaseNumberOfSerializedEntities(cmsEntity); // qNameOfEntityCurrentlySerialized = null; //} } private void startedNewEntitySerialization(String qName) { //if (qNameOfEntityCurrentlySerialized == null){ // qNameOfEntityCurrentlySerialized = qName; //} } private void addXsiTypeAttribute(String qName) throws Exception { if (!outputIsJSON()){ writeAttribute("xsi:type", qName); } } private void increaseNumberOfSerializedEntities(CmsEntityType entity) { if (serializationReport !=null) { switch (entity) { case TAXONOMY: ((SerializationReportImpl)serializationReport).increaseNumberOfCompletedSerializedTaxonomies(1); break; case OBJECT: ((SerializationReportImpl)serializationReport).increaseNumberOfCompletedSerializedObjects(1); break; case REPOSITORY_USER: ((SerializationReportImpl)serializationReport).increaseNumberOfCompletedSerializedRepositoryUsers(1); break; case SPACE: ((SerializationReportImpl)serializationReport).increaseNumberOfCompletedSerializedSpaces(1); break; case TOPIC: ((SerializationReportImpl)serializationReport).increaseNumberOfCompletedSerializedTopics(1); break; default: break; } } } private String retrievePrefixedQName(String qName) { if (prefixesPerType != null && qName != null && prefixesPerType.containsKey(qName)){ return prefixesPerType.get(qName)+CmsConstants.QNAME_PREFIX_SEPARATOR+qName; } return qName; } private String retrieveCmsIdentifier(Node node) throws RepositoryException { String cmsIdentifier = null; if (cmsRepositoryEntityUtils.hasCmsIdentifier(node)){ cmsIdentifier = cmsRepositoryEntityUtils.getCmsIdentifier(node); } return cmsIdentifier; } private boolean cmsIdentifierAlreadyProcessed(String cmsIdentifier) { return cmsIdentifier != null && processedCmsRepositoryEntityIdentifiers.contains(cmsIdentifier); } private void serializeBuiltInProperties(Node node, boolean nodeRepresentsParentNodeOfTopicOrSpace) throws Exception { PropertyIterator builtInProperties = node.getProperties(BCCMS_PREFIX_WITH_SEMICOLON+CmsConstants.ANY_NAME); boolean addOwner = false; while (builtInProperties.hasNext()){ Property property = builtInProperties.nextProperty(); if (property.getName().equals(CmsBuiltInItem.Name.getJcrName())){ writeAttribute(CmsBuiltInItem.Name.getLocalPart(), property.getString()); } else if (property.getName().equals(CmsBuiltInItem.SystemName.getJcrName())){ writeAttribute(CmsBuiltInItem.SystemName.getLocalPart(), property.getString()); } else if (property.getName().equals(CmsBuiltInItem.ContentObjectTypeName.getJcrName())){ writeAttribute(CmsBuiltInItem.ContentObjectTypeName.getLocalPart(), property.getString()); } else if (property.getName().equals(CmsBuiltInItem.AllowsReferrerContentObjects.getJcrName())){ if (! nodeRepresentsParentNodeOfTopicOrSpace){ writeAttribute(CmsBuiltInItem.AllowsReferrerContentObjects.getLocalPart(), String.valueOf(property.getBoolean())); } } else if (property.getName().equals(CmsBuiltInItem.Encoding.getJcrName())){ writeAttribute(CmsBuiltInItem.Encoding.getLocalPart(), property.getString()); } else if (property.getName().equals(CmsBuiltInItem.ExternalId.getJcrName())){ writeAttribute(CmsBuiltInItem.ExternalId.getLocalPart(), property.getString()); } else if (property.getName().equals(CmsBuiltInItem.Label.getJcrName())){ writeAttribute(CmsBuiltInItem.Label.getLocalPart(), property.getString()); } else if (property.getName().equals(CmsBuiltInItem.MimeType.getJcrName())){ writeAttribute(CmsBuiltInItem.MimeType.getLocalPart(), property.getString()); } else if (property.getName().equals(CmsBuiltInItem.Order.getJcrName()) && (node.isNodeType(CmsBuiltInItem.Space.getJcrName()) || node.isNodeType(CmsBuiltInItem.Topic.getJcrName())) ){ if (!nodeRepresentsParentNodeOfTopicOrSpace){ writeAttribute(CmsBuiltInItem.Order.getLocalPart(), property.getString()); } } else if (property.getName().equals(CmsBuiltInItem.SourceFileName.getJcrName())){ writeAttribute(CmsBuiltInItem.SourceFileName.getLocalPart(), property.getString()); } else if (property.getName().equals(CmsBuiltInItem.Size.getJcrName())){ writeAttribute(CmsBuiltInItem.Size.getLocalPart(), property.getString()); } else if (property.getName().equals(CmsBuiltInItem.OwnerCmsIdentifier.getJcrName())){ //Do not add owner if entity to be serialized is content object and specific //properties must be serialized if (CollectionUtils.isEmpty(propertyPathsWhoseValuesWillBeIncludedInTheSerialization) || propertyPathsWhoseValuesWillBeIncludedInTheSerialization.contains(CmsConstants.OWNER_ELEMENT_NAME)){ addOwner = true; } } } //Add url for this node addUrlForEntityRepresentedByNode(node); if (!nodeRepresentsParentNodeOfTopicOrSpace){ addNumberOfChildren(node); } processLocalization(node); if (addOwner){ addOwnerAsElement(node); } } private void addUrlForEntityRepresentedByNode(Node node) throws Exception { UrlProperties urlProperties = new UrlProperties(); urlProperties.setRelative(false); urlProperties.setResourceRepresentationType(serializationConfiguration.getResourceRepresentationType()); if (node.isNodeType(CmsBuiltInItem.StructuredContentObject.getJcrName())){ if (node.hasProperty(CmsBuiltInItem.SystemName.getJcrName())){ urlProperties.setFriendly(true); urlProperties.setName(node.getProperty(CmsBuiltInItem.SystemName.getJcrName()).getString()); writeAttribute(CmsConstants.URL_ATTRIBUTE_NAME, ResourceApiURLUtils.generateUrlForType(ContentObject.class, urlProperties)); } else{ urlProperties.setFriendly(false); urlProperties.setIdentifier(cmsRepositoryEntityUtils.getCmsIdentifier(node)); writeAttribute(CmsConstants.URL_ATTRIBUTE_NAME, ResourceApiURLUtils.generateUrlForType(ContentObject.class, urlProperties)); } } else if (node.isNodeType(CmsBuiltInItem.Topic.getJcrName())){ if (node.hasProperty(CmsBuiltInItem.Name.getJcrName())){ urlProperties.setFriendly(true); urlProperties.setName(node.getProperty(CmsBuiltInItem.Name.getJcrName()).getString()); writeAttribute(CmsConstants.URL_ATTRIBUTE_NAME, ResourceApiURLUtils.generateUrlForType(Topic.class, urlProperties)); } else{ urlProperties.setFriendly(false); urlProperties.setIdentifier(cmsRepositoryEntityUtils.getCmsIdentifier(node)); writeAttribute(CmsConstants.URL_ATTRIBUTE_NAME, ResourceApiURLUtils.generateUrlForType(Topic.class, urlProperties)); } } else if (node.isNodeType(CmsBuiltInItem.Space.getJcrName())){ if (node.hasProperty(CmsBuiltInItem.Name.getJcrName())){ urlProperties.setFriendly(true); urlProperties.setName(node.getProperty(CmsBuiltInItem.Name.getJcrName()).getString()); writeAttribute(CmsConstants.URL_ATTRIBUTE_NAME, ResourceApiURLUtils.generateUrlForType(Space.class, urlProperties)); } else{ urlProperties.setFriendly(false); urlProperties.setIdentifier(cmsRepositoryEntityUtils.getCmsIdentifier(node)); writeAttribute(CmsConstants.URL_ATTRIBUTE_NAME, ResourceApiURLUtils.generateUrlForType(Space.class, urlProperties)); } } else if (node.isNodeType(CmsBuiltInItem.Taxonomy.getJcrName())){ urlProperties.setFriendly(true); urlProperties.setName(node.getName()); writeAttribute(CmsConstants.URL_ATTRIBUTE_NAME, ResourceApiURLUtils.generateUrlForType(Taxonomy.class, urlProperties)); } } private void addNumberOfChildren(Node node) throws RepositoryException, Exception { if (node.isNodeType(CmsBuiltInItem.Topic.getJcrName()) || node.isNodeType(CmsBuiltInItem.Taxonomy.getJcrName())){ if (node.hasNode(CmsBuiltInItem.Topic.getJcrName())){ writeAttribute(CmsConstants.NUMBER_OF_CHILDREN_ATTRIBUTE_NAME, String.valueOf(node.getNodes(CmsBuiltInItem.Topic.getJcrName()).getSize())); } else{ writeAttribute(CmsConstants.NUMBER_OF_CHILDREN_ATTRIBUTE_NAME, "0"); } } else if ( node.isNodeType(CmsBuiltInItem.Space.getJcrName()) ){ if (node.hasNode(CmsBuiltInItem.Space.getJcrName())){ writeAttribute(CmsConstants.NUMBER_OF_CHILDREN_ATTRIBUTE_NAME, String.valueOf(node.getNodes(CmsBuiltInItem.Space.getJcrName()).getSize())); } else{ writeAttribute(CmsConstants.NUMBER_OF_CHILDREN_ATTRIBUTE_NAME, "0"); } } } private void serializeProperties(Node node) throws Exception{ PropertyIterator properties = node.getProperties(); String objectPath = createObjectPath(node); while (properties.hasNext()){ Property property = properties.nextProperty(); //Do not process any property which starts with jcr, nt or bccms String propertyName = property.getName(); if (propertyName.startsWith(JCR_MIX_PREFIX_WITH_SEMI_COLON) || propertyName.startsWith(JCR_PREFIX_WITH_SEMI_COLON) || propertyName.startsWith(NT_PREFIX_WITH_SEMI_COLON) || propertyName.startsWith(BCCMS_PREFIX_WITH_SEMICOLON) ){ continue; } serializeProperty(property, objectPath); } } private void serializeProperty(Property property, String objectPath) throws Exception{ String propertyName = property.getName(); if (shouldSerializeProperty(propertyName)){ CmsPropertyDefinition propertyDefinition = retrieveDefinitionForChildProperty(propertyName); if (propertyDefinition == null){ logger.warn("Could not serialize property {}. No definition found. Property path in JCR is {}", propertyName, property.getPath()); return; } final ValueType valueType = propertyDefinition.getValueType(); boolean dateTimePattern = propertyDefinition != null && ValueType.Date == valueType && ((CalendarPropertyDefinition)propertyDefinition).isDateTime(); /* * There are some cases where users might change property's cardinality * from multiple to single value or vice versa. * If user does not perform any kind of migration, there will be an inconsistency in the way * property values are stored in JCR. * For example, if a user decides to change property 'language' from single to multi value, * then new values will be stored inside an array in contrary to the old values. * * JCR specifies different methods for retrieving values of a property (getValue() for single * and getValues() for multiple) and it throws an exception if you call getValues() on a * single value property. * * In our example, in order to get old values you need to call method getValue() and in order * to retrieve new values you need to call getValues()!!! This is due to the fact that JCR * stores property definition among other things. * * We must detect this type of inconsistency but instead of throwing an exception, * we detect which method we should call and just issue a warning. In the case where * cardinality is changed from multivalue to single value, we serialize only the first value * because we must conform to the property' definition in the XSD. */ boolean userHasDefinedPropertyAsMultiple = propertyDefinition != null && propertyDefinition.isMultiple(); boolean jcrHasMarkedPropertyAsMultiple = property.getDefinition() != null && property.getDefinition().isMultiple(); boolean exportAsAnAttribute = propertyDefinition instanceof SimpleCmsPropertyDefinition && ((SimpleCmsPropertyDefinitionImpl)propertyDefinition).isRepresentsAnXmlAttribute(); if (jcrHasMarkedPropertyAsMultiple){ for (Value value : property.getValues()){ writeValueForProperty(propertyName, value, valueType, dateTimePattern, userHasDefinedPropertyAsMultiple, objectPath,exportAsAnAttribute); if (!userHasDefinedPropertyAsMultiple){ logger.warn("Property "+propertyDefinition.getFullPath() + " has been defined as a single value property. Property's instance " + property.getPath() + " has been marked as multiple. Probably this property used to be a multi value property but its cardinality" + " has changed. Astroboa will serialize only the first value in order to be consistent with current definition in XSD." ); break; } } } else{ writeValueForProperty(propertyName, property.getValue(), valueType, dateTimePattern, userHasDefinedPropertyAsMultiple,objectPath,exportAsAnAttribute); if (userHasDefinedPropertyAsMultiple){ logger.warn("Property "+propertyDefinition.getFullPath() + " has been defined as a multi value property. Property's instance " + property.getPath() + " has been marked as single. Probably this property used to be a single value property but its cardinality" + " has changed. Property value will be serialized normally" ); } } } } private String retrieveFullPathForProperty(String propertyName){ if (!parentPropertyDefinitionQueue.isEmpty()){ LocalizableCmsDefinition currentDefinition = parentPropertyDefinitionQueue.peek(); if (currentDefinition instanceof CmsPropertyDefinition){ CmsPropertyDefinition rootDefinition = (CmsPropertyDefinition) currentDefinition; boolean rootDefinitionIsATypeDefinition = false; while (rootDefinition != null){ if (rootDefinition.getParentDefinition() instanceof ContentObjectTypeDefinition){ rootDefinitionIsATypeDefinition = true; break; } rootDefinition = (CmsPropertyDefinition) rootDefinition.getParentDefinition(); } if (rootDefinitionIsATypeDefinition){ return PropertyPath.createFullPropertyPath(((CmsPropertyDefinition)currentDefinition).getPath(), propertyName); } else { return PropertyPath.createFullPropertyPath(((CmsPropertyDefinition)currentDefinition).getFullPath(), propertyName); } } } return propertyName; } private CmsPropertyDefinition retrieveDefinitionForChildProperty(String propertyName) { if (!parentPropertyDefinitionQueue.isEmpty()){ LocalizableCmsDefinition currentDefinition = parentPropertyDefinitionQueue.peek(); if (currentDefinition instanceof ComplexCmsPropertyDefinition){ return ((ComplexCmsPropertyDefinition)currentDefinition).getChildCmsPropertyDefinition(propertyName); } else if (currentDefinition instanceof ContentObjectTypeDefinition){ return ((ContentObjectTypeDefinition)currentDefinition).getCmsPropertyDefinition(propertyName); } } return null; } private String convertCalendarToXMLFormat(Calendar calendar, boolean dateTimePattern){ if (dateTimePattern){ GregorianCalendar gregCalendar = new GregorianCalendar(calendar.getTimeZone()); gregCalendar.setTimeInMillis(calendar.getTimeInMillis()); return df.newXMLGregorianCalendar(gregCalendar).toXMLFormat(); } else{ return df.newXMLGregorianCalendarDate( calendar.get( Calendar.YEAR ), calendar.get( Calendar.MONTH )+1, // Calendar.MONTH is zero based, XSD Date datatype's month field starts // with JANUARY as 1. calendar.get( Calendar.DAY_OF_MONTH ), DatatypeConstants.FIELD_UNDEFINED ).toXMLFormat(); } } private void writeValueForProperty(String propertyName, Value value, ValueType valueType, boolean dateTimePattern, boolean propertyMayHaveMultipltValues, String objectPath, boolean exportAsAnAttribute) throws Exception { if (value != null){ if (exportAsAnAttribute){ if (ValueType.Date == valueType){ String dateValue = convertCalendarToXMLFormat(value.getDate(), dateTimePattern); writeAttribute(propertyName, dateValue); } else{ writeAttribute(propertyName, value.getString()); } } else{ if (ValueType.TopicReference == valueType){ addTopicReferenceElement(propertyName, value.getString(), propertyMayHaveMultipltValues,objectPath); } else if (ValueType.ObjectReference == valueType){ addContentObjectReferenceElement(propertyName, value.getString(), propertyMayHaveMultipltValues, objectPath); } else{ if (propertyMayHaveMultipltValues && outputIsJSON()){ openEntityWithAttribute(propertyName, CmsConstants.EXPORT_AS_AN_ARRAY_INSTRUCTION, "true"); } else{ openEntityWithNoAttributes(propertyName); } if (ValueType.Binary == valueType){ exportContentHandler.closeOpenElement(); serializeBinaryValue(value); } else if (ValueType.Date == valueType){ char[] ch = convertCalendarToXMLFormat(value.getDate(), dateTimePattern).toCharArray(); writeElementContent(ch); } else { char[] ch = value.getString().toCharArray(); writeElementContent(ch); } closeEntity(propertyName); } } } } private void writeElementContent(char[] ch) throws Exception { exportContentHandler.writeContent(ch, 0, ch.length); } private void addTopicReferenceElement(String propertyName, String topicId, boolean referenceMayHaveMultipleValues, String objectPath) throws Exception{ if (cmsIdentifierAlreadyProcessed(topicId)){ serializeCmsRepositoryEntityIdentifierForAnAlreadySerializedEntity(propertyName, topicId); } else{ openEntityWithAttribute(propertyName, CmsBuiltInItem.CmsIdentifier.getLocalPart(), topicId); } Node topicJcrNode = cmsRepositoryEntityUtils.retrieveUniqueNodeForTopic(session, topicId); if (topicJcrNode == null){ logger.warn("Could not serialize value {} for property {} of object {} because no topic was found with this identifier '{}'", new Object[]{topicId, propertyName, objectPath, topicId}); return; } markCmsIdentifierProcessed(topicId); if (referenceMayHaveMultipleValues){ informContentHandlerWhetherEntityIsAnArray(true); } serializeBasicTopicInformation(topicJcrNode); closeEntity(propertyName); } private void serializeBasicTopicInformation(Node topicJcrNode) throws RepositoryException, Exception, ValueFormatException, PathNotFoundException { if (topicJcrNode != null){ if (topicJcrNode.hasProperty(CmsBuiltInItem.Name.getJcrName())){ writeAttribute(CmsBuiltInItem.Name.getLocalPart(), topicJcrNode.getProperty(CmsBuiltInItem.Name.getJcrName()).getString()); } addUrlForEntityRepresentedByNode(topicJcrNode); processLocalization(topicJcrNode); } } private void addContentObjectReferenceElement(String propertyName, String contentObjectId, boolean referenceMayHaveMultipleValues, String objectPath) throws Exception { Node contentObjectJcrNode = cmsRepositoryEntityUtils.retrieveUniqueNodeForContentObject(session, contentObjectId); if (contentObjectJcrNode == null){ logger.warn("Could not serialize value {} for property {} of object {} because no object was found with this identifier '{}'", new Object[]{contentObjectId, propertyName, objectPath, contentObjectId}); return; } openEntityWithAttribute(propertyName, CmsBuiltInItem.CmsIdentifier.getLocalPart(), contentObjectId); if (referenceMayHaveMultipleValues){ informContentHandlerWhetherEntityIsAnArray(true); } serializeBasicContentObjectInformation(contentObjectId, contentObjectJcrNode); objectReferenceIsSerialized = true; serializeChildCmsProperties(contentObjectJcrNode); serializeAspects(contentObjectJcrNode); objectReferenceIsSerialized = false; closeEntity(propertyName); } private void serializeBasicContentObjectInformation(String contentObjectId, Node contentObjectJcrNode) throws Exception, RepositoryException, ValueFormatException, PathNotFoundException { boolean systemNameFound = false; UrlProperties urlProperties = new UrlProperties(); urlProperties.setRelative(false); urlProperties.setResourceRepresentationType(serializationConfiguration.getResourceRepresentationType()); if (contentObjectJcrNode != null){ //ContentObject SystemName if (contentObjectJcrNode.hasProperty(CmsBuiltInItem.SystemName.getJcrName())){ final String systemName = contentObjectJcrNode.getProperty(CmsBuiltInItem.SystemName.getJcrName()).getString(); writeAttribute(CmsBuiltInItem.SystemName.getLocalPart(), systemName); urlProperties.setFriendly(true); urlProperties.setName(systemName); writeAttribute(CmsConstants.URL_ATTRIBUTE_NAME, ResourceApiURLUtils.generateUrlForType(ContentObject.class, urlProperties)); systemNameFound = true; } //ContentObjecType name if (contentObjectJcrNode.hasProperty(CmsBuiltInItem.ContentObjectTypeName.getJcrName())){ writeAttribute(CmsBuiltInItem.ContentObjectTypeName.getLocalPart(), contentObjectJcrNode.getProperty(CmsBuiltInItem.ContentObjectTypeName.getJcrName()).getString()); } } if (!systemNameFound){ urlProperties.setFriendly(false); urlProperties.setIdentifier(contentObjectId); writeAttribute(CmsConstants.URL_ATTRIBUTE_NAME, ResourceApiURLUtils.generateUrlForType(ContentObject.class, urlProperties )); } } private void addOwnerAsElement(Node node) throws Exception { String ownerCmsIdentifier = null; if (node.hasProperty(CmsBuiltInItem.OwnerCmsIdentifier.getJcrName())){ ownerCmsIdentifier = node.getProperty(CmsBuiltInItem.OwnerCmsIdentifier.getJcrName()).getString(); } if (ownerCmsIdentifier != null){ if (cmsIdentifierAlreadyProcessed(ownerCmsIdentifier)){ serializeCmsRepositoryEntityIdentifierForAnAlreadySerializedEntity(CmsConstants.OWNER_ELEMENT_NAME, ownerCmsIdentifier); } else{ openEntityWithAttribute(CmsConstants.OWNER_ELEMENT_NAME, CmsBuiltInItem.CmsIdentifier.getLocalPart(), ownerCmsIdentifier); markCmsIdentifierProcessed(ownerCmsIdentifier); } Node ownerJcrNode = cmsRepositoryEntityUtils.retrieveUniqueNodeForRepositoryUser(session, ownerCmsIdentifier); if (ownerJcrNode != null){ if (ownerJcrNode.hasProperty(CmsBuiltInItem.ExternalId.getJcrName())){ writeAttribute(CmsBuiltInItem.ExternalId.getLocalPart(), ownerJcrNode.getProperty(CmsBuiltInItem.ExternalId.getJcrName()).getString()); } if (ownerJcrNode.hasProperty(CmsBuiltInItem.Label.getJcrName())){ writeAttribute(CmsBuiltInItem.Label.getLocalPart(), ownerJcrNode.getProperty(CmsBuiltInItem.Label.getJcrName()).getString()); } } closeEntity(CmsConstants.OWNER_ELEMENT_NAME); } } private void markCmsIdentifierProcessed(String cmsIdentifier) { processedCmsRepositoryEntityIdentifiers.add(cmsIdentifier); } private NodeType determineNodeType(Node node) throws Exception { if (node.isNodeType(CmsBuiltInItem.StructuredContentObject.getJcrName())){ return NodeType.ContentObject; } else if ( node.isNodeType(CmsBuiltInItem.GenericYearFolder.getJcrName()) || node.isNodeType(CmsBuiltInItem.GenericMonthFolder.getJcrName()) || node.isNodeType(CmsBuiltInItem.GenericDayFolder.getJcrName()) || node.isNodeType(CmsBuiltInItem.GenericHourFolder.getJcrName()) || node.isNodeType(CmsBuiltInItem.GenericMinuteFolder.getJcrName()) || node.isNodeType(CmsBuiltInItem.GenericSecondFolder.getJcrName()) || node.isNodeType(CmsBuiltInItem.ContentObjectRoot.getJcrName()) || node.isNodeType(CmsBuiltInItem.RepositoryUserRoot.getJcrName()) || node.isNodeType(CmsBuiltInItem.TaxonomyRoot.getJcrName()) ){ return NodeType.ToBeIgnored; } else if (node.isNodeType(CmsBuiltInItem.Taxonomy.getJcrName())){ return NodeType.Taxonomy; } else if (node.isNodeType(CmsBuiltInItem.Topic.getJcrName())){ return NodeType.Topic; } else if (node.isNodeType(CmsBuiltInItem.Space.getJcrName())){ return NodeType.Space; } if (node.getParent() !=null && StringUtils.equals(node.getParent().getName(), CmsBuiltInItem.ContentObjectRoot.getJcrName())){ return NodeType.ToBeIgnored; } return NodeType.Other; } private void serializeCmsRepositoryEntityIdentifierForAnAlreadySerializedEntity(String entityName, String entityId) throws Exception{ openEntityWithAttribute(entityName, CmsBuiltInItem.CmsIdentifier.getLocalPart(), entityId); } public void writeAttribute(String attributeName, String attributeValue) throws Exception{ exportContentHandler.writeAttribute(attributeName, attributeValue); } public void openEntityWithNoAttributes(String elementQName) throws Exception{ if (rootElementAttributes != null){ exportContentHandler.startElement(elementQName, rootElementAttributes); rootElementAttributes = null; } else{ exportContentHandler.startElementWithNoAttributes(elementQName); } } public void openEntityWithAttribute(String elementQName, String attributeName, String attributeValue) throws Exception{ if (rootElementAttributes != null){ IOUtils.addAttribute(rootElementAttributes, attributeName, attributeValue); exportContentHandler.startElement(elementQName,rootElementAttributes); rootElementAttributes = null; } else{ exportContentHandler.startElementWithOnlyOneAttribute(elementQName, attributeName, attributeValue); } } public void openEntity(String elementQName, AttributesImpl atts) throws Exception{ if (rootElementAttributes != null){ for (int i=0;i<atts.getLength(); i++){ IOUtils.addAttribute(rootElementAttributes, atts.getLocalName(i), atts.getValue(i)); } exportContentHandler.startElement(elementQName, rootElementAttributes); rootElementAttributes = null; } else{ exportContentHandler.startElement(elementQName, atts); } } public void closeEntity(String elementQName) throws Exception{ exportContentHandler.endElement(elementQName); } public void setPrefixesPerType(Map<String, String> prefixesPerType){ this.prefixesPerType = prefixesPerType; } public void setSerializationReport(SerializationReport serializationReport){ this.serializationReport = serializationReport; } public void setDefinitionServiceDao(DefinitionServiceDao definitionServiceDao){ this.definitionServiceDao = definitionServiceDao; } public void setPropertyPathsWhoseValuesWillBeIncludedInTheSerialization(List<String> propertyPathsWhoseValuesWillBeIncludedInTheSerialization) { this.propertyPathsWhoseValuesWillBeIncludedInTheSerialization = propertyPathsWhoseValuesWillBeIncludedInTheSerialization; } public void useTheSameNameForAllObjects( boolean useTheSameNameForAllObjects) { this.useTheSameNameForAllObjects = useTheSameNameForAllObjects; } }