fr.openwide.talendalfresco.alfresco.importer.ViewParserBase.java Source code

Java tutorial

Introduction

Here is the source code for fr.openwide.talendalfresco.alfresco.importer.ViewParserBase.java

Source

/*
 * Copyright (C) 2005-2009 Alfresco Software Limited.
 *
 * This program 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 2
 * of the License, or (at your option) any later version.
    
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
    
 * As a special exception to the terms and conditions of version 2.0 of 
 * the GPL, you may redistribute this Program in connection with Free/Libre 
 * and Open Source Software ("FLOSS") applications as described in Alfresco's 
 * FLOSS exception.  You should have recieved a copy of the text describing 
 * the FLOSS exception, and it is also available here: 
 * http://www.alfresco.com/legal/licensing"
 */
package fr.openwide.talendalfresco.alfresco.importer;

import java.io.IOException;
import java.io.Reader;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.Stack;

import org.alfresco.repo.importer.Importer;
import org.alfresco.repo.importer.Parser;
import org.alfresco.repo.importer.view.ElementContext;
import org.alfresco.repo.importer.view.MetaDataContext;
import org.alfresco.repo.importer.view.NodeContext;
import org.alfresco.repo.importer.view.NodeItemContext;
import org.alfresco.repo.importer.view.ParentContext;
import org.alfresco.repo.importer.view.ViewParser;
import org.alfresco.service.cmr.dictionary.AspectDefinition;
import org.alfresco.service.cmr.dictionary.AssociationDefinition;
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.dictionary.TypeDefinition;
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.datatype.DefaultTypeConverter;
import org.alfresco.service.cmr.security.AccessStatus;
import org.alfresco.service.cmr.view.ImporterException;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;

/**
 * [talendalfresco] "almost" copy of ViewParser v3.2 to allow extending it.
 * Changes : changed private to protected to allow override for :
 * startReference(), to allow catching non existing asso refs errors
 * processEndType(), to allow for a separate transactions for rules & behaviours
 * importNode(), to refer to it in overriden sprocessEndType()
 * logger, nodeService
 * ParserContext
 * 
 * Importer for parsing and importing nodes given the Repository View schema.
 * 
 * @author David Caruana
 */
public class ViewParserBase implements Parser {
    // Logger
    protected static final Log logger = LogFactory.getLog(ViewParser.class);

    // View schema elements and attributes
    private static final String VIEW_CHILD_NAME_ATTR = "childName";
    private static final String VIEW_DATATYPE_ATTR = "datatype";
    private static final String VIEW_ISNULL_ATTR = "isNull";
    private static final String VIEW_INHERIT_PERMISSIONS_ATTR = "inherit";
    private static final String VIEW_ACCESS_STATUS_ATTR = "access";
    private static final String VIEW_ID_ATTR = "id";
    private static final String VIEW_IDREF_ATTR = "idref";
    private static final String VIEW_PATHREF_ATTR = "pathref";
    private static final String VIEW_NODEREF_ATTR = "noderef";
    private static final String VIEW_LOCALE_ATTR = "locale";
    private static final QName VIEW_METADATA = QName.createQName(NamespaceService.REPOSITORY_VIEW_1_0_URI,
            "metadata");
    private static final QName VIEW_VALUE_QNAME = QName.createQName(NamespaceService.REPOSITORY_VIEW_1_0_URI,
            "value");
    private static final QName VIEW_VALUES_QNAME = QName.createQName(NamespaceService.REPOSITORY_VIEW_1_0_URI,
            "values");
    private static final QName VIEW_ASPECTS = QName.createQName(NamespaceService.REPOSITORY_VIEW_1_0_URI,
            "aspects");
    private static final QName VIEW_PROPERTIES = QName.createQName(NamespaceService.REPOSITORY_VIEW_1_0_URI,
            "properties");
    private static final QName VIEW_ASSOCIATIONS = QName.createQName(NamespaceService.REPOSITORY_VIEW_1_0_URI,
            "associations");
    private static final QName VIEW_ACL = QName.createQName(NamespaceService.REPOSITORY_VIEW_1_0_URI, "acl");
    private static final QName VIEW_ACE = QName.createQName(NamespaceService.REPOSITORY_VIEW_1_0_URI, "ace");
    private static final QName VIEW_AUTHORITY = QName.createQName(NamespaceService.REPOSITORY_VIEW_1_0_URI,
            "authority");
    private static final QName VIEW_PERMISSION = QName.createQName(NamespaceService.REPOSITORY_VIEW_1_0_URI,
            "permission");
    private static final QName VIEW_REFERENCE = QName.createQName(NamespaceService.REPOSITORY_VIEW_1_0_URI,
            "reference");
    private static final QName VIEW_ML_VALUE = QName.createQName(NamespaceService.REPOSITORY_VIEW_1_0_URI,
            "mlvalue");

    // XML Pull Parser Factory
    private XmlPullParserFactory factory;

    // Supporting services
    private NamespaceService namespaceService;
    protected NodeService nodeService;
    private DictionaryService dictionaryService;

    // Parser Context maintained during each parse
    protected class ParserContext {
        Importer importer;
        DictionaryService dictionaryService;
        Stack<ElementContext> elementStack;
        Map<String, NodeRef> importIds = new HashMap<String, NodeRef>();
    }

    /**
     * Construct
     */
    public ViewParserBase() {
        try {
            // Construct Xml Pull Parser Factory
            factory = XmlPullParserFactory.newInstance(System.getProperty(XmlPullParserFactory.PROPERTY_NAME),
                    this.getClass());
            factory.setNamespaceAware(true);
        } catch (XmlPullParserException e) {
            throw new ImporterException("Failed to initialise view importer", e);
        }
    }

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

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

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

    /* (non-Javadoc)
     * @see org.alfresco.repo.importer.Parser#parse(java.io.Reader, org.alfresco.repo.importer.Importer)
     */
    public void parse(Reader viewReader, Importer importer) {
        try {
            XmlPullParser xpp = factory.newPullParser();
            xpp.setInput(viewReader);

            ParserContext parserContext = new ParserContext();
            parserContext.importer = importer;
            parserContext.dictionaryService = dictionaryService;
            parserContext.elementStack = new Stack<ElementContext>();

            try {
                for (int eventType = xpp.getEventType(); eventType != XmlPullParser.END_DOCUMENT; eventType = xpp
                        .next()) {
                    switch (eventType) {
                    case XmlPullParser.START_TAG: {
                        if (xpp.getDepth() == 1) {
                            processRoot(xpp, parserContext);
                        } else {
                            processStartElement(xpp, parserContext);
                        }
                        break;
                    }
                    case XmlPullParser.END_TAG: {
                        processEndElement(xpp, parserContext);
                        break;
                    }
                    }
                }
            } catch (Exception e) {
                throw new ImporterException("Failed to import package at line " + xpp.getLineNumber() + "; column "
                        + xpp.getColumnNumber() + " due to error: " + e.getMessage(), e);
            }
        } catch (XmlPullParserException e) {
            throw new ImporterException("Failed to parse view", e);
        }
    }

    /**
     * Process start of xml element
     * 
     * @param xpp
     * @param contextStack
     * @throws XmlPullParserException
     * @throws IOException
     */
    private void processStartElement(XmlPullParser xpp, ParserContext parserContext)
            throws XmlPullParserException, IOException {
        // Extract qualified name
        QName defName = getName(xpp);

        // Process the element
        Object element = parserContext.elementStack.peek();

        // Handle special view directives
        if (defName.equals(VIEW_METADATA)) {
            MetaDataContext metaDataContext = new MetaDataContext(defName, (ElementContext) element);
            parserContext.elementStack.push(metaDataContext);

            if (logger.isDebugEnabled())
                logger.debug(indentLog("Pushed " + metaDataContext, parserContext.elementStack.size() - 1));
        } else if (defName.equals(VIEW_ASPECTS) || defName.equals(VIEW_PROPERTIES)
                || defName.equals(VIEW_ASSOCIATIONS) || defName.equals(VIEW_ACL)) {
            if (element instanceof NodeItemContext) {
                throw new ImporterException("Cannot nest element " + defName + " within "
                        + ((NodeItemContext) element).getElementName());
            }
            if (!(element instanceof NodeContext)) {
                throw new ImporterException("Element " + defName + " can only be declared within a node");
            }
            NodeContext node = (NodeContext) element;
            NodeItemContext nodeItemContext = new NodeItemContext(defName, node);
            parserContext.elementStack.push(nodeItemContext);

            if (logger.isDebugEnabled())
                logger.debug(indentLog("Pushed " + nodeItemContext, parserContext.elementStack.size() - 1));

            // process ACL specific attributes
            if (defName.equals(VIEW_ACL)) {
                processACL(xpp, parserContext);
            }
        } else {
            if (element instanceof MetaDataContext) {
                processMetaData(xpp, defName, parserContext);
            } else if (element instanceof ParentContext) {
                if (defName.equals(VIEW_REFERENCE)) {
                    // Process reference
                    processStartReference(xpp, defName, parserContext);
                } else {
                    // Process type definition 
                    TypeDefinition typeDef = dictionaryService.getType(defName);
                    if (typeDef == null) {
                        throw new ImporterException(
                                "Type " + defName + " has not been defined in the Repository dictionary");
                    }
                    processStartType(xpp, typeDef, parserContext);
                }
                return;
            } else if (element instanceof NodeContext) {
                // Process children of node
                // Note: Process in the following order: properties aspects, and associations
                Object def = determineDefinition(((NodeContext) element), defName);
                if (def == null) {
                    throw new ImporterException(
                            "Definition " + defName + " is not valid; cannot find in Repository dictionary");
                }

                if (def instanceof AspectDefinition) {
                    processAspect(xpp, (AspectDefinition) def, parserContext);
                    return;
                } else if (def instanceof PropertyDefinition) {
                    processProperty(xpp, ((PropertyDefinition) def).getName(), parserContext);
                    return;
                } else if (def instanceof AssociationDefinition) {
                    processStartAssoc(xpp, (AssociationDefinition) def, parserContext);
                    return;
                }
            } else if (element instanceof NodeItemContext) {
                NodeItemContext nodeItem = (NodeItemContext) element;
                NodeContext node = nodeItem.getNodeContext();
                QName itemName = nodeItem.getElementName();
                if (itemName.equals(VIEW_ASPECTS)) {
                    AspectDefinition def = node.determineAspect(defName);
                    if (def == null) {
                        throw new ImporterException(
                                "Aspect name " + defName + " is not valid; cannot find in Repository dictionary");
                    }
                    processAspect(xpp, def, parserContext);
                } else if (itemName.equals(VIEW_PROPERTIES)) {
                    // Note: Allow properties which do not have a data dictionary definition
                    processProperty(xpp, defName, parserContext);
                } else if (itemName.equals(VIEW_ASSOCIATIONS)) {
                    AssociationDefinition def = (AssociationDefinition) node.determineAssociation(defName);
                    if (def == null) {
                        throw new ImporterException("Association name " + defName
                                + " is not valid; cannot find in Repository dictionary");
                    }
                    processStartAssoc(xpp, (AssociationDefinition) def, parserContext);
                } else if (itemName.equals(VIEW_ACL)) {
                    processAccessControlEntry(xpp, parserContext);
                }
            }
        }
    }

    public Object determineDefinition(NodeContext ctx, QName defName) {
        Object def = ctx.determineProperty(defName);
        if (def == null) {
            def = ctx.determineAspect(defName);
            if (def == null) {
                def = ctx.determineAssociation(defName);
            }
        }
        return def;
    }

    /**
     * Process Root
     * 
     * @param xpp
     * @param parentRef
     * @param childAssocType
     * @param configuration
     * @param progress
     * @param contextStack
     * @throws XmlPullParserException
     * @throws IOException
     */
    private void processRoot(XmlPullParser xpp, ParserContext parserContext)
            throws XmlPullParserException, IOException {
        ParentContext parent = new ParentContext(getName(xpp), parserContext.dictionaryService,
                parserContext.importer);
        parserContext.elementStack.push(parent);

        if (logger.isDebugEnabled())
            logger.debug(indentLog("Pushed " + parent, parserContext.elementStack.size() - 1));
    }

    /**
     * Process meta-data
     * 
     * @param xpp
     * @param metaDataName
     * @param contextStack
     * @throws XmlPullParserException
     * @throws IOException
     */
    private void processMetaData(XmlPullParser xpp, QName metaDataName, ParserContext parserContext)
            throws XmlPullParserException, IOException {
        MetaDataContext metaData = (MetaDataContext) parserContext.elementStack.peek();

        String value = null;

        int eventType = xpp.next();
        if (eventType == XmlPullParser.TEXT) {
            // Extract value
            value = xpp.getText();
            eventType = xpp.next();
        }
        if (eventType != XmlPullParser.END_TAG) {
            throw new ImporterException("Meta data element " + metaDataName + " is missing end tag");
        }

        metaData.setProperty(metaDataName, value);
    }

    /**
     * Process start of a node definition
     * 
     * @param xpp
     * @param typeDef
     * @param contextStack
     * @throws XmlPullParserException
     * @throws IOException
     */
    private void processStartType(XmlPullParser xpp, TypeDefinition typeDef, ParserContext parserContext)
            throws XmlPullParserException, IOException {
        ParentContext parent = (ParentContext) parserContext.elementStack.peek();
        NodeContext node = new NodeContext(typeDef.getName(), parent, typeDef);

        // Extract child name if explicitly defined
        String childName = xpp.getAttributeValue(NamespaceService.REPOSITORY_VIEW_1_0_URI, VIEW_CHILD_NAME_ATTR);
        if (childName != null && childName.length() > 0) {
            node.setChildName(childName);
        }

        // Extract import id if explicitly defined
        String importId = xpp.getAttributeValue(NamespaceService.REPOSITORY_VIEW_1_0_URI, VIEW_ID_ATTR);
        if (importId != null && importId.length() > 0) {
            node.setImportId(importId);
        }

        parserContext.elementStack.push(node);

        if (logger.isDebugEnabled())
            logger.debug(indentLog("Pushed " + node, parserContext.elementStack.size() - 1));
    }

    /**
     * Process start reference
     * 
     * @param xpp
     * @param typeDef
     * @param contextStack
     * @throws XmlPullParserException
     * @throws IOException
     */
    protected void processStartReference(XmlPullParser xpp, QName refName, ParserContext parserContext)
            throws XmlPullParserException, IOException {
        ParentContext parent = (ParentContext) parserContext.elementStack.peek();
        NodeContext node = new NodeContext(refName, parent, null);
        node.setReference(true);

        // Extract Import scoped reference Id if explicitly defined
        String idRefAttr = xpp.getAttributeValue(NamespaceService.REPOSITORY_VIEW_1_0_URI, VIEW_IDREF_ATTR);
        String pathRefAttr = xpp.getAttributeValue(NamespaceService.REPOSITORY_VIEW_1_0_URI, VIEW_PATHREF_ATTR);
        String nodeRefAttr = xpp.getAttributeValue(NamespaceService.REPOSITORY_VIEW_1_0_URI, VIEW_NODEREF_ATTR);
        if ((idRefAttr != null && idRefAttr.length() > 0) && (pathRefAttr != null && pathRefAttr.length() > 0)
                && (nodeRefAttr != null && nodeRefAttr.length() > 0)) {
            // Do not support both IDREF and PATHREF
            throw new ImporterException("Only one of " + VIEW_IDREF_ATTR + " or " + VIEW_PATHREF_ATTR + " or "
                    + VIEW_NODEREF_ATTR + " can be specified.");
        }

        // Convert to Node Reference
        NodeRef nodeRef = null;
        if (nodeRefAttr != null) {
            nodeRef = new NodeRef(nodeRefAttr);
        } else if (idRefAttr != null && idRefAttr.length() > 0) {
            // retrieve uuid from previously imported node
            nodeRef = getImportReference(parserContext, idRefAttr);
            if (nodeRef == null) {
                throw new ImporterException("Cannot find node referenced by id " + idRefAttr);
            }
        } else if (pathRefAttr != null && pathRefAttr.length() > 0) {
            nodeRef = parserContext.importer.resolvePath(pathRefAttr);
            if (nodeRef == null) {
                throw new ImporterException("Cannot find node referenced by path " + pathRefAttr);
            }
        }

        // Establish node definition
        node.setUUID(nodeRef.getId());
        node.setTypeDefinition(dictionaryService.getType(nodeService.getType(nodeRef)));
        Set<QName> aspects = nodeService.getAspects(nodeRef);
        for (QName aspect : aspects) {
            node.addAspect(dictionaryService.getAspect(aspect));
        }

        // Extract child name if explicitly defined
        String childName = xpp.getAttributeValue(NamespaceService.REPOSITORY_VIEW_1_0_URI, VIEW_CHILD_NAME_ATTR);
        if (childName != null && childName.length() > 0) {
            node.setChildName(childName);
        }

        // Extract import id if explicitly defined
        String importId = xpp.getAttributeValue(NamespaceService.REPOSITORY_VIEW_1_0_URI, VIEW_ID_ATTR);
        if (importId != null && importId.length() > 0) {
            node.setImportId(importId);
        }

        parserContext.elementStack.push(node);

        if (logger.isDebugEnabled())
            logger.debug(indentLog("Pushed Reference " + node, parserContext.elementStack.size() - 1));
    }

    /**
     * Process aspect definition
     * 
     * @param xpp
     * @param aspectDef
     * @param contextStack
     * @throws XmlPullParserException
     * @throws IOException
     */
    private void processAspect(XmlPullParser xpp, AspectDefinition aspectDef, ParserContext parserContext)
            throws XmlPullParserException, IOException {
        NodeContext node = peekNodeContext(parserContext.elementStack);
        node.addAspect(aspectDef);

        int eventType = xpp.next();
        if (eventType != XmlPullParser.END_TAG) {
            throw new ImporterException(
                    "Aspect " + aspectDef.getName() + " definition is not valid - it cannot contain any elements");
        }

        if (logger.isDebugEnabled())
            logger.debug(indentLog("Processed aspect " + aspectDef.getName(), parserContext.elementStack.size()));
    }

    /**
     * Process ACL definition
     * 
     * @param xpp
     * @param contextStack
     */
    private void processACL(XmlPullParser xpp, ParserContext parserContext) {
        NodeContext node = peekNodeContext(parserContext.elementStack);

        String strInherit = xpp.getAttributeValue(NamespaceService.REPOSITORY_VIEW_1_0_URI,
                VIEW_INHERIT_PERMISSIONS_ATTR);
        if (strInherit != null) {
            Boolean inherit = Boolean.valueOf(strInherit);
            if (!inherit) {
                node.setInheritPermissions(false);
            }
        }
    }

    /**
     * Process ACE definition
     * 
     * @param xpp
     * @param contextStack
     * @throws XmlPullParserException
     * @throws IOException
     */
    private void processAccessControlEntry(XmlPullParser xpp, ParserContext parserContext)
            throws XmlPullParserException, IOException {
        NodeContext node = peekNodeContext(parserContext.elementStack);

        QName defName = getName(xpp);
        if (!defName.equals(VIEW_ACE)) {
            throw new ImporterException("Expected start element " + VIEW_ACE);
        }

        // extract Access Status
        String access = xpp.getAttributeValue(NamespaceService.REPOSITORY_VIEW_1_0_URI, VIEW_ACCESS_STATUS_ATTR);
        AccessStatus accessStatus = (access == null) ? AccessStatus.ALLOWED
                : AccessStatus.valueOf(AccessStatus.class, access);
        if (accessStatus == null) {
            throw new ImporterException("Permission access status '" + access + "' is not recognised.");
        }

        // extract authority and permission
        String authority = null;
        String permission = null;
        int eventType = xpp.next();
        while (eventType != XmlPullParser.END_TAG) {
            if (eventType == XmlPullParser.START_TAG) {
                defName = getName(xpp);
                if (defName.equals(VIEW_AUTHORITY)) {
                    eventType = xpp.next();
                    if (eventType != XmlPullParser.TEXT) {
                        throw new ImporterException("Element " + VIEW_AUTHORITY + " must have a value");
                    }
                    authority = xpp.getText();
                } else if (defName.equals(VIEW_PERMISSION)) {
                    eventType = xpp.next();
                    if (eventType != XmlPullParser.TEXT) {
                        throw new ImporterException("Element " + VIEW_PERMISSION + " must have a value");
                    }
                    permission = xpp.getText();
                } else {
                    throw new ImporterException(
                            "Expected start element " + VIEW_AUTHORITY + " or " + VIEW_PERMISSION);
                }

                eventType = xpp.next();
                if (eventType != XmlPullParser.END_TAG) {
                    throw new ImporterException("Expected end element " + defName);
                }
                QName endDefName = getName(xpp);
                if (!defName.equals(endDefName)) {
                    throw new ImporterException("Expected end element " + defName);
                }
            }

            eventType = xpp.next();
        }

        // validate authority and permission
        if (authority == null || authority.length() == 0) {
            throw new ImporterException("Authority must be specified");
        }
        if (permission == null || permission.length() == 0) {
            throw new ImporterException("Permisssion must be specified");
        }

        // extract end of ace
        defName = getName(xpp);
        if (!defName.equals(VIEW_ACE)) {
            throw new ImporterException("Expected end element " + VIEW_ACE);
        }

        // update node context
        node.addAccessControlEntry(accessStatus, authority, permission);
    }

    /**
     * Process property definition
     * 
     * @param xpp
     * @param propDef
     * @param contextStack
     * @throws XmlPullParserException
     * @throws IOException
     */
    private void processProperty(XmlPullParser xpp, QName propertyName, ParserContext parserContext)
            throws XmlPullParserException, IOException {
        NodeContext node = peekNodeContext(parserContext.elementStack);

        // Extract single value
        String value = "";
        int eventType = xpp.next();
        if (eventType == XmlPullParser.TEXT) {
            value = xpp.getText();
            eventType = xpp.next();
        }
        if (eventType == XmlPullParser.END_TAG) {
            node.addProperty(propertyName, value);
        } else {
            // Extract collection, if specified
            boolean isCollection = false;
            boolean isMLProperty = false;
            if (eventType == XmlPullParser.START_TAG) {
                QName name = getName(xpp);
                if (name.equals(VIEW_VALUES_QNAME)) {
                    node.addPropertyCollection(propertyName);
                    isCollection = true;
                    eventType = xpp.next();
                    if (eventType == XmlPullParser.TEXT) {
                        eventType = xpp.next();
                    }
                } else if (name.equals(VIEW_ML_VALUE)) {
                    isMLProperty = true;
                }
            }

            // Extract ML value

            if (isMLProperty) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Start parsing MLValue for property: " + propertyName);
                }
                value = "";
                String locale = "";
                node.addDatatype(propertyName, dictionaryService.getDataType(DataTypeDefinition.MLTEXT));
                MLText mlText = new MLText();
                while (isMLProperty) {
                    isMLProperty = false;

                    locale = xpp.getAttributeValue(NamespaceService.REPOSITORY_VIEW_1_0_URI, VIEW_LOCALE_ATTR);
                    eventType = xpp.next();
                    if (eventType == XmlPullParser.TEXT) {
                        value = xpp.getText();
                        eventType = xpp.next();
                    }
                    if (eventType == XmlPullParser.END_TAG) {
                        if (logger.isDebugEnabled()) {
                            logger.debug("Found ML entry: " + locale + "=" + value);
                        }
                        mlText.addValue(DefaultTypeConverter.INSTANCE.convert(Locale.class, locale), value);

                        eventType = xpp.next();
                        if (eventType == XmlPullParser.TEXT) {
                            eventType = xpp.next();
                        }
                    }

                    if (eventType == XmlPullParser.START_TAG) {
                        QName name = getName(xpp);
                        if (name.equals(VIEW_ML_VALUE)) {
                            isMLProperty = true;
                        }
                    }
                }

                if (logger.isDebugEnabled()) {
                    logger.debug("End parsing MLValue for property: " + propertyName);
                }
                node.addProperty(propertyName, mlText);

            }

            // Extract decorated value
            while (eventType == XmlPullParser.START_TAG) {
                QName name = getName(xpp);
                if (!name.equals(VIEW_VALUE_QNAME)) {
                    throw new ImporterException("Invalid view structure - expected element " + VIEW_VALUE_QNAME
                            + " for property " + propertyName);
                }
                QName datatype = QName.createQName(
                        xpp.getAttributeValue(NamespaceService.REPOSITORY_VIEW_1_0_URI, VIEW_DATATYPE_ATTR),
                        namespaceService);
                Boolean isNull = Boolean
                        .valueOf(xpp.getAttributeValue(NamespaceService.REPOSITORY_VIEW_1_0_URI, VIEW_ISNULL_ATTR));
                String decoratedValue = isNull ? null : "";
                eventType = xpp.next();
                if (eventType == XmlPullParser.TEXT) {
                    decoratedValue = xpp.getText();
                    eventType = xpp.next();
                }
                if (eventType == XmlPullParser.END_TAG) {
                    node.addProperty(propertyName, decoratedValue);
                    if (datatype != null) {
                        node.addDatatype(propertyName, dictionaryService.getDataType(datatype));
                    }
                } else {
                    throw new ImporterException("Value for property " + propertyName
                            + " has not been defined correctly - missing end tag");
                }
                eventType = xpp.next();
                if (eventType == XmlPullParser.TEXT) {
                    eventType = xpp.next();
                }
            }

            // End of value
            if (eventType != XmlPullParser.END_TAG) {
                throw new ImporterException(
                        "Invalid view structure - property " + propertyName + " definition is invalid");
            }

            // End of collection
            if (isCollection) {
                eventType = xpp.next();
                if (eventType == XmlPullParser.TEXT) {
                    eventType = xpp.next();
                }
                if (eventType != XmlPullParser.END_TAG) {
                    throw new ImporterException(
                            "Invalid view structure - property " + propertyName + " definition is invalid");
                }
            }
        }

        if (logger.isDebugEnabled())
            logger.debug(indentLog("Processed property " + propertyName, parserContext.elementStack.size()));
    }

    /**
     * Process start of association definition
     * 
     * @param xpp
     * @param AssocDef
     * @param contextStack
     * @throws XmlPullParserException
     * @throws IOException
     */
    private void processStartAssoc(XmlPullParser xpp, AssociationDefinition assocDef, ParserContext parserContext)
            throws XmlPullParserException, IOException {
        NodeContext node = peekNodeContext(parserContext.elementStack);
        importNode(parserContext, node);

        // Construct Child Association Context
        ParentContext parent = new ParentContext(assocDef.getName(), node, assocDef);
        parserContext.elementStack.push(parent);

        if (logger.isDebugEnabled())
            logger.debug(indentLog("Pushed " + parent, parserContext.elementStack.size() - 1));
    }

    /**
     * Process end of xml element
     * 
     * @param xpp
     * @param contextStack
     */
    private void processEndElement(XmlPullParser xpp, ParserContext parserContext) {
        ElementContext element = parserContext.elementStack.peek();
        if (element.getElementName().getLocalName().equals(xpp.getName())
                && element.getElementName().getNamespaceURI().equals(xpp.getNamespace())) {
            element = parserContext.elementStack.pop();

            if (logger.isDebugEnabled())
                logger.debug(indentLog("Popped " + element, parserContext.elementStack.size()));

            if (element instanceof NodeContext) {
                processEndType(parserContext, (NodeContext) element);
            } else if (element instanceof ParentContext) {
                processEndAssoc(parserContext, (ParentContext) element);
            } else if (element instanceof MetaDataContext) {
                processEndMetaData(parserContext, (MetaDataContext) element);
            }
        }
    }

    /**
     * Process end of the type definition
     * 
     * @param node
     */
    protected void processEndType(ParserContext parserContext, NodeContext node) {
        importNode(parserContext, node);
        NodeRef nodeRef = node.getNodeRef();
        node.getImporter().childrenImported(nodeRef);
    }

    /**
     * Process end of the child association
     * 
     * @param context
     */
    private void processEndAssoc(ParserContext parserContext, ParentContext parent) {
    }

    /**
     * Process end of meta data
     * 
     * @param context
     */
    private void processEndMetaData(ParserContext parserContext, MetaDataContext context) {
        context.getImporter().importMetaData(context.getProperties());
    }

    /**
     * Import node
     * 
     * @param parserContext  parser context
     * @param node  node context
     */
    protected void importNode(ParserContext parserContext, NodeContext node) {
        if (node.getNodeRef() == null) {
            // Import Node
            NodeRef nodeRef = node.getImporter().importNode(node);
            node.setNodeRef(nodeRef);

            // Maintain running list of "import" scoped ids
            String importId = node.getImportId();
            if (importId != null && importId.length() > 0) {
                createImportReference(parserContext, importId, nodeRef);
            }
        }
    }

    /**
     * Maps an Import Id to a Node Reference
     * 
     * @param importId  import Id
     * @param nodeRef  node reference
     */
    private void createImportReference(ParserContext parserContext, String importId, NodeRef nodeRef) {
        if (parserContext.importIds.containsKey(importId)) {
            throw new ImporterException("Import id " + importId + " already specified within import file");
        }
        parserContext.importIds.put(importId, nodeRef);
    }

    /**
     * Gets the Node Reference for the specified Import Id
     * 
     * @param importId  the import id
     * @return  the node reference
     */
    private NodeRef getImportReference(ParserContext parserContext, String importId) {
        return parserContext.importIds.get(importId);
    }

    /**
     * Get parent Node Context
     * 
     * @param contextStack  context stack
     * @return  node context
     */
    private NodeContext peekNodeContext(Stack<ElementContext> contextStack) {
        ElementContext element = contextStack.peek();
        if (element instanceof NodeContext) {
            return (NodeContext) element;
        } else if (element instanceof NodeItemContext) {
            return ((NodeItemContext) element).getNodeContext();
        }
        throw new ImporterException("Internal error: Failed to retrieve node context");
    }

    /**
     * Helper to create Qualified name from current xml element
     * 
     * @param xpp
     * @return
     */
    private QName getName(XmlPullParser xpp) {
        // Ensure namespace is valid
        String uri = xpp.getNamespace();
        if (namespaceService.getURIs().contains(uri) == false) {
            throw new ImporterException(
                    "Namespace URI " + uri + " has not been defined in the Repository dictionary");
        }

        // Construct name
        String name = xpp.getName();
        return QName.createQName(uri, name);
    }

    /**
     * Helper to indent debug output
     * 
     * @param msg
     * @param depth
     * @return
     */
    private String indentLog(String msg, int depth) {
        StringBuffer buf = new StringBuffer(1024);
        for (int i = 0; i < depth; i++) {
            buf.append(' ');
        }
        buf.append(msg);
        return buf.toString();
    }

}