org.alfresco.repo.exporter.ViewXMLExporter.java Source code

Java tutorial

Introduction

Here is the source code for org.alfresco.repo.exporter.ViewXMLExporter.java

Source

/*
 * #%L
 * Alfresco Repository
 * %%
 * Copyright (C) 2005 - 2016 Alfresco Software Limited
 * %%
 * This file is part of the Alfresco software. 
 * If the software was purchased under a paid Alfresco license, the terms of 
 * the paid license agreement will prevail.  Otherwise, the software is 
 * provided under the following open source license terms:
 * 
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Alfresco is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */
package org.alfresco.repo.exporter;

import java.io.InputStream;
import java.util.List;
import java.util.Locale;

import org.alfresco.model.ContentModel;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.dictionary.PropertyDefinition;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.ContentData;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.Path;
import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
import org.alfresco.service.cmr.search.SearchService;
import org.alfresco.service.cmr.security.AccessPermission;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.cmr.view.Exporter;
import org.alfresco.service.cmr.view.ExporterContext;
import org.alfresco.service.cmr.view.ExporterException;
import org.alfresco.service.cmr.view.ReferenceType;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.ISO9075;
import org.apache.commons.lang.ArrayUtils;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;

/**
 * Exporter that exports Repository information to XML (Alfresco Repository View Schema)
 * 
 * @author David Caruana
 */
/*package*/ class ViewXMLExporter implements Exporter {
    // Repository View Schema Definitions
    private static final String VIEW_LOCALNAME = "view";
    private static final String VALUES_LOCALNAME = "values";
    private static final String VALUE_LOCALNAME = "value";
    private static final String CHILDNAME_LOCALNAME = "childName";
    private static final String ASPECTS_LOCALNAME = "aspects";
    private static final String PROPERTIES_LOCALNAME = "properties";
    private static final String ASSOCIATIONS_LOCALNAME = "associations";
    private static final String DATATYPE_LOCALNAME = "datatype";
    private static final String ISNULL_LOCALNAME = "isNull";
    private static final String METADATA_LOCALNAME = "metadata";
    private static final String EXPORTEDBY_LOCALNAME = "exportBy";
    private static final String EXPORTEDDATE_LOCALNAME = "exportDate";
    private static final String EXPORTERVERSION_LOCALNAME = "exporterVersion";
    private static final String EXPORTOF_LOCALNAME = "exportOf";
    private static final String MLVALUE_LOCALNAME = "mlvalue";
    private static final String LOCALE_LOCALNAME = "locale";
    private static final String ACL_LOCALNAME = "acl";
    private static final String ACE_LOCALNAME = "ace";
    private static final String ACCESS_LOCALNAME = "access";
    private static final String AUTHORITY_LOCALNAME = "authority";
    private static final String PERMISSION_LOCALNAME = "permission";
    private static final String INHERITPERMISSIONS_LOCALNAME = "inherit";
    private static final String REFERENCE_LOCALNAME = "reference";
    private static final String PATHREF_LOCALNAME = "pathref";
    private static final String NODEREF_LOCALNAME = "noderef";
    private static QName VIEW_QNAME;
    private static QName VALUES_QNAME;
    private static QName VALUE_QNAME;
    private static QName PROPERTIES_QNAME;
    private static QName ASPECTS_QNAME;
    private static QName ASSOCIATIONS_QNAME;
    private static QName CHILDNAME_QNAME;
    private static QName DATATYPE_QNAME;
    private static QName ISNULL_QNAME;
    private static QName METADATA_QNAME;
    private static QName EXPORTEDBY_QNAME;
    private static QName EXPORTEDDATE_QNAME;
    private static QName EXPORTERVERSION_QNAME;
    private static QName EXPORTOF_QNAME;
    private static QName ACL_QNAME;
    private static QName ACE_QNAME;
    private static QName ACCESS_QNAME;
    private static QName AUTHORITY_QNAME;
    private static QName PERMISSION_QNAME;
    private static QName INHERITPERMISSIONS_QNAME;
    private static QName REFERENCE_QNAME;
    private static QName PATHREF_QNAME;
    private static QName NODEREF_QNAME;
    private static QName LOCALE_QNAME;
    private static QName MLVALUE_QNAME;
    private static final AttributesImpl EMPTY_ATTRIBUTES = new AttributesImpl();

    // Service dependencies
    private NamespaceService namespaceService;
    private NodeService nodeService;
    private SearchService searchService;
    private DictionaryService dictionaryService;
    private PermissionService permissionService;

    // View context
    private ContentHandler contentHandler;
    private ExporterContext context;

    // Configuration
    private ReferenceType referenceType;

    /**
     * Construct
     * 
     * @param namespaceService  namespace service
     * @param nodeService  node service
     * @param contentHandler  content handler
     */
    ViewXMLExporter(NamespaceService namespaceService, NodeService nodeService, SearchService searchService,
            DictionaryService dictionaryService, PermissionService permissionService,
            ContentHandler contentHandler) {
        this.namespaceService = namespaceService;
        this.nodeService = nodeService;
        this.searchService = searchService;
        this.dictionaryService = dictionaryService;
        this.permissionService = permissionService;
        this.contentHandler = contentHandler;

        VIEW_QNAME = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, VIEW_LOCALNAME, namespaceService);
        VALUE_QNAME = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, VALUE_LOCALNAME, namespaceService);
        VALUES_QNAME = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, VALUES_LOCALNAME,
                namespaceService);
        CHILDNAME_QNAME = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, CHILDNAME_LOCALNAME,
                namespaceService);
        ASPECTS_QNAME = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, ASPECTS_LOCALNAME,
                namespaceService);
        PROPERTIES_QNAME = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, PROPERTIES_LOCALNAME,
                namespaceService);
        ASSOCIATIONS_QNAME = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, ASSOCIATIONS_LOCALNAME,
                namespaceService);
        DATATYPE_QNAME = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, DATATYPE_LOCALNAME,
                namespaceService);
        ISNULL_QNAME = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, ISNULL_LOCALNAME,
                namespaceService);
        METADATA_QNAME = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, METADATA_LOCALNAME,
                namespaceService);
        EXPORTEDBY_QNAME = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, EXPORTEDBY_LOCALNAME,
                namespaceService);
        EXPORTEDDATE_QNAME = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, EXPORTEDDATE_LOCALNAME,
                namespaceService);
        EXPORTERVERSION_QNAME = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX,
                EXPORTERVERSION_LOCALNAME, namespaceService);
        EXPORTOF_QNAME = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, EXPORTOF_LOCALNAME,
                namespaceService);
        ACL_QNAME = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, ACL_LOCALNAME, namespaceService);
        ACE_QNAME = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, ACE_LOCALNAME, namespaceService);
        ACCESS_QNAME = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, ACCESS_LOCALNAME,
                namespaceService);
        AUTHORITY_QNAME = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, AUTHORITY_LOCALNAME,
                namespaceService);
        PERMISSION_QNAME = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, PERMISSION_LOCALNAME,
                namespaceService);
        INHERITPERMISSIONS_QNAME = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX,
                INHERITPERMISSIONS_LOCALNAME, namespaceService);
        REFERENCE_QNAME = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, REFERENCE_LOCALNAME,
                namespaceService);
        PATHREF_QNAME = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, PATHREF_LOCALNAME,
                namespaceService);
        NODEREF_QNAME = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, NODEREF_LOCALNAME,
                namespaceService);
        LOCALE_QNAME = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, LOCALE_LOCALNAME,
                namespaceService);
        MLVALUE_QNAME = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, MLVALUE_LOCALNAME,
                namespaceService);

    }

    /**
     * Set Reference Type to export with
     * 
     * @param referenceType  reference type to export
     */
    public void setReferenceType(ReferenceType referenceType) {
        this.referenceType = referenceType;
    }

    /* (non-Javadoc)
     * @see org.alfresco.service.cmr.view.Exporter#start()
     */
    public void start(ExporterContext context) {
        try {
            this.context = context;
            contentHandler.startDocument();
            contentHandler.startPrefixMapping(NamespaceService.REPOSITORY_VIEW_PREFIX,
                    NamespaceService.REPOSITORY_VIEW_1_0_URI);
            contentHandler.startElement(NamespaceService.REPOSITORY_VIEW_PREFIX, VIEW_LOCALNAME,
                    VIEW_QNAME.toPrefixString(), EMPTY_ATTRIBUTES);

            //
            // output metadata
            //
            contentHandler.startElement(NamespaceService.REPOSITORY_VIEW_PREFIX, METADATA_LOCALNAME,
                    METADATA_QNAME.toPrefixString(), EMPTY_ATTRIBUTES);

            // exported by
            contentHandler.startElement(NamespaceService.REPOSITORY_VIEW_PREFIX, EXPORTEDBY_LOCALNAME,
                    EXPORTEDBY_QNAME.toPrefixString(), EMPTY_ATTRIBUTES);
            contentHandler.characters(context.getExportedBy().toCharArray(), 0, context.getExportedBy().length());
            contentHandler.endElement(NamespaceService.REPOSITORY_VIEW_PREFIX, EXPORTEDBY_LOCALNAME,
                    EXPORTEDBY_QNAME.toPrefixString());

            // exported date
            contentHandler.startElement(NamespaceService.REPOSITORY_VIEW_PREFIX, EXPORTEDDATE_LOCALNAME,
                    EXPORTEDDATE_QNAME.toPrefixString(), EMPTY_ATTRIBUTES);
            String date = DefaultTypeConverter.INSTANCE.convert(String.class, context.getExportedDate());
            contentHandler.characters(date.toCharArray(), 0, date.length());
            contentHandler.endElement(NamespaceService.REPOSITORY_VIEW_PREFIX, EXPORTEDDATE_LOCALNAME,
                    EXPORTEDDATE_QNAME.toPrefixString());

            // exporter version
            contentHandler.startElement(NamespaceService.REPOSITORY_VIEW_PREFIX, EXPORTERVERSION_LOCALNAME,
                    EXPORTERVERSION_QNAME.toPrefixString(), EMPTY_ATTRIBUTES);
            contentHandler.characters(context.getExporterVersion().toCharArray(), 0,
                    context.getExporterVersion().length());
            contentHandler.endElement(NamespaceService.REPOSITORY_VIEW_PREFIX, EXPORTERVERSION_LOCALNAME,
                    EXPORTERVERSION_QNAME.toPrefixString());

            // export of
            contentHandler.startElement(NamespaceService.REPOSITORY_VIEW_PREFIX, EXPORTOF_LOCALNAME,
                    EXPORTOF_QNAME.toPrefixString(), EMPTY_ATTRIBUTES);
            NodeRef[] exportList = context.getExportList();
            int comma = 1;
            for (int i = 0; i < exportList.length; i++) {
                NodeRef nodeRef = exportList[i];
                String path = nodeService.getPath(nodeRef).toPrefixString(namespaceService);
                if (i == exportList.length - 1) {
                    comma = 0;
                }
                contentHandler.characters(ArrayUtils.addAll(path.toCharArray(), ",".toCharArray()), 0,
                        path.length() + comma);
            }
            contentHandler.endElement(NamespaceService.REPOSITORY_VIEW_PREFIX, EXPORTOF_LOCALNAME,
                    EXPORTOF_QNAME.toPrefixString());
            contentHandler.endElement(NamespaceService.REPOSITORY_VIEW_PREFIX, METADATA_LOCALNAME,
                    METADATA_QNAME.toPrefixString());
        } catch (SAXException e) {
            throw new ExporterException("Failed to process export start event", e);
        }
    }

    /* (non-Javadoc)
     * @see org.alfresco.service.cmr.view.Exporter#startNamespace(java.lang.String, java.lang.String)
     */
    public void startNamespace(String prefix, String uri) {
        try {
            contentHandler.startPrefixMapping(prefix, uri);
        } catch (SAXException e) {
            throw new ExporterException(
                    "Failed to process start namespace event - prefix " + prefix + " uri " + uri, e);
        }
    }

    /* (non-Javadoc)
     * @see org.alfresco.service.cmr.view.Exporter#endNamespace(java.lang.String)
     */
    public void endNamespace(String prefix) {
        try {
            contentHandler.endPrefixMapping(prefix);
        } catch (SAXException e) {
            throw new ExporterException("Failed to process end namespace event - prefix " + prefix, e);
        }
    }

    /* (non-Javadoc)
     * @see org.alfresco.service.cmr.view.Exporter#startNode(org.alfresco.service.cmr.repository.NodeRef)
     */
    public void startNode(NodeRef nodeRef) {
        try {
            AttributesImpl attrs = new AttributesImpl();

            Path path = nodeService.getPath(nodeRef);
            if (path.size() > 1) {
                // a child name does not exist for root
                Path.ChildAssocElement pathElement = (Path.ChildAssocElement) path.last();
                QName childQName = pathElement.getRef().getQName();
                attrs.addAttribute(NamespaceService.REPOSITORY_VIEW_1_0_URI, CHILDNAME_LOCALNAME,
                        CHILDNAME_QNAME.toPrefixString(), null, toPrefixString(childQName));
            }

            QName type = nodeService.getType(nodeRef);
            contentHandler.startElement(type.getNamespaceURI(), type.getLocalName(), toPrefixString(type), attrs);
        } catch (SAXException e) {
            throw new ExporterException("Failed to process start node event - node ref " + nodeRef.toString(), e);
        }
    }

    /* (non-Javadoc)
     * @see org.alfresco.service.cmr.view.Exporter#endNode(org.alfresco.service.cmr.repository.NodeRef)
     */
    public void endNode(NodeRef nodeRef) {
        try {
            QName type = nodeService.getType(nodeRef);
            contentHandler.endElement(type.getNamespaceURI(), type.getLocalName(), toPrefixString(type));
        } catch (SAXException e) {
            throw new ExporterException("Failed to process end node event - node ref " + nodeRef.toString(), e);
        }
    }

    /* (non-Javadoc)
     * @see org.alfresco.service.cmr.view.Exporter#startAspects(org.alfresco.service.cmr.repository.NodeRef)
     */
    public void startAspects(NodeRef nodeRef) {
        try {
            contentHandler.startElement(ASPECTS_QNAME.getNamespaceURI(), ASPECTS_LOCALNAME,
                    toPrefixString(ASPECTS_QNAME), EMPTY_ATTRIBUTES);
        } catch (SAXException e) {
            throw new ExporterException("Failed to process start aspects", e);
        }
    }

    /* (non-Javadoc)
     * @see org.alfresco.service.cmr.view.Exporter#endAspects(org.alfresco.service.cmr.repository.NodeRef)
     */
    public void endAspects(NodeRef nodeRef) {
        try {
            contentHandler.endElement(ASPECTS_QNAME.getNamespaceURI(), ASPECTS_LOCALNAME,
                    toPrefixString(ASPECTS_QNAME));
        } catch (SAXException e) {
            throw new ExporterException("Failed to process end aspects", e);
        }
    }

    /* (non-Javadoc)
     * @see org.alfresco.service.cmr.view.Exporter#startAspect(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName)
     */
    public void startAspect(NodeRef nodeRef, QName aspect) {
        try {
            contentHandler.startElement(aspect.getNamespaceURI(), aspect.getLocalName(), toPrefixString(aspect),
                    EMPTY_ATTRIBUTES);
        } catch (SAXException e) {
            throw new ExporterException("Failed to process start aspect event - node ref " + nodeRef.toString()
                    + "; aspect " + toPrefixString(aspect), e);
        }
    }

    /* (non-Javadoc)
     * @see org.alfresco.service.cmr.view.Exporter#endAspect(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName)
     */
    public void endAspect(NodeRef nodeRef, QName aspect) {
        try {
            contentHandler.endElement(aspect.getNamespaceURI(), aspect.getLocalName(), toPrefixString(aspect));
        } catch (SAXException e) {
            throw new ExporterException("Failed to process end aspect event - node ref " + nodeRef.toString()
                    + "; aspect " + toPrefixString(aspect), e);
        }
    }

    /* (non-Javadoc)
     * @see org.alfresco.service.cmr.view.Exporter#startACL(org.alfresco.service.cmr.repository.NodeRef)
     */
    public void startACL(NodeRef nodeRef) {
        try {
            AttributesImpl attrs = new AttributesImpl();
            boolean inherit = permissionService.getInheritParentPermissions(nodeRef);
            if (!inherit) {
                attrs.addAttribute(NamespaceService.REPOSITORY_VIEW_1_0_URI, INHERITPERMISSIONS_LOCALNAME,
                        INHERITPERMISSIONS_QNAME.toPrefixString(), null, "false");
            }
            contentHandler.startElement(ACL_QNAME.getNamespaceURI(), ACL_QNAME.getLocalName(),
                    toPrefixString(ACL_QNAME), attrs);
        } catch (SAXException e) {
            throw new ExporterException("Failed to process start ACL event - node ref " + nodeRef.toString());
        }
    }

    /* (non-Javadoc)
     * @see org.alfresco.service.cmr.view.Exporter#permission(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.security.AccessPermission)
     */
    public void permission(NodeRef nodeRef, AccessPermission permission) {
        try {
            // output access control entry
            AttributesImpl attrs = new AttributesImpl();
            attrs.addAttribute(NamespaceService.REPOSITORY_VIEW_1_0_URI, ACCESS_LOCALNAME,
                    ACCESS_QNAME.toPrefixString(), null, permission.getAccessStatus().toString());
            contentHandler.startElement(ACE_QNAME.getNamespaceURI(), ACE_QNAME.getLocalName(),
                    toPrefixString(ACE_QNAME), attrs);

            // output authority
            contentHandler.startElement(AUTHORITY_QNAME.getNamespaceURI(), AUTHORITY_QNAME.getLocalName(),
                    toPrefixString(AUTHORITY_QNAME), EMPTY_ATTRIBUTES);
            String authority = permission.getAuthority();
            contentHandler.characters(authority.toCharArray(), 0, authority.length());
            contentHandler.endElement(AUTHORITY_QNAME.getNamespaceURI(), AUTHORITY_QNAME.getLocalName(),
                    toPrefixString(AUTHORITY_QNAME));

            // output permission
            contentHandler.startElement(PERMISSION_QNAME.getNamespaceURI(), PERMISSION_QNAME.getLocalName(),
                    toPrefixString(PERMISSION_QNAME), EMPTY_ATTRIBUTES);
            String strPermission = permission.getPermission();
            contentHandler.characters(strPermission.toCharArray(), 0, strPermission.length());
            contentHandler.endElement(PERMISSION_QNAME.getNamespaceURI(), PERMISSION_QNAME.getLocalName(),
                    toPrefixString(PERMISSION_QNAME));

            // end access control entry
            contentHandler.endElement(ACE_QNAME.getNamespaceURI(), ACE_QNAME.getLocalName(),
                    toPrefixString(ACE_QNAME));
        } catch (SAXException e) {
            throw new ExporterException("Failed to process permission event - node ref " + nodeRef.toString()
                    + "; permission " + permission);
        }
    }

    /* (non-Javadoc)
     * @see org.alfresco.service.cmr.view.Exporter#endACL(org.alfresco.service.cmr.repository.NodeRef)
     */
    public void endACL(NodeRef nodeRef) {
        try {
            contentHandler.endElement(ACL_QNAME.getNamespaceURI(), ACL_QNAME.getLocalName(),
                    toPrefixString(ACL_QNAME));
        } catch (SAXException e) {
            throw new ExporterException("Failed to process end ACL event - node ref " + nodeRef.toString());
        }
    }

    /* (non-Javadoc)
     * @see org.alfresco.service.cmr.view.Exporter#startProperties(org.alfresco.service.cmr.repository.NodeRef)
     */
    public void startProperties(NodeRef nodeRef) {
        try {
            contentHandler.startElement(PROPERTIES_QNAME.getNamespaceURI(), PROPERTIES_LOCALNAME,
                    toPrefixString(PROPERTIES_QNAME), EMPTY_ATTRIBUTES);
        } catch (SAXException e) {
            throw new ExporterException("Failed to process start properties", e);
        }
    }

    /* (non-Javadoc)
     * @see org.alfresco.service.cmr.view.Exporter#endProperties(org.alfresco.service.cmr.repository.NodeRef)
     */
    public void endProperties(NodeRef nodeRef) {
        try {
            contentHandler.endElement(PROPERTIES_QNAME.getNamespaceURI(), PROPERTIES_LOCALNAME,
                    toPrefixString(PROPERTIES_QNAME));
        } catch (SAXException e) {
            throw new ExporterException("Failed to process start properties", e);
        }
    }

    /* (non-Javadoc)
     * @see org.alfresco.service.cmr.view.Exporter#startProperty(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName)
     */
    public void startProperty(NodeRef nodeRef, QName property) {
        try {
            QName encodedProperty = QName.createQName(property.getNamespaceURI(),
                    ISO9075.encode(property.getLocalName()));
            contentHandler.startElement(encodedProperty.getNamespaceURI(), encodedProperty.getLocalName(),
                    toPrefixString(encodedProperty), EMPTY_ATTRIBUTES);
        } catch (SAXException e) {
            throw new ExporterException("Failed to process start property event - nodeRef " + nodeRef
                    + "; property " + toPrefixString(property), e);
        }
    }

    /* (non-Javadoc)
     * @see org.alfresco.service.cmr.view.Exporter#endProperty(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName)
     */
    public void endProperty(NodeRef nodeRef, QName property) {
        try {
            QName encodedProperty = QName.createQName(property.getNamespaceURI(),
                    ISO9075.encode(property.getLocalName()));
            contentHandler.endElement(encodedProperty.getNamespaceURI(), encodedProperty.getLocalName(),
                    toPrefixString(encodedProperty));
        } catch (SAXException e) {
            throw new ExporterException("Failed to process end property event - nodeRef " + nodeRef + "; property "
                    + toPrefixString(property), e);
        }
    }

    /* (non-Javadoc)
     * @see org.alfresco.service.cmr.view.Exporter#startValueCollection(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName)
     */
    public void startValueCollection(NodeRef nodeRef, QName property) {
        try {
            // start collection
            contentHandler.startElement(NamespaceService.REPOSITORY_VIEW_PREFIX, VALUES_LOCALNAME,
                    toPrefixString(VALUES_QNAME), EMPTY_ATTRIBUTES);
        } catch (SAXException e) {
            throw new ExporterException("Failed to process start collection event - nodeRef " + nodeRef
                    + "; property " + toPrefixString(property), e);
        }
    }

    /* (non-Javadoc)
     * @see org.alfresco.service.cmr.view.Exporter#endValueCollection(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName)
     */
    public void endValueCollection(NodeRef nodeRef, QName property) {
        try {
            // end collection
            contentHandler.endElement(NamespaceService.REPOSITORY_VIEW_PREFIX, VALUES_LOCALNAME,
                    toPrefixString(VALUES_QNAME));
        } catch (SAXException e) {
            throw new ExporterException("Failed to process end collection event - nodeRef " + nodeRef
                    + "; property " + toPrefixString(property), e);
        }
    }

    /* (non-Javadoc)
     * @see org.alfresco.service.cmr.view.Exporter#value(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName, java.io.Serializable)
     */
    public void value(NodeRef nodeRef, QName property, Object value, int index) {
        try {
            // determine data type of value
            QName valueDataType = null;
            PropertyDefinition propDef = dictionaryService.getProperty(property);
            DataTypeDefinition dataTypeDef = (propDef == null) ? null : propDef.getDataType();
            if (dataTypeDef == null || dataTypeDef.getName().equals(DataTypeDefinition.ANY)) {
                dataTypeDef = (value == null) ? null : dictionaryService.getDataType(value.getClass());
                if (dataTypeDef != null) {
                    valueDataType = dataTypeDef.getName();
                }
            }

            boolean isMLText = (dataTypeDef != null && dataTypeDef.getName().equals(DataTypeDefinition.MLTEXT));

            // convert node references to paths
            if (value instanceof NodeRef && referenceType.equals(ReferenceType.PATHREF)) {
                NodeRef valueNodeRef = (NodeRef) value;
                if (nodeRef.getStoreRef().equals(valueNodeRef.getStoreRef())) {
                    Path nodeRefPath = null;
                    if (property.equals(ContentModel.PROP_CATEGORIES)) {
                        // Special case for categories - use the full path so that categories
                        // can be successfully assigned to imported content (provided the same store
                        // was used for both import and export and the categories still exist).
                        nodeRefPath = nodeService.getPath(valueNodeRef);
                    } else {
                        nodeRefPath = createPath(context.getExportParent(), nodeRef, valueNodeRef);
                    }
                    value = (nodeRefPath == null) ? null : nodeRefPath.toPrefixString(namespaceService);
                }
            }

            // output value wrapper if value is null or property data type is ANY or value is part of collection
            if (value == null || valueDataType != null || index != -1) {
                AttributesImpl attrs = new AttributesImpl();
                if (value == null) {
                    attrs.addAttribute(NamespaceService.REPOSITORY_VIEW_PREFIX, ISNULL_LOCALNAME,
                            ISNULL_QNAME.toPrefixString(), null, "true");
                }
                if (valueDataType != null) {
                    attrs.addAttribute(NamespaceService.REPOSITORY_VIEW_PREFIX, DATATYPE_LOCALNAME,
                            DATATYPE_QNAME.toPrefixString(), null, toPrefixString(valueDataType));
                }
                if (!isMLText) {
                    // only output for non MLTEXT values
                    contentHandler.startElement(NamespaceService.REPOSITORY_VIEW_PREFIX, VALUE_LOCALNAME,
                            toPrefixString(VALUE_QNAME), attrs);
                }
            }

            // output value
            String strValue = (String) DefaultTypeConverter.INSTANCE.convert(String.class, value);
            if (strValue != null) {
                for (int i = 0; i < strValue.length(); i++) {
                    char[] temp = new char[] { strValue.charAt(i) };
                    contentHandler.characters(temp, 0, 1);
                }
            }

            // output value wrapper if property data type is any
            if ((value == null || valueDataType != null || index != -1) && !isMLText) {
                // only output for non MLTEXT values
                contentHandler.endElement(NamespaceService.REPOSITORY_VIEW_PREFIX, VALUE_LOCALNAME,
                        toPrefixString(VALUE_QNAME));
            }
        } catch (SAXException e) {
            throw new ExporterException("Failed to process value event - nodeRef " + nodeRef + "; property "
                    + toPrefixString(property) + "; value " + value, e);
        }
    }

    /* (non-Javadoc)
     * @see org.alfresco.service.cmr.view.Exporter#content(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName, java.io.InputStream)
     */
    public void content(NodeRef nodeRef, QName property, InputStream content, ContentData contentData, int index) {
        // TODO: Base64 encode content and send out via Content Handler
    }

    /* (non-Javadoc)
     * @see org.alfresco.service.cmr.view.Exporter#startAssoc(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName)
     */
    public void startAssoc(NodeRef nodeRef, QName assoc) {
        try {
            contentHandler.startElement(assoc.getNamespaceURI(), assoc.getLocalName(), toPrefixString(assoc),
                    EMPTY_ATTRIBUTES);
        } catch (SAXException e) {
            throw new ExporterException("Failed to process start assoc event - nodeRef " + nodeRef
                    + "; association " + toPrefixString(assoc), e);
        }
    }

    /* (non-Javadoc)
     * @see org.alfresco.service.cmr.view.Exporter#endAssoc(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName)
     */
    public void endAssoc(NodeRef nodeRef, QName assoc) {
        try {
            contentHandler.endElement(assoc.getNamespaceURI(), assoc.getLocalName(), toPrefixString(assoc));
        } catch (SAXException e) {
            throw new ExporterException("Failed to process end assoc event - nodeRef " + nodeRef + "; association "
                    + toPrefixString(assoc), e);
        }
    }

    /* (non-Javadoc)
     * @see org.alfresco.service.cmr.view.Exporter#startAssocs(org.alfresco.service.cmr.repository.NodeRef)
     */
    public void startAssocs(NodeRef nodeRef) {
        try {
            contentHandler.startElement(ASSOCIATIONS_QNAME.getNamespaceURI(), ASSOCIATIONS_LOCALNAME,
                    toPrefixString(ASSOCIATIONS_QNAME), EMPTY_ATTRIBUTES);
        } catch (SAXException e) {
            throw new ExporterException("Failed to process start associations", e);
        }
    }

    /* (non-Javadoc)
     * @see org.alfresco.service.cmr.view.Exporter#endAssocs(org.alfresco.service.cmr.repository.NodeRef)
     */
    public void endAssocs(NodeRef nodeRef) {
        try {
            contentHandler.endElement(ASSOCIATIONS_QNAME.getNamespaceURI(), ASSOCIATIONS_LOCALNAME,
                    toPrefixString(ASSOCIATIONS_QNAME));
        } catch (SAXException e) {
            throw new ExporterException("Failed to process end associations", e);
        }
    }

    /* (non-Javadoc)
     * @see org.alfresco.service.cmr.view.Exporter#startReference(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName)
     */
    public void startReference(NodeRef nodeRef, QName childName) {
        try {
            // determine format of reference e.g. node or path based
            ReferenceType referenceFormat = referenceType;
            if (nodeRef.equals(nodeService.getRootNode(nodeRef.getStoreRef()))) {
                referenceFormat = ReferenceType.PATHREF;
            }

            // output reference
            AttributesImpl attrs = new AttributesImpl();
            if (referenceFormat.equals(ReferenceType.PATHREF)) {
                Path path = createPath(context.getExportParent(), context.getExportParent(), nodeRef);
                attrs.addAttribute(NamespaceService.REPOSITORY_VIEW_1_0_URI, PATHREF_LOCALNAME,
                        PATHREF_QNAME.toPrefixString(), null, path.toPrefixString(namespaceService));
            } else {
                attrs.addAttribute(NamespaceService.REPOSITORY_VIEW_1_0_URI, NODEREF_LOCALNAME,
                        NODEREF_QNAME.toPrefixString(), null, nodeRef.toString());
            }
            if (childName != null) {
                attrs.addAttribute(NamespaceService.REPOSITORY_VIEW_1_0_URI, CHILDNAME_LOCALNAME,
                        CHILDNAME_QNAME.toPrefixString(), null, childName.toPrefixString(namespaceService));
            }
            contentHandler.startElement(REFERENCE_QNAME.getNamespaceURI(), REFERENCE_LOCALNAME,
                    toPrefixString(REFERENCE_QNAME), attrs);
        } catch (SAXException e) {
            throw new ExporterException("Failed to process start reference", e);
        }
    }

    /*
     * (non-Javadoc)
     * @see org.alfresco.service.cmr.view.Exporter#endReference(org.alfresco.service.cmr.repository.NodeRef)
     */
    public void endReference(NodeRef nodeRef) {
        try {
            contentHandler.endElement(REFERENCE_QNAME.getNamespaceURI(), REFERENCE_LOCALNAME,
                    toPrefixString(REFERENCE_QNAME));
        } catch (SAXException e) {
            throw new ExporterException("Failed to process end reference", e);
        }
    }

    public void startValueMLText(NodeRef nodeRef, Locale locale, boolean isNull) {
        AttributesImpl attrs = new AttributesImpl();
        attrs.addAttribute(NamespaceService.REPOSITORY_VIEW_PREFIX, LOCALE_LOCALNAME, LOCALE_QNAME.toPrefixString(),
                null, locale.toString());
        if (isNull) {
            attrs.addAttribute(NamespaceService.REPOSITORY_VIEW_PREFIX, ISNULL_LOCALNAME,
                    ISNULL_QNAME.toPrefixString(), null, "true");
        }
        try {
            contentHandler.startElement(NamespaceService.REPOSITORY_VIEW_PREFIX, MLVALUE_LOCALNAME,
                    MLVALUE_QNAME.toPrefixString(), attrs);
        } catch (SAXException e) {
            throw new ExporterException("Failed to process start mlvalue", e);
        }
    }

    public void endValueMLText(NodeRef nodeRef) {
        try {
            contentHandler.endElement(NamespaceService.REPOSITORY_VIEW_PREFIX, MLVALUE_LOCALNAME,
                    MLVALUE_QNAME.toPrefixString());
        } catch (SAXException e) {
            throw new ExporterException("Failed to process end mltext", e);
        }

    }

    /* (non-Javadoc)
     * @see org.alfresco.service.cmr.view.Exporter#warning(java.lang.String)
     */
    public void warning(String warning) {
    }

    /* (non-Javadoc)
     * @see org.alfresco.service.cmr.view.Exporter#end()
     */
    public void end() {
        try {
            contentHandler.endElement(NamespaceService.REPOSITORY_VIEW_PREFIX, VIEW_LOCALNAME,
                    VIEW_QNAME.toPrefixString());
            contentHandler.endPrefixMapping(NamespaceService.REPOSITORY_VIEW_PREFIX);
            contentHandler.endDocument();
        } catch (SAXException e) {
            throw new ExporterException("Failed to process end export event", e);
        }
    }

    /**
     * Get the prefix for the specified URI
     * @param qname  the QName
     * @return  the prefix (or null, if one is not registered)
     */
    private String toPrefixString(QName qname) {
        return qname.toPrefixString(namespaceService);
    }

    /**
     * Return relative path between from and to references within export root
     * 
     * @param fromRef  from reference
     * @param toRef  to reference
     * @return  path
     */
    private Path createPath(NodeRef rootRef, NodeRef fromRef, NodeRef toRef) {
        // Check that item exists first
        if (!nodeService.exists(toRef)) {
            // return null path
            return null;
        }

        // Check whether item is the root node of the store
        // If so, always return absolute path
        if (toRef.equals(nodeService.getRootNode(toRef.getStoreRef()))) {
            return nodeService.getPath(toRef);
        }

        // construct relative path
        Path rootPath = createIndexedPath(rootRef, nodeService.getPath(rootRef));
        Path fromPath = createIndexedPath(fromRef, nodeService.getPath(fromRef));
        Path toPath = createIndexedPath(toRef, nodeService.getPath(toRef));
        Path relativePath = null;

        try {
            // Determine if 'to path' is a category
            // TODO: This needs to be resolved in a more appropriate manner - special support is
            //       required for categories.
            for (int i = 0; i < toPath.size(); i++) {
                Path.Element pathElement = toPath.get(i);
                if (pathElement.getPrefixedString(namespaceService).equals("cm:categoryRoot")) {
                    Path.ChildAssocElement childPath = (Path.ChildAssocElement) pathElement;
                    relativePath = new Path();
                    relativePath.append(new Path.ChildAssocElement(
                            new ChildAssociationRef(null, null, null, childPath.getRef().getParentRef())));
                    relativePath.append(toPath.subPath(i + 1, toPath.size() - 1));
                    break;
                }
            }

            if (relativePath == null) {
                // Determine if from node is relative to export tree
                int i = 0;
                while (i < rootPath.size() && i < fromPath.size() && rootPath.get(i).equals(fromPath.get(i))) {
                    i++;
                }
                if (i == rootPath.size()) {
                    // Determine if to node is relative to export tree
                    for (NodeRef nodeRef : context.getExportParentList()) {
                        int j = 0;
                        Path tryPath = createIndexedPath(nodeRef, nodeService.getPath(nodeRef));
                        while (j < tryPath.size() && j < toPath.size() && tryPath.get(j).equals(toPath.get(j))) {
                            j++;
                        }
                        if (j == tryPath.size()) {
                            // build relative path between from and to
                            relativePath = new Path();
                            for (int p = 0; p < fromPath.size() - i; p++) {
                                relativePath.append(new Path.ParentElement());
                            }
                            if (j < toPath.size()) {
                                relativePath.append(toPath.subPath(j, toPath.size() - 1));
                            }

                            break;
                        }
                    }
                }
            }

            if (relativePath == null) {
                // default to absolute path
                relativePath = toPath;
            }
        } catch (Throwable e) {
            String msg = "Failed to determine relative path: root path=" + rootPath + "; from path=" + fromPath
                    + "; to path=" + toPath;
            throw new ExporterException(msg, e);
        }

        return relativePath;
    }

    /**
     * Helper to convert a path into an indexed path which uniquely identifies a node
     * 
     * @param nodeRef NodeRef
     * @param path Path
     * @return Path
     */
    private Path createIndexedPath(NodeRef nodeRef, Path path) {
        // Add indexes for same name siblings
        // TODO: Look at more efficient approach
        for (int i = path.size() - 1; i >= 0; i--) {
            Path.Element pathElement = path.get(i);
            if (i > 0 && pathElement instanceof Path.ChildAssocElement) {
                int index = 1; // for xpath index compatibility
                String searchPath = path.subPath(i).toPrefixString(namespaceService);
                List<NodeRef> siblings = searchService.selectNodes(nodeRef, searchPath, null, namespaceService,
                        false);
                if (siblings.size() > 1) {
                    ChildAssociationRef childAssoc = ((Path.ChildAssocElement) pathElement).getRef();
                    NodeRef childRef = childAssoc.getChildRef();
                    for (NodeRef sibling : siblings) {
                        if (sibling.equals(childRef)) {
                            childAssoc.setNthSibling(index);
                            break;
                        }
                        index++;
                    }
                }
            }
        }

        return path;
    }
}