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