se.vgregion.alfresco.repo.utils.VGRexporterComponent.java Source code

Java tutorial

Introduction

Here is the source code for se.vgregion.alfresco.repo.utils.VGRexporterComponent.java

Source

package se.vgregion.alfresco.repo.utils;

/*
 * Copyright (C) 2005-2010 Alfresco Software Limited.
 *
 * This file is part of Alfresco
 *
 * 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/>.
 */
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import org.alfresco.model.ContentModel;
import org.alfresco.repo.exporter.ExporterComponent;
import org.alfresco.repo.exporter.ExporterCrawler;
import org.alfresco.repo.node.MLPropertyInterceptor;
import org.alfresco.service.cmr.dictionary.AssociationDefinition;
import org.alfresco.service.cmr.dictionary.ClassDefinition;
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.AssociationRef;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.ContentData;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.MLText;
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.TypeConversionException;
import org.alfresco.service.cmr.search.SearchService;
import org.alfresco.service.cmr.security.AccessPermission;
import org.alfresco.service.cmr.security.AccessStatus;
import org.alfresco.service.cmr.security.AuthenticationService;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.cmr.view.ExportPackageHandler;
import org.alfresco.service.cmr.view.Exporter;
import org.alfresco.service.cmr.view.ExporterContext;
import org.alfresco.service.cmr.view.ExporterCrawlerParameters;
import org.alfresco.service.cmr.view.ExporterException;
import org.alfresco.service.cmr.view.ImporterException;
import org.alfresco.service.cmr.view.Location;
import org.alfresco.service.cmr.view.ReferenceType;
import org.alfresco.service.descriptor.DescriptorService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.namespace.RegexQNamePattern;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import org.springframework.extensions.surf.util.ParameterCheck;

/**
 * Default implementation of the Exporter Service.
 * 
 * @author David Caruana
 */
public class VGRexporterComponent extends ExporterComponent {
    // Supporting services
    private NamespaceService namespaceService;
    private DictionaryService dictionaryService;
    private NodeService nodeService;
    private SearchService searchService;
    private ContentService contentService;
    private DescriptorService descriptorService;
    private AuthenticationService authenticationService;
    private PermissionService permissionService;

    /** Indent Size */
    private int indentSize = 2;

    /**
     * @param nodeService  the node service
     */
    @Override
    public void setNodeService(NodeService nodeService) {
        this.nodeService = nodeService;
    }

    /**
     * @param searchService  the service to perform path searches
     */
    @Override
    public void setSearchService(SearchService searchService) {
        this.searchService = searchService;
    }

    /**
     * @param contentService  the content service
     */
    @Override
    public void setContentService(ContentService contentService) {
        this.contentService = contentService;
    }

    /**
     * @param dictionaryService  the dictionary service
     */
    @Override
    public void setDictionaryService(DictionaryService dictionaryService) {
        this.dictionaryService = dictionaryService;
    }

    /**
     * @param namespaceService  the namespace service
     */
    @Override
    public void setNamespaceService(NamespaceService namespaceService) {
        this.namespaceService = namespaceService;
    }

    /**
     * @param descriptorService  the descriptor service
     */
    @Override
    public void setDescriptorService(DescriptorService descriptorService) {
        this.descriptorService = descriptorService;
    }

    /**
     * @param authenticationService  the authentication service
     */
    @Override
    public void setAuthenticationService(AuthenticationService authenticationService) {
        this.authenticationService = authenticationService;
    }

    /**
     * @param permissionService  the permission service
     */
    @Override
    public void setPermissionService(PermissionService permissionService) {
        this.permissionService = permissionService;
    }

    /* (non-Javadoc)
     * @see org.alfresco.service.cmr.view.ExporterService#exportView(java.io.OutputStream, org.alfresco.service.cmr.view.ExporterCrawlerParameters, org.alfresco.service.cmr.view.Exporter)
     */
    @Override
    public void exportView(OutputStream viewWriter, ExporterCrawlerParameters parameters, Exporter progress) {
        ParameterCheck.mandatory("View Writer", viewWriter);

        // Construct a basic XML Exporter
        Exporter xmlExporter = createXMLExporter(viewWriter, parameters.getReferenceType());

        // Export
        exportView(xmlExporter, parameters, progress);
    }

    /* (non-Javadoc)
     * @see org.alfresco.service.cmr.view.ExporterService#exportView(org.alfresco.service.cmr.view.ExportPackageHandler, org.alfresco.service.cmr.view.ExporterCrawlerParameters, org.alfresco.service.cmr.view.Exporter)
     */
    @Override
    public void exportView(ExportPackageHandler exportHandler, ExporterCrawlerParameters parameters,
            Exporter progress) {
        ParameterCheck.mandatory("Stream Handler", exportHandler);

        // create exporter around export handler
        exportHandler.startExport();
        OutputStream dataFile = exportHandler.createDataStream();
        Exporter xmlExporter = createXMLExporter(dataFile, parameters.getReferenceType());
        VGRURLExporter urlExporter = new VGRURLExporter(xmlExporter, exportHandler);

        // export
        exportView(urlExporter, parameters, progress);

        // end export
        exportHandler.endExport();
    }

    /* (non-Javadoc)
     * @see org.alfresco.service.cmr.view.ExporterService#exportView(org.alfresco.service.cmr.view.Exporter, org.alfresco.service.cmr.view.ExporterCrawler, org.alfresco.service.cmr.view.Exporter)
     */
    @Override
    public void exportView(Exporter exporter, ExporterCrawlerParameters parameters, Exporter progress) {
        ParameterCheck.mandatory("Exporter", exporter);

        VGRChainedExporter chainedExporter = new VGRChainedExporter(new Exporter[] { exporter, progress });
        DefaultCrawler crawler = new DefaultCrawler();
        crawler.export(parameters, chainedExporter);
    }

    /**
     * Create an XML Exporter that exports repository information to the specified
     * output stream in xml format.
     * 
     * @param viewWriter  the output stream to write to
     * @param referenceType  the format of references to export
     * @return  the xml exporter
     */
    private Exporter createXMLExporter(OutputStream viewWriter, ReferenceType referenceType) {
        // Define output format
        OutputFormat format = OutputFormat.createPrettyPrint();
        format.setNewLineAfterDeclaration(false);
        format.setIndentSize(indentSize);
        format.setEncoding("UTF-8");

        // Construct an XML Exporter
        try {
            XMLWriter writer = new XMLWriter(viewWriter, format);
            VGRViewXMLExporter exporter = new VGRViewXMLExporter(namespaceService, nodeService, searchService,
                    dictionaryService, permissionService, writer);
            exporter.setReferenceType(referenceType);
            return exporter;
        } catch (UnsupportedEncodingException e) {
            throw new ExporterException("Failed to create XML Writer for export", e);
        } catch (Exception e) {
            throw new ExporterException("Failed to create XML Writer for export", e);
        }
    }

    /**
     * Responsible for navigating the Repository from specified location and invoking
     * the provided exporter call-back for the actual export implementation.
     * 
     * @author David Caruana
     */
    private class DefaultCrawler implements ExporterCrawler {
        private ExporterContext context;
        private Map<NodeRef, NodeRef> nodesWithSecondaryLinks = new HashMap<NodeRef, NodeRef>();
        private Map<NodeRef, NodeRef> nodesWithAssociations = new HashMap<NodeRef, NodeRef>();

        /* (non-Javadoc)
         * @see org.alfresco.service.cmr.view.ExporterCrawler#export(org.alfresco.service.cmr.view.Exporter)
         */
        @Override
        public void export(ExporterCrawlerParameters parameters, Exporter exporter) {
            // Initialise Crawler
            nodesWithSecondaryLinks.clear();
            nodesWithAssociations.clear();
            context = new ExporterContextImpl(parameters);
            exporter.start(context);

            //
            // Export Nodes
            //

            while (context.canRetrieve()) {
                // determine if root repository node
                NodeRef nodeRef = context.getExportOf();
                if (parameters.isCrawlSelf()) {
                    // export root node of specified export location
                    walkStartNamespaces(parameters, exporter);
                    boolean rootNode = nodeService.getRootNode(nodeRef.getStoreRef()).equals(nodeRef);
                    walkNode(nodeRef, parameters, exporter, rootNode);
                    walkEndNamespaces(parameters, exporter);
                } else if (parameters.isCrawlChildNodes()) {
                    // export child nodes only
                    List<ChildAssociationRef> childAssocs = nodeService.getChildAssocs(nodeRef);
                    for (ChildAssociationRef childAssoc : childAssocs) {
                        walkStartNamespaces(parameters, exporter);
                        walkNode(childAssoc.getChildRef(), parameters, exporter, false);
                        walkEndNamespaces(parameters, exporter);
                    }
                }

                //
                // Export Secondary Links between Nodes
                //
                for (NodeRef nodeWithAssociations : nodesWithSecondaryLinks.keySet()) {
                    walkStartNamespaces(parameters, exporter);
                    walkNodeSecondaryLinks(nodeWithAssociations, parameters, exporter);
                    walkEndNamespaces(parameters, exporter);
                }

                //
                // Export Associations between Nodes
                //

                for (NodeRef nodeWithAssociations : nodesWithAssociations.keySet()) {
                    walkStartNamespaces(parameters, exporter);
                    walkNodeAssociations(nodeWithAssociations, parameters, exporter);
                    walkEndNamespaces(parameters, exporter);
                }

                context.setNextValue();
            }
            exporter.end();
        }

        /**
         * Call-backs for start of Namespace scope
         */
        private void walkStartNamespaces(ExporterCrawlerParameters parameters, Exporter exporter) {
            Collection<String> prefixes = namespaceService.getPrefixes();
            for (String prefix : prefixes) {
                if (!prefix.equals("xml")) {
                    String uri = namespaceService.getNamespaceURI(prefix);
                    exporter.startNamespace(prefix, uri);
                }
            }
        }

        /**
         * Call-backs for end of Namespace scope
         */
        private void walkEndNamespaces(ExporterCrawlerParameters parameters, Exporter exporter) {
            Collection<String> prefixes = namespaceService.getPrefixes();
            for (String prefix : prefixes) {
                if (!prefix.equals("xml")) {
                    exporter.endNamespace(prefix);
                }
            }
        }

        /**
         * Navigate a Node.
         * 
         * @param nodeRef  the node to navigate
         */
        private void walkNode(NodeRef nodeRef, ExporterCrawlerParameters parameters, Exporter exporter,
                boolean exportAsRef) {
            // Export node (but only if it's not excluded from export)
            QName type = nodeService.getType(nodeRef);
            if (isExcludedURI(parameters.getExcludeNamespaceURIs(), type.getNamespaceURI())) {
                return;
            }

            // explicitly included ?
            if (parameters.getIncludedPaths() != null) {
                String nodePathPrefixString = nodeService.getPath(nodeRef).toPrefixString(namespaceService);
                if (!isIncludedPath(parameters.getIncludedPaths(), nodePathPrefixString)) {
                    return;
                }
            }

            // export node as reference to node, or as the actual node
            if (exportAsRef) {
                exporter.startReference(nodeRef, null);
            } else {
                exporter.startNode(nodeRef);
            }

            // Export node aspects
            exporter.startAspects(nodeRef);
            Set<QName> aspects = nodeService.getAspects(nodeRef);
            for (QName aspect : aspects) {
                if (isExcludedURI(parameters.getExcludeNamespaceURIs(), aspect.getNamespaceURI())) {
                    continue;
                } else if (isExcludedAspect(parameters.getExcludeAspects(), aspect)) {
                    continue;
                } else {
                    exporter.startAspect(nodeRef, aspect);
                    exporter.endAspect(nodeRef, aspect);
                }
            }
            exporter.endAspects(nodeRef);

            // Export node permissions
            AccessStatus readPermission = permissionService.hasPermission(nodeRef,
                    PermissionService.READ_PERMISSIONS);
            if (authenticationService.isCurrentUserTheSystemUser() || readPermission.equals(AccessStatus.ALLOWED)) {
                Set<AccessPermission> permissions = permissionService.getAllSetPermissions(nodeRef);
                boolean inheritPermissions = permissionService.getInheritParentPermissions(nodeRef);
                if (permissions.size() > 0 || !inheritPermissions) {
                    exporter.startACL(nodeRef);
                    for (AccessPermission permission : permissions) {
                        if (permission.isSetDirectly()) {
                            exporter.permission(nodeRef, permission);
                        }
                    }
                    exporter.endACL(nodeRef);
                }
            }

            // Export node properties
            exporter.startProperties(nodeRef);
            boolean aware = MLPropertyInterceptor.setMLAware(true);
            Map<QName, Serializable> properties = nodeService.getProperties(nodeRef);
            MLPropertyInterceptor.setMLAware(aware);
            for (QName property : properties.keySet()) {
                // filter out properties whose namespace is excluded
                if (isExcludedURI(parameters.getExcludeNamespaceURIs(), property.getNamespaceURI())) {
                    continue;
                }
                if (isExcludedAspectProperty(parameters.getExcludeAspects(), property)) {
                    continue;
                }

                // filter out properties whose value is null, if not required
                Object value = properties.get(property);
                if (!parameters.isCrawlNullProperties() && value == null) {
                    continue;
                }

                // start export of property
                exporter.startProperty(nodeRef, property);

                if (value instanceof Collection) {
                    exporter.startValueCollection(nodeRef, property);
                    int index = 0;
                    for (Object valueInCollection : (Collection<?>) value) {
                        walkProperty(nodeRef, property, valueInCollection, index, parameters, exporter);
                        index++;
                    }
                    exporter.endValueCollection(nodeRef, property);
                } else {
                    if (value instanceof MLText) {
                        MLText valueMLT = (MLText) value;
                        Set<Locale> locales = valueMLT.getLocales();
                        for (Locale locale : locales) {
                            String localeValue = valueMLT.getValue(locale);
                            if (localeValue == null) {
                                walkProperty(nodeRef, property, localeValue, -1, parameters, exporter);
                                continue;
                            }
                            exporter.startValueMLText(nodeRef, locale, false);
                            walkProperty(nodeRef, property, localeValue, -1, parameters, exporter);
                            exporter.endValueMLText(nodeRef);
                        }
                    } else {
                        walkProperty(nodeRef, property, value, -1, parameters, exporter);
                    }
                }

                // end export of property
                exporter.endProperty(nodeRef, property);
            }
            exporter.endProperties(nodeRef);
            // Signal end of node
            // export node as reference to node, or as the actual node
            if (exportAsRef) {
                exporter.endReference(nodeRef);
            } else {
                exporter.endNode(nodeRef);
            }
        }

        /**
         * Export Property
         * 
         * @param nodeRef
         * @param property
         * @param value
         * @param index
         * @param parameters
         * @param exporter
         */
        private void walkProperty(NodeRef nodeRef, QName property, Object value, int index,
                ExporterCrawlerParameters parameters, Exporter exporter) {
            // determine data type of value
            PropertyDefinition propDef = dictionaryService.getProperty(property);
            DataTypeDefinition dataTypeDef = propDef == null ? null : propDef.getDataType();
            QName valueDataType = null;
            if (dataTypeDef == null || dataTypeDef.getName().equals(DataTypeDefinition.ANY)) {
                dataTypeDef = value == null ? null : dictionaryService.getDataType(value.getClass());
                if (dataTypeDef != null) {
                    valueDataType = dataTypeDef.getName();
                }
            } else {
                valueDataType = dataTypeDef.getName();
            }

            if (valueDataType == null || !valueDataType.equals(DataTypeDefinition.CONTENT)) {
                // Export non content data types
                try {
                    exporter.value(nodeRef, property, value, index);
                } catch (TypeConversionException e) {
                    exporter.warning("Value of property " + property + " could not be converted to xml string");
                    exporter.value(nodeRef, property, value == null ? null : value.toString(), index);
                }
            } else {
                // export property of datatype CONTENT
                ContentReader reader = contentService.getReader(nodeRef, property);
                if (!parameters.isCrawlContent() || reader == null || reader.exists() == false) {
                    // export an empty url for the content
                    ContentData contentData = (ContentData) value;
                    ContentData noContentURL = null;
                    if (contentData == null) {
                        noContentURL = new ContentData("", null, 0L, "UTF-8");
                    } else {
                        noContentURL = new ContentData("", contentData.getMimetype(), contentData.getSize(),
                                contentData.getEncoding());
                    }
                    exporter.content(nodeRef, property, null, noContentURL, index);
                    exporter.warning("Skipped content for property " + property + " on node " + nodeRef);
                } else {
                    InputStream inputStream = reader.getContentInputStream();
                    try {
                        exporter.content(nodeRef, property, inputStream, reader.getContentData(), index);
                    } finally {
                        try {
                            inputStream.close();
                        } catch (IOException e) {
                            throw new ExporterException("Failed to export node content for node " + nodeRef, e);
                        }
                    }
                }
            }
        }

        /**
         * Export Secondary Links
         * 
         * @param nodeRef
         * @param parameters
         * @param exporter
         */
        private void walkNodeSecondaryLinks(NodeRef nodeRef, ExporterCrawlerParameters parameters,
                Exporter exporter) {
            // sort associations into assoc type buckets filtering out unneccessary associations
            Map<QName, List<ChildAssociationRef>> assocTypes = new HashMap<QName, List<ChildAssociationRef>>();
            List<ChildAssociationRef> childAssocs = nodeService.getChildAssocs(nodeRef);
            for (ChildAssociationRef childAssoc : childAssocs) {
                // determine if child association should be exported
                QName childAssocType = childAssoc.getTypeQName();
                if (isExcludedURI(parameters.getExcludeNamespaceURIs(), childAssocType.getNamespaceURI())) {
                    continue;
                }
                if (isExcludedChildAssoc(parameters.getExcludeChildAssocs(), childAssocType)) {
                    continue;
                }
                if (isExcludedAspectAssociation(parameters.getExcludeAspects(), childAssocType)) {
                    continue;
                }
                if (childAssoc.isPrimary()) {
                    continue;
                }
                if (!isWithinExport(childAssoc.getChildRef(), parameters)) {
                    continue;
                }

                List<ChildAssociationRef> assocRefs = assocTypes.get(childAssocType);
                if (assocRefs == null) {
                    assocRefs = new ArrayList<ChildAssociationRef>();
                    assocTypes.put(childAssocType, assocRefs);
                }
                assocRefs.add(childAssoc);
            }

            // output each association type bucket
            if (assocTypes.size() > 0) {
                exporter.startReference(nodeRef, null);
                exporter.startAssocs(nodeRef);
                for (Map.Entry<QName, List<ChildAssociationRef>> assocType : assocTypes.entrySet()) {
                    List<ChildAssociationRef> assocRefs = assocType.getValue();
                    if (assocRefs.size() > 0) {
                        exporter.startAssoc(nodeRef, assocType.getKey());
                        for (ChildAssociationRef assocRef : assocRefs) {
                            exporter.startReference(assocRef.getChildRef(), assocRef.getQName());
                            exporter.endReference(assocRef.getChildRef());
                        }
                        exporter.endAssoc(nodeRef, assocType.getKey());
                    }
                }
                exporter.endAssocs(nodeRef);
                exporter.endReference(nodeRef);
            }
        }

        /**
         * Export Node Associations
         * 
         * @param nodeRef
         * @param parameters
         * @param exporter
         */
        private void walkNodeAssociations(NodeRef nodeRef, ExporterCrawlerParameters parameters,
                Exporter exporter) {
            // sort associations into assoc type buckets filtering out unneccessary associations
            Map<QName, List<AssociationRef>> assocTypes = new HashMap<QName, List<AssociationRef>>();
            List<AssociationRef> assocs = nodeService.getTargetAssocs(nodeRef, RegexQNamePattern.MATCH_ALL);
            for (AssociationRef assoc : assocs) {
                QName assocType = assoc.getTypeQName();
                if (isExcludedURI(parameters.getExcludeNamespaceURIs(), assocType.getNamespaceURI())) {
                    continue;
                }
                if (!isWithinExport(assoc.getTargetRef(), parameters)) {
                    continue;
                }

                List<AssociationRef> assocRefs = assocTypes.get(assocType);
                if (assocRefs == null) {
                    assocRefs = new ArrayList<AssociationRef>();
                    assocTypes.put(assocType, assocRefs);
                }
                assocRefs.add(assoc);
            }

            // output each association type bucket
            if (assocTypes.size() > 0) {
                exporter.startReference(nodeRef, null);
                exporter.startAssocs(nodeRef);
                for (Map.Entry<QName, List<AssociationRef>> assocType : assocTypes.entrySet()) {
                    List<AssociationRef> assocRefs = assocType.getValue();
                    if (assocRefs.size() > 0) {
                        exporter.startAssoc(nodeRef, assocType.getKey());
                        for (AssociationRef assocRef : assocRefs) {
                            exporter.startReference(assocRef.getTargetRef(), null);
                            exporter.endReference(assocRef.getTargetRef());
                        }
                        exporter.endAssoc(nodeRef, assocType.getKey());
                    }
                }
                exporter.endAssocs(nodeRef);
                exporter.endReference(nodeRef);
            }
        }

        /**
         * Is the specified URI an excluded URI?
         * 
         * @param uri  the URI to test
         * @return  true => it's excluded from the export
         */
        private boolean isExcludedURI(String[] excludeNamespaceURIs, String uri) {
            for (String excludedURI : excludeNamespaceURIs) {
                if (uri.equals(excludedURI)) {
                    return true;
                }
            }
            return false;
        }

        private boolean isIncludedPath(String[] includedPaths, String path) {
            for (String includePath : includedPaths) {
                // note: allow parents or children - e.g. if included path is /a/b/c then /, /a, /a/b, /a/b/c, /a/b/c/d, /a/b/c/d/e are all included
                if (includePath.startsWith(path) || path.startsWith(includePath)) {
                    return true;
                }
            }

            return false;
        }

        /**
         * Is the aspect unexportable?
         * 
         * @param aspectQName           the aspect name
         * @return                      <tt>true</tt> if the aspect can't be exported
         */
        private boolean isExcludedAspect(QName[] excludeAspects, QName aspectQName) {
            if (aspectQName.equals(ContentModel.ASPECT_MULTILINGUAL_DOCUMENT)
                    || aspectQName.equals(ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION)) {
                return true;
            } else {
                for (QName excludeAspect : excludeAspects) {
                    if (aspectQName.equals(excludeAspect)) {
                        return true;
                    }
                }
            }
            return false;
        }

        /**
         * Is the child association unexportable?
         * 
         * @param childAssocQName           the child assoc name
         * @return                      <tt>true</tt> if the aspect can't be exported
         */
        private boolean isExcludedChildAssoc(QName[] excludeChildAssocs, QName childAssocQName) {
            for (QName excludeChildAssoc : excludeChildAssocs) {
                if (childAssocQName.equals(excludeChildAssoc)) {
                    return true;
                }
            }
            return false;
        }

        /**
         * Is the property unexportable?
         */
        private boolean isExcludedAspectProperty(QName[] excludeAspects, QName propertyQName) {
            PropertyDefinition propDef = dictionaryService.getProperty(propertyQName);
            if (propDef == null) {
                return false;
            }

            ClassDefinition classDef = propDef.getContainerClass();
            if (classDef == null || !classDef.isAspect()) {
                return false;
            }

            return isExcludedAspect(excludeAspects, classDef.getName());
        }

        /**
         * Is the association unexportable?
         */
        private boolean isExcludedAspectAssociation(QName[] excludeAspects, QName associationQName) {
            AssociationDefinition assocDef = dictionaryService.getAssociation(associationQName);
            if (assocDef == null) {
                return false;
            }

            ClassDefinition classDef = assocDef.getSourceClass();
            if (classDef == null || !classDef.isAspect()) {
                return false;
            }

            return isExcludedAspect(excludeAspects, classDef.getName());
        }

        /**
         * Determine if specified Node Reference is within the set of nodes to be exported
         * 
         * @param nodeRef  node reference to check
         * @return  true => node reference is within export set
         */
        private boolean isWithinExport(NodeRef nodeRef, ExporterCrawlerParameters parameters) {
            boolean isWithin = false;

            // Current strategy is to determine if node is a child of the root exported node
            for (NodeRef exportRoot : context.getExportList()) {
                if (nodeRef.equals(exportRoot) && parameters.isCrawlSelf() == true) {
                    // node to export is the root export node (and root is to be exported)
                    isWithin = true;
                } else {
                    // locate export root in primary parent path of node
                    Path nodePath = nodeService.getPath(nodeRef);
                    for (int i = nodePath.size() - 1; i >= 0; i--) {
                        Path.ChildAssocElement pathElement = (Path.ChildAssocElement) nodePath.get(i);
                        if (pathElement.getRef().getChildRef().equals(exportRoot)) {
                            isWithin = true;
                            break;
                        }
                    }
                }
            }
            return isWithin;
        }
    }

    /**
     * Exporter Context
     */
    private class ExporterContextImpl implements ExporterContext {
        private NodeRef[] exportList;
        private NodeRef[] parentList;
        private String exportedBy;
        private Date exportedDate;
        private String exporterVersion;

        private int index;

        /**
         * Construct
         * 
         * @param parameters  exporter crawler parameters
         */
        public ExporterContextImpl(ExporterCrawlerParameters parameters) {
            index = 0;

            // get current user performing export
            String currentUserName = authenticationService.getCurrentUserName();
            exportedBy = currentUserName == null ? "unknown" : currentUserName;

            // get current date
            exportedDate = new Date(System.currentTimeMillis());

            // get list of exported nodes
            exportList = parameters.getExportFrom() == null ? null : parameters.getExportFrom().getNodeRefs();
            if (exportList == null) {
                // multi-node export
                exportList = new NodeRef[1];
                NodeRef exportOf = getNodeRef(parameters.getExportFrom());
                exportList[0] = exportOf;
            }
            parentList = new NodeRef[exportList.length];
            for (int i = 0; i < exportList.length; i++) {
                parentList[i] = getParent(exportList[i], parameters.isCrawlSelf());
            }

            // get exporter version
            exporterVersion = descriptorService.getServerDescriptor().getVersion();
        }

        @Override
        public boolean canRetrieve() {
            return index < exportList.length;
        }

        @Override
        public int setNextValue() {
            return ++index;
        }

        @Override
        public void resetContext() {
            index = 0;
        }

        /* (non-Javadoc)
         * @see org.alfresco.service.cmr.view.ExporterContext#getExportedBy()
         */
        @Override
        public String getExportedBy() {
            return exportedBy;
        }

        /* (non-Javadoc)
         * @see org.alfresco.service.cmr.view.ExporterContext#getExportedDate()
         */
        @Override
        public Date getExportedDate() {
            return exportedDate;
        }

        /* (non-Javadoc)
         * @see org.alfresco.service.cmr.view.ExporterContext#getExporterVersion()
         */
        @Override
        public String getExporterVersion() {
            return exporterVersion;
        }

        /* (non-Javadoc)
         * @see org.alfresco.service.cmr.view.ExporterContext#getExportOf()
         */
        @Override
        public NodeRef getExportOf() {
            if (canRetrieve()) {
                return exportList[index];
            }
            return null;
        }

        /*
         * (non-Javadoc)
         * @see org.alfresco.service.cmr.view.ExporterContext#getExportParent()
         */
        @Override
        public NodeRef getExportParent() {
            if (canRetrieve()) {
                return parentList[index];
            }
            return null;
        }

        /*
         * (non-Javadoc)
         * @see org.alfresco.service.cmr.view.ExporterContext#getExportList()
         */
        @Override
        public NodeRef[] getExportList() {
            return exportList;
        }

        /*
         * (non-Javadoc)
         * @see org.alfresco.service.cmr.view.ExporterContext#getExportParentList()
         */
        @Override
        public NodeRef[] getExportParentList() {
            return parentList;
        }

        /**
         * Get the Node Ref from the specified Location
         * 
         * @param location  the location
         * @return  the node reference
         */
        private NodeRef getNodeRef(Location location) {
            ParameterCheck.mandatory("Location", location);

            // Establish node to export from
            NodeRef nodeRef = location == null ? null : location.getNodeRef();
            if (nodeRef == null) {
                // If a specific node has not been provided, default to the root
                nodeRef = nodeService.getRootNode(location.getStoreRef());
            }

            // Resolve to path within node, if one specified
            String path = location == null ? null : location.getPath();
            if (path != null && path.length() > 0) {
                // Create a valid path and search
                List<NodeRef> nodeRefs = searchService.selectNodes(nodeRef, path, null, namespaceService, false);
                if (nodeRefs.size() == 0) {
                    throw new ImporterException("Path " + path + " within node " + nodeRef
                            + " does not exist - the path must resolve to a valid location");
                }
                if (nodeRefs.size() > 1) {
                    throw new ImporterException("Path " + path + " within node " + nodeRef
                            + " found too many locations - the path must resolve to one location");
                }
                nodeRef = nodeRefs.get(0);
            }

            // TODO: Check Node actually exists

            return nodeRef;
        }

        /**
         * Gets the parent node of the items to be exported
         * 
         * @param exportOf
         * @param exportSelf
         * @return
         */
        private NodeRef getParent(NodeRef exportOf, boolean exportSelf) {
            NodeRef parent = null;

            if (exportSelf) {
                NodeRef rootNode = nodeService.getRootNode(exportOf.getStoreRef());
                if (rootNode.equals(exportOf)) {
                    parent = exportOf;
                } else {
                    ChildAssociationRef parentRef = nodeService.getPrimaryParent(exportOf);
                    parent = parentRef.getParentRef();
                }
            } else {
                parent = exportOf;
            }

            return parent;
        }

    }

}