org.jahia.services.importexport.LegacyImportHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.jahia.services.importexport.LegacyImportHandler.java

Source

/**
 * ==========================================================================================
 * =                   JAHIA'S DUAL LICENSING - IMPORTANT INFORMATION                       =
 * ==========================================================================================
 *
 *                                 http://www.jahia.com
 *
 *     Copyright (C) 2002-2017 Jahia Solutions Group SA. All rights reserved.
 *
 *     THIS FILE IS AVAILABLE UNDER TWO DIFFERENT LICENSES:
 *     1/GPL OR 2/JSEL
 *
 *     1/ GPL
 *     ==================================================================================
 *
 *     IF YOU DECIDE TO CHOOSE THE GPL LICENSE, YOU MUST COMPLY WITH THE FOLLOWING TERMS:
 *
 *     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 3 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, see <http://www.gnu.org/licenses/>.
 *
 *
 *     2/ JSEL - Commercial and Supported Versions of the program
 *     ===================================================================================
 *
 *     IF YOU DECIDE TO CHOOSE THE JSEL LICENSE, YOU MUST COMPLY WITH THE FOLLOWING TERMS:
 *
 *     Alternatively, commercial and supported versions of the program - also known as
 *     Enterprise Distributions - must be used in accordance with the terms and conditions
 *     contained in a separate written agreement between you and Jahia Solutions Group SA.
 *
 *     If you are unsure which license is appropriate for your use,
 *     please contact the sales department at sales@jahia.com.
 */
package org.jahia.services.importexport;

import net.htmlparser.jericho.Source;
import net.htmlparser.jericho.TextExtractor;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.jackrabbit.spi.Name;
import org.apache.jackrabbit.util.ISO8601;
import org.jahia.api.Constants;
import org.jahia.exceptions.JahiaException;
import org.jahia.registries.ServicesRegistry;
import org.jahia.services.SpringContextSingleton;
import org.jahia.services.categories.Category;
import org.jahia.services.content.JCRContentUtils;
import org.jahia.services.content.JCRNodeWrapper;
import org.jahia.services.content.JCRSessionWrapper;
import org.jahia.services.content.nodetypes.ExtendedItemDefinition;
import org.jahia.services.content.nodetypes.ExtendedNodeDefinition;
import org.jahia.services.content.nodetypes.ExtendedNodeType;
import org.jahia.services.content.nodetypes.ExtendedPropertyDefinition;
import org.jahia.services.content.nodetypes.NodeTypeRegistry;
import org.jahia.services.content.nodetypes.SelectorType;
import org.jahia.services.content.nodetypes.ValueImpl;
import org.jahia.services.importexport.DefinitionsMapping.Action;
import org.jahia.services.importexport.DefinitionsMapping.AddMixin;
import org.jahia.services.importexport.DefinitionsMapping.AddNode;
import org.jahia.services.importexport.DefinitionsMapping.SetProperties;
import org.jahia.services.seo.VanityUrl;
import org.jahia.services.seo.jcr.VanityUrlManager;
import org.jahia.settings.SettingsBean;
import org.jahia.utils.Patterns;
import org.jahia.utils.i18n.ResourceBundleMarker;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.PathNotFoundException;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.Value;
import javax.jcr.nodetype.ConstraintViolationException;
import javax.jcr.nodetype.NoSuchNodeTypeException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.text.DateFormat;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author toto
 *         Date: Oct 28, 2009
 *         Time: 2:31:18 PM
 */
public class LegacyImportHandler extends DefaultHandler {
    private static Logger logger = LoggerFactory.getLogger(LegacyImportHandler.class);

    private final static int CTX_PAGE = 0;
    private final static int CTX_CTN = 1;
    private final static int CTX_LIST = 2;
    private final static int CTX_FIELD = 3;
    private final static int CTX_SKIP = 4;
    private final static int CTX_SHAREABLE = 5;
    private final static int CTX_DIRECTSUBNODES = 6;

    private final static int CTX_NAVLINK = 7;

    private NodeTypeRegistry registry;
    private DefinitionsMapping mapping;

    private Locale locale;

    private JCRNodeWrapper currentSiteNode;
    private Stack<PageContext> currentCtx = new Stack<PageContext>();

    private String originatingJahiaRelease = null;

    private int ctnId = 1;

    private Map<String, String> uuidMapping = new HashMap<String, String>();
    private Map<String, List<String>> references = new HashMap<String, List<String>>();

    private JCRSessionWrapper session;
    private final LegacyPidMappingTool legacyPidMappingTool;

    private final String externalLinkType;
    private final String externalLinkUrlPropertyName;
    private final boolean externalLinkInternationalized;

    private static final String HTTP_WWW_JAHIA_ORG = "http://www.jahia.org/";
    private static final String PAGE = "page";
    private static final String LINK = "link";
    private static final String EMPTY = "empty";

    public static final Set<String> READ_ROLES = new HashSet<String>(Arrays.asList("reader"));
    public static final Set<String> WRITE_ROLES = new HashSet<String>(Arrays.asList("editor", "contributor"));
    public static final Set<String> ADMIN_ROLES = new HashSet<String>(Arrays.asList("reviewer", "owner"));

    public static Set<String> CUSTOM_CONTENT_READ_ROLES;
    public static Set<String> CUSTOM_CONTENT_WRITE_ROLES;
    public static Set<String> CUSTOM_CONTENT_ADMIN_ROLES;

    public static Set<String> CUSTOM_FILES_READ_ROLES;
    public static Set<String> CUSTOM_FILES_WRITE_ROLES;

    private String currentNode;
    private int level = 0;

    private VanityUrlManager urlMgr;

    public LegacyImportHandler(JCRSessionWrapper session, JCRNodeWrapper currentSiteNode, NodeTypeRegistry registry,
            DefinitionsMapping mapping, Locale locale, String originatingJahiaRelease,
            LegacyPidMappingTool legacyPidMappingTool, int initialCtnId) {
        this.session = session;
        this.uuidMapping = session.getUuidMapping();
        this.currentSiteNode = currentSiteNode;
        ctnId = initialCtnId < 1 ? 1 : initialCtnId;

        this.registry = registry;

        if (mapping == null) {
            mapping = new DefinitionsMapping();
        }

        this.mapping = mapping;
        this.locale = locale;
        this.originatingJahiaRelease = originatingJahiaRelease;
        this.legacyPidMappingTool = legacyPidMappingTool;

        String s = SettingsBean.getInstance().lookupString("legacy.import.externalLink.nodeType");
        externalLinkType = StringUtils.isNotBlank(s) ? s.trim() : Constants.JAHIANT_EXTERNAL_PAGE_LINK;
        s = SettingsBean.getInstance().lookupString("legacy.import.externalLink.urlPropertyName");
        externalLinkUrlPropertyName = StringUtils.isNotBlank(s) ? s.trim() : Constants.URL;
        externalLinkInternationalized = SettingsBean.getInstance()
                .lookupBoolean("legacy.import.externalLink.internationalized");
        urlMgr = (VanityUrlManager) SpringContextSingleton.getBean("org.jahia.services.seo.jcr.VanityUrlManager");
    }

    public void setReferences(Map<String, List<String>> references) {
        this.references = references;
    }

    /**
     * Receive notification of the start of an element.
     * <p/>
     * <p>
     * By default, do nothing. Application writers may override this method in a subclass to take specific actions at the start of each
     * element (such as allocating a new tree node or writing output to a file).
     * </p>
     *
     * @param uri        The Namespace URI, or the empty string if the element has no Namespace URI or if Namespace processing is not being
     *                   performed.
     * @param localName  The local name (without prefix), or the empty string if Namespace processing is not being performed.
     * @param qName      The qualified name (with prefix), or the empty string if qualified names are not available.
     * @param attributes The attributes attached to the element. If there are no attributes, it shall be an empty Attributes object.
     * @throws org.xml.sax.SAXException Any SAX exception, possibly wrapping another exception.
     * @see org.xml.sax.ContentHandler#startElement
     */
    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes)
            throws SAXException {
        int ctx = -1;
        String uuid = null;
        try {
            currentNode = localName;
            uuid = attributes.getValue("jcr:uuid");

            if (!currentCtx.isEmpty()) {
                ctx = currentCtx.peek().ctx.peek();
            }

            if (logger.isDebugEnabled()) {
                logger.debug(StringUtils.repeat(" ", level) + "<" + currentNode + "> , ctx = " + ctx);
            }
            level++;
            final Map<String, String> creationMetadata = getMetadataForNodeCreation(attributes);
            if (ctx == -1) {
                if (HTTP_WWW_JAHIA_ORG.equals(uri) && PAGE.equals(localName)) {
                    createPage(attributes.getValue(Name.NS_JCR_URI, "primaryType"),
                            attributes.getValue("jahia:title"), attributes.getValue("jahia:template"),
                            attributes.getValue(HTTP_WWW_JAHIA_ORG, "pageKey"), uuid, creationMetadata,
                            attributes.getValue("jahia:pid"), attributes.getValue("jcr:mixinTypes"),
                            getAdditionalProperties(attributes.getValue("jcr:additionalProperties")));
                    setAcl(attributes.getValue(HTTP_WWW_JAHIA_ORG, "acl"));
                    setMetadata(attributes);
                    setSeoURLs(attributes.getValue(HTTP_WWW_JAHIA_ORG, "urlMappings"));
                    return;
                } else if (HTTP_WWW_JAHIA_ORG.equals(uri) && EMPTY.equals(localName)) {
                    logger.warn("The site " + currentSiteNode.getDisplayableName()
                            + " was not published in language " + locale + ", nothing to import");
                    return;
                }
                logger.error("Unexpected root markup: <" + uri + ":" + localName + " /> for site"
                        + currentSiteNode.getDisplayableName());
                return;
            }

            switch (ctx) {
            case CTX_PAGE:
                if (localName.endsWith("List") && getCurrentContentType() != null
                        && getCurrentContentType().getChildNodeDefinitionsAsMap()
                                .containsKey(StringUtils.substringBeforeLast(localName, "List"))) {
                    // Must be a container list
                    ExtendedNodeDefinition nodeDef = getCurrentContentType().getChildNodeDefinitionsAsMap()
                            .get(StringUtils.substringBeforeLast(localName, "List"));

                    createContentList(nodeDef, uuid, creationMetadata, attributes);
                    setMetadata(attributes);
                    setAcl(attributes.getValue(HTTP_WWW_JAHIA_ORG, "acl"));
                    setSeoURLs(attributes.getValue(HTTP_WWW_JAHIA_ORG, "urlMappings"));
                } else {
                    logger.error("Unexpected " + localName + " element (" + uuid
                            + ") in import file - skipping it and its subtree (more info in debug mode)");
                    if (logger.isDebugEnabled() && localName.endsWith("List")) {
                        if (getCurrentContentType() == null) {
                            logger.debug("CurrentContentType is null!");
                        } else {
                            logger.debug(
                                    "Only the following elements are allowed sccording to the source definitions: "
                                            + getCurrentContentType().getChildNodeDefinitionsAsMap().keySet()
                                                    .toString());
                        }
                    }
                    currentCtx.peek().pushSkip();
                }
                break;
            case CTX_CTN:
                if (localName.endsWith("List") && getCurrentContentType() != null
                        && getCurrentContentType().getChildNodeDefinitionsAsMap()
                                .containsKey(StringUtils.substringBeforeLast(localName, "List"))) {
                    // Must be a container list
                    ExtendedNodeDefinition nodeDef = getCurrentContentType().getChildNodeDefinitionsAsMap()
                            .get(StringUtils.substringBeforeLast(localName, "List"));

                    try {
                        createContentList(nodeDef, uuid, creationMetadata, attributes);
                        if (currentCtx.peek().ctx.peek() != CTX_DIRECTSUBNODES) {
                            setMetadata(attributes);
                            setAcl(attributes.getValue(HTTP_WWW_JAHIA_ORG, "acl"));
                            setSeoURLs(attributes.getValue(HTTP_WWW_JAHIA_ORG, "urlMappings"));
                        }
                    } catch (ConstraintViolationException cve) {
                        logger.error(
                                "Error when creating contentList with def={} (localname={} , uuid={} , currentContentType={})",
                                new Object[] { nodeDef.getName(), localName, uuid,
                                        getCurrentContentType().getName() });
                    }
                } else {
                    logger.debug("create field " + localName);
                    ExtendedItemDefinition itemDef;
                    if (getCurrentContentType().getChildNodeDefinitionsAsMap().containsKey(localName)
                            && getCurrentContentType().getPropertyDefinitionsAsMap().containsKey(localName)) {
                        itemDef = getCurrentContentType().getChildNodeDefinitionsAsMap().get(localName);
                        if (!itemDef.getDeclaringNodeType().getName()
                                .equals(getCurrentContentType().getNodeTypeDefinition().getName())) {
                            ExtendedItemDefinition itemDef2 = getCurrentContentType().getPropertyDefinitionsAsMap()
                                    .get(localName);
                            if (itemDef2.getDeclaringNodeType().getName()
                                    .equals(getCurrentContentType().getNodeTypeDefinition().getName())) {
                                itemDef = itemDef2;
                            }
                        }
                    } else if (getCurrentContentType().getChildNodeDefinitionsAsMap().containsKey(localName)) {
                        itemDef = getCurrentContentType().getChildNodeDefinitionsAsMap().get(localName);
                    } else {
                        itemDef = getCurrentContentType().getPropertyDefinitionsAsMap().get(localName);
                    }
                    if (itemDef == null) {
                        logger.error("Definition not found for field " + localName + " in node "
                                + getCurrentContentType().getName() + " , uuid=" + uuid);
                    } else if (logger.isDebugEnabled()) {
                        if (itemDef.isNode()) {
                            logger.debug(
                                    "The field {} is a subnode of the node type {} (child node definition = {})",
                                    new Object[] { localName, getCurrentContentType().getName(),
                                            itemDef.getName() });
                        } else {
                            logger.debug("The field is a property: " + localName);
                        }
                    }
                    if (itemDef != null && (itemDef.isNode() || setPropertyField(getCurrentContentType(), localName,
                            attributes.getValue("jahia:value"), creationMetadata))) {
                        String mappedProperty = mapping.getMappedProperty(getCurrentContentType(), localName);
                        if ("#skip".equals(mappedProperty)) {
                            currentCtx.peek().pushSkip();
                        } else {
                            currentCtx.peek().pushField(mappedProperty);
                        }
                    } else {
                        if (logger.isDebugEnabled()) {
                            logger.debug("Not imported field " + localName + ", definition not found");
                            StringBuilder s = new StringBuilder();
                            for (String def : getCurrentContentType().getChildNodeDefinitionsAsMap().keySet())
                                s.append(def).append(", ");
                            logger.debug("Allowed sub definitions: " + (s.length() > 0 ? s : "none"));
                            for (String def : getCurrentContentType().getPropertyDefinitionsAsMap().keySet())
                                s.append(def).append(", ");
                            logger.debug("Allowed properties: " + (s.length() > 0 ? s : "none"));
                        }
                        currentCtx.peek().pushSkip();
                    }
                }
                break;
            case CTX_LIST:
                /**
                 * ExtendedNodeDefinition ctnDefinition =
                 * listType.getDeclaredUnstructuredChildNodeDefinitions().values().iterator().next(); ExtendedNodeType ctnType =
                 * ctnDefinition.getRequiredPrimaryTypes()[0];
                 */
                String pt = attributes.getValue(Name.NS_JCR_URI, "primaryType");
                if (pt == null && StringUtils.startsWith(originatingJahiaRelease, "5")) {
                    pt = qName;
                }

                createContent(pt, uuid, attributes.getValue("jahia:jahiaLinkActivation_picker_relationship"),
                        creationMetadata, attributes.getValue("jcr:mixinTypes"),
                        getAdditionalProperties(attributes.getValue("jcr:additionalProperties")));
                setMetadata(attributes);
                setAcl(attributes.getValue(HTTP_WWW_JAHIA_ORG, "acl"));
                setSeoURLs(attributes.getValue(HTTP_WWW_JAHIA_ORG, "urlMappings"));
                break;
            case CTX_FIELD:
                setNodeField(uri, localName, uuid, attributes);
                break;

            case CTX_SHAREABLE:
                if ("#shareableSource".equals(mapping.getMappedNode(getCurrentContentType(), localName))) {
                    createShareableNode(attributes.getValue("jahia:value"));
                } else {
                    currentCtx.peek().pushSkip();
                }
                break;
            case CTX_SKIP:
                currentCtx.peek().pushSkip();
                break;
            case CTX_NAVLINK:
                currentCtx.peek().pushNavLink(getCurrentContentType(),
                        attributes.getValue(HTTP_WWW_JAHIA_ORG, "acl"));
                currentCtx.peek().properties.peek().putAll(convertToProperties(attributes));
                final JCRNodeWrapper page = currentCtx.peek().contents.peek();

                String title = attributes.getValue("jahia:title");
                if (HTTP_WWW_JAHIA_ORG.equals(uri) && PAGE.equals(localName)) {
                    String acl = null;
                    if (logger.isDebugEnabled()) {
                        logger.debug("page acls: " + currentCtx.peek().acls.toString());
                    }
                    for (String a : currentCtx.peek().acls) {
                        if (a != null) {
                            acl = a;
                        }
                    }
                    if (attributes.getValue(HTTP_WWW_JAHIA_ORG, "acl") != null) {
                        acl = attributes.getValue(HTTP_WWW_JAHIA_ORG, "acl");
                    }
                    Map<String, String> props = currentCtx.peek().properties.peek();
                    createPage(attributes.getValue(Name.NS_JCR_URI, "primaryType"), title,
                            attributes.getValue("jahia:template"),
                            attributes.getValue(HTTP_WWW_JAHIA_ORG, "pageKey"), uuid, creationMetadata,
                            attributes.getValue("jahia:pid"), attributes.getValue("jcr:mixinTypes"),
                            getAdditionalProperties(attributes.getValue("jcr:additionalProperties")));
                    setMetadata(props, creationMetadata);
                    setAcl(acl);
                    setSeoURLs(attributes.getValue(HTTP_WWW_JAHIA_ORG, "urlMappings"));
                    // todo : add a link here ??
                } else if (HTTP_WWW_JAHIA_ORG.equals(uri) && LINK.equals(localName)) {
                    createInternalLink(page, title, uuid, attributes.getValue("jahia:reference"), "jnt:nodeLink",
                            creationMetadata, attributes.getValue("jcr:mixinTypes"),
                            getAdditionalProperties(attributes.getValue("jcr:additionalProperties")));
                } else if (HTTP_WWW_JAHIA_ORG.equals(uri) && localName.equals("url")) {
                    createExternalLink(page, title, uuid, attributes.getValue("jahia:value"), externalLinkType,
                            externalLinkUrlPropertyName, externalLinkInternationalized, creationMetadata,
                            attributes.getValue("jcr:mixinTypes"),
                            getAdditionalProperties(attributes.getValue("jcr:additionalProperties")));
                }

                break;
            case CTX_DIRECTSUBNODES:
                String ctnPt = attributes.getValue(Name.NS_JCR_URI, "primaryType");
                if (ctnPt == null && StringUtils.startsWith(originatingJahiaRelease, "5")) {
                    ctnPt = qName;
                }

                createContent(ctnPt, uuid, attributes.getValue("jahia:jahiaLinkActivation_picker_relationship"),
                        creationMetadata, attributes.getValue("jcr:mixinTypes"),
                        getAdditionalProperties(attributes.getValue("jcr:additionalProperties")));
                setMetadata(attributes);
                setAcl(attributes.getValue(HTTP_WWW_JAHIA_ORG, "acl"));
                setSeoURLs(attributes.getValue(HTTP_WWW_JAHIA_ORG, "urlMappings"));

                break;
            }
        } catch (RepositoryException e) {
            logger.error(MessageFormat.format(
                    "Error while processing element: [uri={0}], [localName={1}], [qName={2}], [uuid={3}], [ctx={4}]",
                    uri, localName, qName, uuid, ctx));
            throw new SAXException(e);
        }

    }

    private JSONObject getAdditionalProperties(String value) {
        if (StringUtils.isBlank(value))
            return null;

        try {
            return new JSONObject(value);
        } catch (Exception e) {
            if (logger.isDebugEnabled()) {
                logger.error("Impossible to read additional properties", e);
            } else {
                logger.error("Impossible to read additional properties: {}", e.getMessage());
            }
        }
        return null;
    }

    private Map<String, String> convertToProperties(Attributes attributes) {
        Map<String, String> properties = new HashMap<String, String>();
        int l = attributes.getLength();
        for (int i = 0; i < l; i++) {
            properties.put(attributes.getQName(i), attributes.getValue(i));
        }
        return properties;
    }

    private Map<String, String> getMetadataForNodeCreation(Attributes attributes) {
        Map<String, String> metadataMap = new HashMap<String, String>();
        metadataMap.put("jahia:createdBy", attributes.getValue("jahia:createdBy"));
        String date = attributes.getValue("jcr:created");
        metadataMap.put("jcr:created", (date != null && date.length() == 19) ? date + ".000Z" : date);
        metadataMap.put("jahia:lastModifiedBy", attributes.getValue("jahia:lastModifiedBy"));
        date = attributes.getValue("jcr:lastModified");
        metadataMap.put("jcr:lastModified", (date != null && date.length() == 19) ? date + ".000Z" : date);
        return metadataMap;
    }

    /**
     * Receive notification of the end of an element.
     * <p/>
     * <p>
     * By default, do nothing. Application writers may override this method in a subclass to take specific actions at the end of each
     * element (such as finalising a tree node or writing output to a file).
     * </p>
     *
     * @param uri       The Namespace URI, or the empty string if the element has no Namespace URI or if Namespace processing is not being
     *                  performed.
     * @param localName The local name (without prefix), or the empty string if Namespace processing is not being performed.
     * @param qName     The qualified name (with prefix), or the empty string if qualified names are not available.
     * @throws org.xml.sax.SAXException Any SAX exception, possibly wrapping another exception.
     * @see org.xml.sax.ContentHandler#endElement
     */
    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        if (currentCtx.peek().ctx.peek() == CTX_PAGE) {
            level--;
            currentCtx.pop();
            if (!currentCtx.isEmpty()) {
                currentCtx.peek().pop();
                if (logger.isDebugEnabled()) {
                    logger.debug(StringUtils.repeat(" ", level) + "</" + localName + "> , popped full ctx , ctx = "
                            + (currentCtx.peek().ctx.empty() ? "empty" : currentCtx.peek().ctx.peek()));
                }
            }
        } else {
            level--;
            if (logger.isDebugEnabled()) {
                logger.debug(StringUtils.repeat(" ", level) + "</" + localName + "> , ctx = "
                        + currentCtx.peek().ctx.peek());
            }
            currentCtx.peek().pop();
        }
    }

    private void createPage(String primaryType, String title, String template, String pageKey, String uuid,
            Map<String, String> creationMetadata, String pageId, String mixinsToAdd, JSONObject propertiesToSet)
            throws RepositoryException {
        JCRNodeWrapper subPage;
        ExtendedNodeType t;
        try {
            t = registry.getNodeType(primaryType);
        } catch (NoSuchNodeTypeException e) {
            t = registry.getNodeType("jnt:page");
        }
        if (uuidMapping.containsKey(uuid)) {
            subPage = session.getNodeByIdentifier(uuidMapping.get(uuid));
        } else {
            JCRNodeWrapper parent = (currentCtx.isEmpty() ? currentSiteNode : getCurrentPageNode());

            if (!parent.isCheckedOut()) {
                session.checkout(parent);
            }

            if (currentCtx.isEmpty()) {
                pageKey = "home";
            }

            if (pageKey == null) {
                try {
                    pageKey = JCRContentUtils.generateNodeName(title);
                } catch (NullPointerException npe) {
                    pageKey = "untitled";
                    logger.error(MessageFormat.format(
                            "Impossible to generate a system name from page title =[{0}] , uuid={1}", title, uuid),
                            npe);
                }
            }

            // remove all unsupported characters
            pageKey = pageKey.replace('/', '_');
            pageKey = JCRContentUtils.replaceColon(pageKey);
            pageKey = pageKey.replace('[', '_');
            pageKey = pageKey.replace(']', '_');

            ExtendedNodeType pageType = registry.getNodeType("jnt:page");
            String templateName = "";
            if (!StringUtils.isEmpty(template)) {
                templateName = mapping.getMappedPropertyValue(pageType, "jahia:template", template);
            }
            templateName = StringUtils.substringAfterLast("/" + templateName, "/");
            subPage = addOrCheckoutPageNode(templateName, parent, pageKey, creationMetadata);
            uuidMapping.put(uuid, subPage.getIdentifier());

            performActions(mapping.getActions(pageType, "jahia:template", template), subPage, creationMetadata);
            performActions(mapping.getActions(t), subPage, creationMetadata);
        }

        currentCtx.push(new PageContext(subPage, t));

        if (StringUtils.isNotBlank(title)) {
            final Node translation = getOrCreateI18N(subPage, locale, creationMetadata);
            if (!translation.isCheckedOut()) {
                session.checkout(translation);
            }
            translation.setProperty("jcr:title", title);
        }

        handleAdditionalMixinsAndProperties(subPage, mixinsToAdd, propertiesToSet, creationMetadata);

        if (legacyPidMappingTool != null) {
            legacyPidMappingTool.defineLegacyMapping(Integer.valueOf(pageId), subPage, locale);
        }
    }

    private void createExternalLink(JCRNodeWrapper page, String title, String uuid, final String url,
            final String nodeType, Map<String, String> creationMetadata) throws RepositoryException {
        createExternalLink(page, title, uuid, url, nodeType, Constants.URL, false, creationMetadata, null, null);
    }

    private void createExternalLink(JCRNodeWrapper page, String title, String uuid, final String url,
            final String nodeType, final String urlPropertyName, boolean urlIsLocalized,
            Map<String, String> creationMetadata, String mixinsToAdd, JSONObject propertiesToSet)
            throws RepositoryException {
        JCRNodeWrapper sub;
        if (uuidMapping.containsKey(uuid)) {
            sub = session.getNodeByIdentifier(uuidMapping.get(uuid));
        } else {
            sub = addOrCheckoutNode(page, "link_" + (ctnId++), nodeType, null, creationMetadata);
            uuidMapping.put(uuid, sub.getIdentifier());
        }

        Node translation = null;
        if (StringUtils.isNotBlank(url)) {
            if (urlIsLocalized) {
                translation = getOrCreateI18N(sub, locale, creationMetadata);
                translation.setProperty(urlPropertyName, url);
            } else
                sub.setProperty(urlPropertyName, url);
        }

        if (StringUtils.isNotBlank(title)) {
            if (translation == null)
                translation = getOrCreateI18N(sub, locale, creationMetadata);
            translation.setProperty("jcr:title", title);
        }
        handleAdditionalMixinsAndProperties(sub, mixinsToAdd, propertiesToSet, creationMetadata);
    }

    private void createInternalLink(JCRNodeWrapper page, String title, String uuid, final String reference,
            final String nodeType, Map<String, String> creationMetadata, String mixinsToAdd,
            JSONObject propertiesToSet) throws RepositoryException {
        JCRNodeWrapper sub;
        if (uuidMapping.containsKey(uuid)) {
            sub = session.getNodeByIdentifier(uuidMapping.get(uuid));
        } else {
            sub = addOrCheckoutNode(page, "link_" + (ctnId++), nodeType, null, creationMetadata);
            if (!references.containsKey(reference)) {
                references.put(reference, new ArrayList<String>());
            }
            references.get(reference).add(sub.getIdentifier() + "/" + Constants.NODE);
            uuidMapping.put(uuid, sub.getIdentifier());
        }

        if (StringUtils.isNotBlank(title)) {
            Node translation = getOrCreateI18N(sub, locale, creationMetadata);
            if (!translation.isCheckedOut()) {
                session.checkout(translation);
            }
            translation.setProperty(Constants.JCR_TITLE, title);
        }
        if (!sub.isCheckedOut()) {
            session.checkout(sub);
        }
        sub.setProperty(Constants.JCR_TITLE, title);
        handleAdditionalMixinsAndProperties(sub, mixinsToAdd, propertiesToSet, creationMetadata);
    }

    private JCRNodeWrapper addOrCheckoutPageNode(String template, JCRNodeWrapper parent, String nodeName,
            Map<String, String> creationMetadata) throws RepositoryException {
        JCRNodeWrapper node;
        try {
            node = parent.getNode(nodeName);
            if (!node.isCheckedOut()) {
                session.checkout(node);
            }
        } catch (PathNotFoundException e) {
            if (!parent.isCheckedOut()) {
                session.checkout(parent);
            }
            Calendar created = !StringUtils.isEmpty(creationMetadata.get("jcr:created"))
                    ? ISO8601.parse(creationMetadata.get("jcr:created"))
                    : null;
            String createdBy = creationMetadata.get("jahia:createdBy");
            Calendar lastModified = !StringUtils.isEmpty(creationMetadata.get("jcr:lastModified"))
                    ? ISO8601.parse(creationMetadata.get("jcr:lastModified"))
                    : null;
            String lastModifiedBy = creationMetadata.get("jahia:lastModifiedBy");

            node = parent.addNode(nodeName, Constants.JAHIANT_PAGE, null, created, createdBy, lastModified,
                    lastModifiedBy);
        }
        if (template != null) {
            node.setProperty("j:templateName", template);
        }
        return node;
    }

    private Node getOrCreateI18N(JCRNodeWrapper node, Locale locale, Map<String, String> creationMetadata)
            throws RepositoryException {
        final Calendar created = !StringUtils.isEmpty(creationMetadata.get("jcr:created"))
                ? ISO8601.parse(creationMetadata.get("jcr:created"))
                : null;
        final String createdBy = creationMetadata.get("jahia:createdBy");
        final Calendar lastModified = !StringUtils.isEmpty(creationMetadata.get("jcr:lastModified"))
                ? ISO8601.parse(creationMetadata.get("jcr:lastModified"))
                : null;
        final String lastModifiedBy = creationMetadata.get("jahia:lastModifiedBy");

        return node.getOrCreateI18N(locale, created, createdBy, lastModified, lastModifiedBy);
    }

    private JCRNodeWrapper checkoutNode(JCRNodeWrapper node) throws RepositoryException {
        if (!node.isCheckedOut()) {
            session.checkout(node);
        }
        return node;
    }

    private JCRNodeWrapper addOrCheckoutNode(JCRNodeWrapper parent, String nodeName, String nodeType,
            List<String> followingNodeNames, Map<String, String> creationMetadata) throws RepositoryException {
        JCRNodeWrapper node;
        try {
            node = parent.getNode(nodeName);
            if (!node.isCheckedOut()) {
                session.checkout(node);
            }
        } catch (PathNotFoundException e) {
            if (!parent.isCheckedOut()) {
                session.checkout(parent);
            }
            if (StringUtils.isEmpty(nodeType)) {
                nodeType = Constants.JAHIANT_CONTENTLIST;
            }
            try {
                Calendar created = !StringUtils.isEmpty(creationMetadata.get("jcr:created"))
                        ? ISO8601.parse(creationMetadata.get("jcr:created"))
                        : null;
                String createdBy = creationMetadata.get("jahia:createdBy");
                Calendar lastModified = !StringUtils.isEmpty(creationMetadata.get("jcr:lastModified"))
                        ? ISO8601.parse(creationMetadata.get("jcr:lastModified"))
                        : null;
                String lastModifiedBy = creationMetadata.get("jahia:lastModifiedBy");
                node = parent.addNode(nodeName, nodeType, null, created, createdBy, lastModified, lastModifiedBy);
            } catch (ConstraintViolationException cve) {
                throw new ConstraintViolationException(
                        MessageFormat.format("Error while adding node {0} of type {1} to node {2}", nodeName,
                                nodeType, parent.getPath()),
                        cve);
            }
            if (!CollectionUtils.isEmpty(followingNodeNames)) {
                boolean takeNextName = false;
                for (NodeIterator it = parent.getNodes(); it.hasNext();) {
                    JCRNodeWrapper nextNode = (JCRNodeWrapper) it.next();
                    int index = followingNodeNames.indexOf(nextNode.getName());
                    if (index > -1) {
                        if (followingNodeNames.get(index).equals(nodeName)) {
                            takeNextName = true;
                        } else {
                            parent.orderBefore(node.getName(), nextNode.getName());
                            break;
                        }
                    } else if (takeNextName) {
                        parent.orderBefore(node.getName(), nextNode.getName());
                        break;
                    }
                }
            }
        }
        return node;
    }

    private void createContentList(ExtendedNodeDefinition listDefinition, String uuid,
            Map<String, String> creationMetadata, Attributes attributes) throws RepositoryException {
        String nodeName = mapping.getMappedNode(getCurrentContentType(), listDefinition.getName());

        String nodeType = Constants.JAHIANT_CONTENTLIST;
        if (nodeName.indexOf("|") > 0) {
            nodeType = StringUtils.substringBefore(nodeName, "|");
            nodeName = StringUtils.substringAfter(nodeName, "|");
        }
        ExtendedNodeType primaryNodeType = listDefinition.getRequiredPrimaryTypes()[0];
        try {
            primaryNodeType = registry.getNodeType(
                    StringUtils.substringBeforeLast(listDefinition.getRequiredPrimaryTypes()[0].getName(), "List"));
        } catch (NoSuchNodeTypeException ex) {
        }
        String mappedNodeType = mapping.getMappedType(primaryNodeType);
        if ("#skip".equals(nodeName) || "#skip".equals(mappedNodeType)) {
            currentCtx.peek().pushSkip();
        } else if ("#navlink".equals(nodeName) || "#navlink".equals(mappedNodeType)) {
            currentCtx.peek().pushNavLink(listDefinition.getRequiredPrimaryTypes()[0], null);
        } else if ("#directSubNodes".equals(nodeName) || "#directSubNodes".equals(mappedNodeType)) {
            currentCtx.peek().pushDirectContainer();
        } else {
            JCRNodeWrapper parent = getCurrentContentNode();
            if (StringUtils.contains(nodeName, "/")) {
                String parentPath = StringUtils.substringBeforeLast(nodeName, "/");
                if (parent.hasNode(parentPath)) {
                    parent = parent.getNode(parentPath);
                } else {
                    parent = parent.addNode(parentPath, Constants.JAHIANT_CONTENTLIST);
                }
                nodeName = StringUtils.substringAfterLast(nodeName, "/");
            }
            if (StringUtils.isEmpty(nodeType)
                    && parent.getPrimaryNodeType().getChildNodeDefinitionsAsMap().get(nodeName) != null) {
                String[] strings = parent.getPrimaryNodeType().getChildNodeDefinitionsAsMap().get(nodeName)
                        .getRequiredPrimaryTypeNames();
                nodeType = strings[0];
            }
            List<String> mappedOldNodeNames = mapping.getMappedNodesForType(getCurrentContentType(), true);
            int indexOfName = mappedOldNodeNames.indexOf(listDefinition.getName());
            List<String> mappedNewNodeNames = null;
            if (indexOfName != -1) {
                mappedNewNodeNames = mapping.getMappedNodesForType(getCurrentContentType(), false)
                        .subList(indexOfName, mappedOldNodeNames.size());
            }
            JCRNodeWrapper node = addOrCheckoutNode(parent, nodeName, nodeType, mappedNewNodeNames,
                    creationMetadata);

            performActions(mapping.getActions(getCurrentContentType(), listDefinition.getName()), node,
                    creationMetadata);
            uuidMapping.put(uuid, node.getIdentifier());

            ExtendedNodeType listType = listDefinition.getRequiredPrimaryTypes()[0];
            currentCtx.peek().pushList(node, listType);

            if (currentCtx.peek().properties.peek() != null) {
                for (Map.Entry<String, String> entry : currentCtx.peek().properties.peek().entrySet()) {
                    setPropertyField(getCurrentContentType(), entry.getKey(), entry.getValue(), creationMetadata);
                }
            }
        }

        handleContainerListSorting(attributes.getValue("jahia:sortHandler"), primaryNodeType);
    }

    private void handleContainerListSorting(String sortHandler, ExtendedNodeType primaryNodeType) {
        if (sortHandler == null || sortHandler.length() == 0) {
            return;
        }

        JCRNodeWrapper node = currentCtx.peek().contents.peek();
        String path = node.getPath();
        try {
            String[] sortConfig = StringUtils.split(sortHandler, "; ");
            String field = sortConfig[sortConfig.length - 1];
            String targetProperty = mapping.getMappedProperty(primaryNodeType, field);
            String direction = sortConfig[1];
            if (targetProperty != null && (node.isNodeType("jnt:contentList") || node.isNodeType("jnt:area"))) {
                logger.debug("Setting automatic ordering for list {} using property {} and direction {}",
                        new String[] { path, targetProperty, direction });
                node.addMixin("jmix:orderedList");
                node.setProperty("firstField", targetProperty);
                node.setProperty("firstDirection", direction != null && "desc".equals(direction) ? "desc" : "asc");
            } else {
                logger.error("Unable to handle container list sorting property " + sortHandler
                        + " as the target node is not a list: " + path);
            }
        } catch (Exception e) {
            logger.error("Unable to handle container list sorting property " + sortHandler + " on node " + path, e);
        }
    }

    private void createShareableNode(String uuid) throws RepositoryException {
        if (!references.containsKey(uuid)) {
            references.put(uuid, new ArrayList<String>());
        }
        references.get(uuid).add(getCurrentContentNode().getIdentifier() + "/@ctn" + (ctnId++));
        currentCtx.peek().pushSkip();
    }

    private void createContent(String primaryType, String uuid, String pickerRelationshipUuid,
            Map<String, String> creationMetadata, String mixinsToAdd, JSONObject propertiesToSet)
            throws RepositoryException {
        ExtendedNodeType t = registry.getNodeType(primaryType);
        String nodeType = mapping.getMappedType(t);

        if (nodeType.equals("#skip")) {
            currentCtx.peek().pushSkip();
        } else if (nodeType.equals("#navlink")) {
            currentCtx.peek().pushNavLink(t, null);
        } else if (nodeType.equals("#shareable")) {
            currentCtx.peek().pushShareable(t);
        } else {
            JCRNodeWrapper node = null;
            if (uuidMapping.containsKey(uuid)) {
                node = session.getNodeByIdentifier(uuidMapping.get(uuid));
                if (pickerRelationshipUuid != null)
                    currentCtx.peek().pushSkip();
                else
                    currentCtx.peek().pushContainer(node, t);
            } else if (pickerRelationshipUuid != null) {
                node = addOrCheckoutNode(getCurrentContentNode(),
                        StringUtils.substringAfter(nodeType, ":") + "_" + (ctnId++), "jnt:contentReference", null,
                        creationMetadata);
                final String contentRefUuid = node.getIdentifier();
                uuidMapping.put(uuid, contentRefUuid);
                performActions(mapping.getActions(t), node, creationMetadata);

                if (uuidMapping.containsKey(pickerRelationshipUuid)) {
                    final JCRNodeWrapper source = session
                            .getNodeByIdentifier(uuidMapping.get(pickerRelationshipUuid));
                    node.setProperty(Constants.NODE, source);
                } else {
                    if (!references.containsKey(pickerRelationshipUuid)) {
                        references.put(pickerRelationshipUuid, new ArrayList<String>());
                    }
                    references.get(pickerRelationshipUuid).add(contentRefUuid + "/" + Constants.NODE);
                }

                currentCtx.peek().pushSkip();
            } else {
                try {
                    ExtendedNodeType nt = NodeTypeRegistry.getInstance().getNodeType(nodeType);
                    if (StringUtils.startsWith(originatingJahiaRelease, "5") && nt.isNodeType("jnt:nodeLink")) {
                        currentCtx.peek().pushNavLink(getCurrentContentType(), null);
                        return;
                    }
                } catch (NoSuchNodeTypeException e) {
                    logger.error("Unexpected nodetype " + nodeType + " - skipping it and its subtree");
                    currentCtx.peek().pushSkip();
                    return;
                }
                node = addOrCheckoutNode(getCurrentContentNode(),
                        StringUtils.substringAfter(nodeType, ":") + "_" + (ctnId++), nodeType, null,
                        creationMetadata);
                uuidMapping.put(uuid, node.getIdentifier());
                performActions(mapping.getActions(t), node, creationMetadata);
                currentCtx.peek().pushContainer(node, t);

            }

            handleAdditionalMixinsAndProperties(node, mixinsToAdd, propertiesToSet, creationMetadata);

            if (currentCtx.peek().properties.peek() != null) {
                for (Map.Entry<String, String> entry : currentCtx.peek().properties.peek().entrySet()) {
                    final ExtendedNodeType currentContentType = getCurrentContentType();
                    final String fieldName = entry.getKey();
                    logger.debug("About to import field {}/{}", currentContentType.getName(), fieldName);
                    if (!setPropertyField(currentContentType, fieldName, entry.getValue(), creationMetadata)
                            && !"#skip".equals(fieldName)) {
                        logger.error("Not imported field {}/{}", currentContentType.getName(), fieldName);
                    }
                }
            }
        }
    }

    private void performActions(List<Action> actions, JCRNodeWrapper node, Map<String, String> creationMetadata)
            throws RepositoryException {
        for (Action action : actions) {
            if (action instanceof AddMixin) {
                AddMixin addMixinAction = (AddMixin) action;
                node = checkoutNode(node);
                node.addMixin(addMixinAction.getNodeType());
            } else if (action instanceof AddNode) {
                AddNode addNodeAction = (AddNode) action;
                JCRNodeWrapper addedNode = addOrCheckoutNode(node, addNodeAction.getName(),
                        addNodeAction.getNodeType(), null, Collections.<String, String>emptyMap());
                setProperties(addedNode, addNodeAction.getProperties(), creationMetadata);
            } else if (action instanceof SetProperties) {
                SetProperties setPropertiesAction = (SetProperties) action;
                setProperties(node, setPropertiesAction.getProperties(), creationMetadata);
            }
        }
    }

    private void setProperties(JCRNodeWrapper node, Map<String, String> properties,
            Map<String, String> creationMetadata) {
        if (logger.isDebugEnabled()) {
            final StringBuilder sb = new StringBuilder();
            for (String p : properties.keySet())
                sb.append(MessageFormat.format("[{0}={1}]", p, properties.get(p)));
            logger.debug("setProperties action called on {} with values : {}", node.getPath(), sb.toString());
        }
        if (properties == null) {
            return;
        }

        for (Map.Entry<String, String> property : properties.entrySet()) {
            String propertyName = property.getKey();
            try {
                setPropertyField(null, null, node, propertyName, property.getValue(), creationMetadata);
            } catch (RepositoryException e) {
                logger.error("Error setting property: " + propertyName + " on node: " + node.getPath(), e);
            }
        }
    }

    private void setMetadata(Attributes attributes) throws RepositoryException {
        int l = attributes.getLength();
        for (int i = 0; i < l; i++) {
            String name = attributes.getQName(i);
            setPropertyField(null, name, attributes.getValue(i), getMetadataForNodeCreation(attributes));
        }
    }

    private void setMetadata(Map<String, String> properties, Map<String, String> creationMetadata)
            throws RepositoryException {
        for (Map.Entry<String, String> property : properties.entrySet()) {
            setPropertyField(null, property.getKey(), property.getValue(), creationMetadata);
        }
    }

    private void setSeoURLs(String urlMapping) {
        if (StringUtils.isBlank(urlMapping))
            return;

        for (String mapping : StringUtils.split(urlMapping, "===")) {
            if (StringUtils.isBlank(mapping))
                continue;
            final String[] mappingItems = StringUtils.split(mapping, "$$$");
            final JCRNodeWrapper node = getCurrentContentNode();
            final String url = mappingItems[0];
            final Boolean isActive = Boolean.valueOf(mappingItems[1]);
            final Boolean isDefault = Boolean.valueOf(mappingItems[2]);
            try {
                final String site = node.getResolveSite().getSiteKey();
                if (urlMgr.findExistingVanityUrls(url, site, node.getSession()).isEmpty()) {
                    final VanityUrl vanityUrl = new VanityUrl(url, site, locale.toString(), isDefault, isActive);
                    urlMgr.saveVanityUrlMapping(node, vanityUrl, node.getSession());
                } else {
                    logger.error("Impossible to save URL mapping " + url + " on node " + node.getPath());
                }
            } catch (RepositoryException re) {
                logger.error("Impossible to save URL mapping " + url + " on node " + node.getPath());
            }
        }
    }

    private void setAcl(String acl) {
        if (acl != null) {
            JCRNodeWrapper node = getCurrentContentNode();
            StringTokenizer st = new StringTokenizer(acl, "|");
            try {
                while (st.hasMoreTokens()) {
                    String ace = st.nextToken();
                    if (ace.equals("break")) {
                        node.setAclInheritanceBreak(true);
                    } else if (ace.equals("none")) {
                        // ?
                    } else {
                        int colonIndex = ace.lastIndexOf(":");
                        String perm = ace.substring(colonIndex + 1);
                        if (perm.length() != 3) {
                            logger.error("Skipping inconsistent acl entry: " + ace);
                            continue;
                        }
                        Set<String> grantedRoles = new HashSet<String>();
                        Set<String> removedRoles = new HashSet<String>();
                        if (perm.charAt(0) == 'r') {
                            if (CollectionUtils.isEmpty(LegacyImportHandler.CUSTOM_CONTENT_READ_ROLES)) {
                                grantedRoles.addAll(LegacyImportHandler.READ_ROLES);
                            } else {
                                grantedRoles.addAll(LegacyImportHandler.CUSTOM_CONTENT_READ_ROLES);
                            }
                        } else {
                            if (CollectionUtils.isEmpty(LegacyImportHandler.CUSTOM_CONTENT_READ_ROLES)) {
                                removedRoles.addAll(LegacyImportHandler.READ_ROLES);
                            } else {
                                removedRoles.addAll(LegacyImportHandler.CUSTOM_CONTENT_READ_ROLES);
                            }
                        }
                        if (perm.charAt(1) == 'w') {
                            if (CollectionUtils.isEmpty(LegacyImportHandler.CUSTOM_CONTENT_WRITE_ROLES)) {
                                grantedRoles.addAll(LegacyImportHandler.WRITE_ROLES);
                            } else {
                                grantedRoles.addAll(LegacyImportHandler.CUSTOM_CONTENT_WRITE_ROLES);
                            }
                        } else {
                            if (CollectionUtils.isEmpty(LegacyImportHandler.CUSTOM_CONTENT_WRITE_ROLES)) {
                                removedRoles.addAll(LegacyImportHandler.WRITE_ROLES);
                            } else {
                                removedRoles.addAll(LegacyImportHandler.CUSTOM_CONTENT_WRITE_ROLES);
                            }
                        }

                        if (perm.charAt(2) == 'a') {
                            if (CollectionUtils.isEmpty(LegacyImportHandler.CUSTOM_CONTENT_ADMIN_ROLES)) {
                                grantedRoles.addAll(LegacyImportHandler.ADMIN_ROLES);
                            } else {
                                grantedRoles.addAll(LegacyImportHandler.CUSTOM_CONTENT_ADMIN_ROLES);
                            }
                        } else {
                            if (CollectionUtils.isEmpty(LegacyImportHandler.CUSTOM_CONTENT_ADMIN_ROLES)) {
                                removedRoles.addAll(LegacyImportHandler.ADMIN_ROLES);
                            } else {
                                removedRoles.addAll(LegacyImportHandler.CUSTOM_CONTENT_ADMIN_ROLES);
                            }
                        }

                        String principal = ace.substring(0, colonIndex);
                        if (principal.equals("g:guest")) {
                            principal = "u:guest";
                        }
                        if (!grantedRoles.isEmpty()) {
                            node.grantRoles(principal, grantedRoles);
                        }
                        if (!removedRoles.isEmpty()) {
                            node.denyRoles(principal, removedRoles);
                        }
                    }
                }
            } catch (RepositoryException e) {
                logger.error("Cannot set user or group ACL entry !!", e);
            }
        }
    }

    private void setNodeField(String uri, String localName, String uuid, Attributes attributes)
            throws RepositoryException, SAXException {

        JCRNodeWrapper node = getCurrentContentNode();
        String title = attributes.getValue("jahia:title");
        String propertyName = currentCtx.peek().propertyNames.peek();

        boolean isProperty = false;

        for (String s : node.getNodeTypes()) {
            ExtendedNodeType nt = NodeTypeRegistry.getInstance().getNodeType(s);
            if (nt.getChildNodeDefinitionsAsMap().containsKey(propertyName)) {
                break;
            } else if (nt.getPropertyDefinitionsAsMap().containsKey(propertyName)) {
                isProperty = true;
                break;
            }
        }

        final Map<String, String> creationMetadata = getMetadataForNodeCreation(attributes);
        if (HTTP_WWW_JAHIA_ORG.equals(uri) && PAGE.equals(localName)) {
            createPage(attributes.getValue(Name.NS_JCR_URI, "primaryType"), title,
                    attributes.getValue("jahia:template"), attributes.getValue(HTTP_WWW_JAHIA_ORG, "pageKey"), uuid,
                    creationMetadata, attributes.getValue("jahia:pid"), attributes.getValue("jcr:mixinTypes"),
                    getAdditionalProperties(attributes.getValue("jcr:additionalProperties")));
            setAcl(attributes.getValue(HTTP_WWW_JAHIA_ORG, "acl"));

            // todo : add a link here ??
        } else if (HTTP_WWW_JAHIA_ORG.equals(uri) && LINK.equals(localName)) {
            String reference = attributes.getValue("jahia:reference");
            if (StringUtils.isNotBlank(reference)) {
                if (!isProperty && !node.hasNode(propertyName)) {
                    JCRNodeWrapper sub = addOrCheckoutNode(node, propertyName, "jnt:nodeLink", null,
                            creationMetadata);

                    if (StringUtils.isNotBlank(title)) {
                        Node translation = getOrCreateI18N(sub, locale, creationMetadata);
                        translation.setProperty(Constants.JCR_TITLE, title);
                    }

                    sub.setProperty(Constants.JCR_TITLE, title);

                    if (!references.containsKey(reference)) {
                        references.put(reference, new ArrayList<String>());
                    }
                    references.get(reference).add(sub.getIdentifier() + "/" + Constants.NODE);
                } else if (isProperty && !node.hasProperty(propertyName)) {
                    Node target = node;
                    if (node.getApplicablePropertyDefinition(propertyName) != null
                            && node.getApplicablePropertyDefinition(propertyName).isInternationalized()) {
                        target = getOrCreateI18N(node, locale, creationMetadata);
                    }
                    if (!references.containsKey(reference)) {
                        references.put(reference, new ArrayList<String>());
                    }
                    references.get(reference).add(target.getIdentifier() + "/" + propertyName);
                }
            } else {
                logger.error("Invalid reference on markup " + uuid);
            }
            currentCtx.peek().pushSkip();
        } else if (HTTP_WWW_JAHIA_ORG.equals(uri) && localName.equals("url")) {

            String value = attributes.getValue("jahia:value");
            if (!node.hasNode(propertyName)) {
                JCRNodeWrapper sub = addOrCheckoutNode(node, propertyName, "jnt:externalLink", null,
                        creationMetadata);

                if (StringUtils.isNotBlank(title)) {
                    Node translation = getOrCreateI18N(sub, locale, creationMetadata);
                    translation.setProperty(Constants.JCR_TITLE, title);
                }

                sub.setProperty("j:url", value);
            } else if (isProperty && !node.hasProperty(propertyName)) {

            }
            currentCtx.peek().pushSkip();
        } else {
            throw new SAXException("Unexpected " + localName);
        }
    }

    private boolean setPropertyField(ExtendedNodeType baseType, String localName, String value,
            Map<String, String> creationMetadata) throws RepositoryException {
        String propertyName = baseType != null ? mapping.getMappedProperty(baseType, localName)
                : mapping.getMappedMetadataProperty(localName);
        logger.debug("localName: " + localName + ", propertyName: " + propertyName);
        return setPropertyField(baseType, localName, getCurrentContentNode(), propertyName, value,
                creationMetadata);
    }

    private boolean setPropertyField(ExtendedNodeType baseType, String localName, JCRNodeWrapper node,
            String propertyName, String value, Map<String, String> creationMetadata) throws RepositoryException {
        JCRNodeWrapper parent = node;
        String mixinType = null;
        if (propertyName.contains("|")) {
            mixinType = StringUtils.substringBefore(propertyName, "|");
            propertyName = StringUtils.substringAfter(propertyName, "|");
        }
        if (StringUtils.contains(propertyName, "/")) {
            String parentPath = StringUtils.substringBeforeLast(propertyName, "/");
            if (parent.hasNode(parentPath)) {
                parent = parent.getNode(parentPath);
            }
            propertyName = StringUtils.substringAfterLast(propertyName, "/");
        }
        parent = checkoutNode(parent);
        if (!StringUtils.isEmpty(mixinType) && !parent.isNodeType(mixinType)) {
            parent.addMixin(mixinType);
        }

        ExtendedPropertyDefinition propertyDefinition;
        propertyDefinition = parent.getApplicablePropertyDefinition(propertyName);
        if (propertyDefinition == null) {
            return false;
        }
        if (propertyDefinition.isProtected()) {
            return false;
        }

        if (StringUtils.isNotBlank(value) && !value.equals("<empty>")) {
            Node n = parent;
            if (propertyDefinition.isInternationalized()) {
                n = getOrCreateI18N(parent, locale, creationMetadata);
            }
            if (!n.isCheckedOut()) {
                session.checkout(n);
            }
            switch (propertyDefinition.getRequiredType()) {
            case PropertyType.DATE:
                GregorianCalendar cal = new GregorianCalendar();
                try {
                    DateFormat df = new SimpleDateFormat(ImportExportService.DATE_FORMAT);
                    Date d = df.parse(value);
                    cal.setTime(d);
                    n.setProperty(propertyName, cal);
                } catch (java.text.ParseException e) {
                    logger.error(e.getMessage(), e);
                }
                break;

            case PropertyType.REFERENCE:
            case PropertyType.WEAKREFERENCE:
                if (propertyDefinition.isMultiple()) {
                    String[] strings = Patterns.TRIPPLE_DOLLAR.split(value);
                    for (String s : strings) {
                        createReferenceValue(s, propertyDefinition.getSelector(), n, propertyName);
                    }
                } else {
                    createReferenceValue(value, propertyDefinition.getSelector(), n, propertyName);
                }
                break;

            default:
                switch (propertyDefinition.getSelector()) {
                case SelectorType.RICHTEXT: {
                    if (value.contains("=\"###")) {
                        int count = 1;
                        StringBuilder buf = new StringBuilder(value);
                        while (buf.indexOf("=\"###") > -1) {
                            int from = buf.indexOf("=\"###") + 2;
                            int to = buf.indexOf("\"", from);

                            String ref = buf.substring(from, to);
                            if (ref.startsWith("###/webdav")) {
                                ref = StringUtils.substringAfter(ref, "###/webdav");
                                buf.replace(from, to, "##doc-context##/{workspace}/##ref:link" + count + "##");
                            } else if (ref.startsWith("###file:")) {
                                ref = StringUtils.substringAfter(ref, "###file:");
                                final int qmPos = ref.indexOf('?');
                                boolean isUuid = false;
                                if (qmPos != -1) {
                                    if (StringUtils.substring(ref, qmPos + 1).startsWith("uuid=default:")) {
                                        ref = StringUtils.substring(ref, qmPos + 14);
                                        isUuid = true;
                                    } else
                                        ref = StringUtils.substringBefore(ref, "?");
                                }
                                if (!isUuid)
                                    ref = correctFilename(ref);
                                buf.replace(from, to, "##doc-context##/{workspace}/##ref:link" + count + "##");
                            } else {
                                ref = StringUtils.substringAfterLast(ref, "/");
                                // we keep the URL parameters if any
                                String params = "";
                                int pos = ref.indexOf('?');
                                if (pos == -1) {
                                    pos = ref.indexOf('#');
                                }
                                if (pos != -1) {
                                    params = ref.substring(pos);
                                    ref = ref.substring(0, pos);
                                }
                                buf.replace(from, to,
                                        "##cms-context##/{mode}/{lang}/##ref:link" + count + "##.html" + params);
                            }
                            try {
                                ref = URLDecoder.decode(ref, "UTF-8");
                            } catch (UnsupportedEncodingException e) {
                            }

                            if (!references.containsKey(ref)) {
                                references.put(ref, new ArrayList<String>());
                            }
                            references.get(ref)
                                    .add(n.getIdentifier() + "/[" + count + "]" + propertyDefinition.getName());
                            count++;
                        }
                        value = buf.toString();
                    }
                    n.setProperty(propertyName, value);
                    if (logger.isDebugEnabled())
                        logger.debug("Setting on node " + n.getPath() + " property " + propertyName + " with value="
                                + value);
                    break;
                }
                default: {
                    String[] vcs = propertyDefinition.getValueConstraints();
                    List<String> constraints = Arrays.asList(vcs);
                    if (!propertyDefinition.isMultiple()) {
                        if (value.startsWith("<jahia-resource")) {
                            value = ResourceBundleMarker.parseMarkerValue(value).getResourceKey();
                            if (value.startsWith(propertyDefinition.getResourceBundleKey())) {
                                value = value.substring(propertyDefinition.getResourceBundleKey().length() + 1);
                            }
                        } else if ("jcr:description".equals(propertyName)) {
                            value = removeHtmlTags(value);
                        }

                        value = baseType != null ? mapping.getMappedPropertyValue(baseType, localName, value)
                                : value;
                        if (valueMatchesContraints(value, constraints)) {
                            try {
                                n.setProperty(propertyName, value);
                                if (logger.isDebugEnabled())
                                    logger.debug("Setting on node " + n.getPath() + " property " + propertyName
                                            + " with value=" + value);
                            } catch (Exception e) {
                                logger.error("Impossible to set property " + propertyName + " due to exception", e);
                            }
                        } else {
                            logger.error(
                                    "Impossible to set property " + propertyName + " due to some constraint error");
                            logger.error(" - value       = " + value);
                            logger.error(" - constraints = " + constraints.toString());
                        }
                    } else {
                        String[] strings = Patterns.TRIPPLE_DOLLAR.split(value);
                        List<Value> values = new ArrayList<Value>();
                        for (String string : strings) {

                            if (string.startsWith("<jahia-resource")) {
                                string = ResourceBundleMarker.parseMarkerValue(string).getResourceKey();
                                if (string.startsWith(propertyDefinition.getResourceBundleKey())) {
                                    string = string
                                            .substring(propertyDefinition.getResourceBundleKey().length() + 1);
                                }
                            }
                            value = baseType != null ? mapping.getMappedPropertyValue(baseType, localName, value)
                                    : value;
                            if (constraints.isEmpty() || constraints.contains(value)) {
                                values.add(new ValueImpl(string, propertyDefinition.getRequiredType()));
                            }
                        }
                        n.setProperty(propertyName, values.toArray(new Value[values.size()]));
                        if (logger.isDebugEnabled())
                            logger.debug("Setting on node " + n.getPath() + " property " + propertyName
                                    + " with value=" + values);
                    }
                    break;
                }
                }
            }
        } else {
            return false;
        }

        return true;
    }

    private boolean valueMatchesContraints(String value, List<String> constraints) {
        if (constraints == null)
            return true;
        if (constraints.isEmpty())
            return true;
        if (constraints.contains(value))
            return true;
        for (final String constraint : constraints) {
            if (Pattern.compile(constraint).matcher(value).matches())
                return true;
        }
        return false;
    }

    private void createReferenceValue(String value, int selector, Node node, String prop)
            throws RepositoryException {
        try {
            switch (selector) {
            case SelectorType.CATEGORY: {
                List<Category> c = ServicesRegistry.getInstance().getCategoryService().getCategory(value);
                if (c.isEmpty()) {
                    logger.warn("Cannot find category : " + value);
                } else {
                    if (!references.containsKey(c.get(0).getID())) {
                        references.put(c.get(0).getID(), new ArrayList<String>());
                    }
                    references.get(c.get(0).getID()).add(node.getIdentifier() + "/" + prop);
                    if (c.size() > 1) {
                        logger.warn("Multiple category match : " + value);
                    }
                }
                break;
            }
            default: {
                if (value.startsWith("/")) {
                    value = correctFilename(value);
                    if (!references.containsKey(value)) {
                        references.put(value, new ArrayList<String>());
                    }
                    references.get(value).add(node.getIdentifier() + "/" + prop);
                } else {
                    String uuid = StringUtils.substringAfter(value, ":");
                    if (!references.containsKey(uuid)) {
                        references.put(uuid, new ArrayList<String>());
                    }
                    references.get(uuid).add(node.getIdentifier() + "/" + prop);
                }
            }
            }
        } catch (JahiaException e) {
            logger.error("Cannot get categories", e);
        }
    }

    private String correctFilename(String value) {
        /*
        if (value.startsWith("/users/")) {
        Matcher m = Pattern.compile("/users/([^/]+)(/.*)?").matcher(value);
        if (m.matches()) {
            value = ServicesRegistry.getInstance().getJahiaUserManagerService().getUserSplittingRule().getPathForUsername(m.group(1));
            value = value + "/files" + ((m.group(2) != null) ? m.group(2) : "");
        }
        } else
        */
        if (value.startsWith("/content/users/")) {
            Matcher m = Pattern.compile("/content/users/([^/]+)(/.*)?").matcher(value);
            if (m.matches()) {
                value = ServicesRegistry.getInstance().getJahiaUserManagerService().getUserSplittingRule()
                        .getPathForUsername(m.group(1));
                value = value + ((m.group(2) != null) ? m.group(2) : "");
            }
        }
        return JCRContentUtils.escapeNodePath(value);
    }

    private String removeHtmlTags(String value) {
        Source source = new Source(value);
        TextExtractor textExtractor = source.getTextExtractor();
        textExtractor.setExcludeNonHTMLElements(true);
        textExtractor.setConvertNonBreakingSpaces(false);
        textExtractor.setIncludeAttributes(false);
        return textExtractor.toString();
    }

    private void handleAdditionalMixinsAndProperties(JCRNodeWrapper node, String mixinsToAdd,
            JSONObject propertiesToSet, Map<String, String> creationMetadata) {
        if (node == null)
            return;

        if (StringUtils.isNotBlank(mixinsToAdd)) {
            if (logger.isDebugEnabled()) {
                logger.debug(MessageFormat.format("Adding mixins [{0}] to the node {1} in language {2}",
                        mixinsToAdd, node.getPath(), this.locale.toString()));
            }
            for (final String mixin : Arrays.asList(StringUtils.split(mixinsToAdd))) {
                try {
                    if (StringUtils.isNotBlank(mixin) && !node.isNodeType(mixin))
                        node.addMixin(mixin);
                } catch (RepositoryException re) {
                    logger.error(MessageFormat.format("Imposible to apply mixin {0} to the node {1}", mixin,
                            node.getPath()), re);
                }
            }
        }

        if (propertiesToSet != null) {
            final Iterator<String> properties = propertiesToSet.keys();
            while (properties.hasNext()) {
                final String propName = properties.next();
                try {
                    final String propValue = String.valueOf(propertiesToSet.get(propName));
                    if (logger.isDebugEnabled()) {
                        logger.debug(MessageFormat.format(
                                "Setting the property {0} with value [{1}] on the node {2} in language {3}",
                                propName, propValue, node.getPath(), this.locale.toString()));
                    }
                    setPropertyField(null, null, node, propName, propValue, creationMetadata);
                } catch (Exception e) {
                    logger.error(MessageFormat.format(
                            "Error while setting additional property {0} on the node {1} for locale {2}", propName,
                            node.getPath(), this.locale.toString()), e);

                }
            }
        }
    }

    private JCRNodeWrapper getCurrentPageNode() {
        return currentCtx.peek().contents.firstElement();
    }

    private JCRNodeWrapper getCurrentContentNode() {
        return currentCtx.peek().contents.peek();
    }

    private ExtendedNodeType getCurrentContentType() {
        return currentCtx.peek().contentsType.peek();
    }

    public int getCtnId() {
        return ctnId;
    }

    class PageContext {
        Stack<Integer> ctx = new Stack<Integer>();
        Stack<JCRNodeWrapper> contents = new Stack<JCRNodeWrapper>();
        Stack<ExtendedNodeType> contentsType = new Stack<ExtendedNodeType>();
        Stack<String> propertyNames = new Stack<String>();
        Stack<String> acls = new Stack<String>();
        Stack<Map<String, String>> properties = new Stack<Map<String, String>>();

        PageContext(JCRNodeWrapper page, ExtendedNodeType pageType) {
            contents.push(page);
            contentsType.push(pageType);
            propertyNames.push(null);
            acls.push(null);
            properties.push(null);
            ctx.push(CTX_PAGE);
        }

        void pushList(JCRNodeWrapper node, ExtendedNodeType type) {
            if (logger.isDebugEnabled()) {
                logger.debug(" push " + currentNode + " , ctx = " + CTX_LIST);
            }
            contents.push(node);
            contentsType.push(type);
            propertyNames.push(null);
            acls.push(null);
            properties.push(properties.peek() != null ? new HashMap<>(properties.peek()) : null);
            ctx.push(CTX_LIST);
        }

        void pushContainer(JCRNodeWrapper node, ExtendedNodeType type) {
            if (logger.isDebugEnabled()) {
                logger.debug(" push " + currentNode + " , ctx = " + CTX_CTN);
            }
            contents.push(node);
            contentsType.push(type);
            propertyNames.push(null);
            acls.push(null);
            properties.push(properties.peek() != null ? new HashMap<>(properties.peek()) : null);
            ctx.push(CTX_CTN);
        }

        void pushDirectContainer() {
            contents.push(getCurrentContentNode());
            contentsType.push(null);
            propertyNames.push(null);
            acls.push(null);
            properties.push(null);
            ctx.push(CTX_DIRECTSUBNODES);
        }

        void pushField(String propertyName) {
            if (logger.isDebugEnabled()) {
                logger.debug(" push " + currentNode + " , ctx = " + CTX_FIELD);
            }
            contents.push(contents.peek());
            contentsType.push(contentsType.peek());
            propertyNames.push(propertyName);
            acls.push(null);
            properties.push(null);
            ctx.push(CTX_FIELD);
        }

        void pushNavLink(ExtendedNodeType t, String acl) {
            if (logger.isDebugEnabled()) {
                logger.debug(" push " + currentNode + " , ctx = " + CTX_NAVLINK);
            }
            contents.push(contents.peek());
            contentsType.push(contentsType.peek());
            propertyNames.push(null);
            acls.push(acl);
            properties
                    .push(ctx.peek() == CTX_NAVLINK && properties.peek() != null ? new HashMap<>(properties.peek())
                            : new HashMap<String, String>());
            ctx.push(CTX_NAVLINK);
        }

        void pushSkip() {
            if (logger.isDebugEnabled()) {
                logger.debug(" push " + currentNode + " , ctx = " + CTX_SKIP);
            }
            contents.push(contents.peek());
            contentsType.push(contentsType.peek());
            propertyNames.push(null);
            acls.push(null);
            properties.push(null);
            ctx.push(CTX_SKIP);
        }

        void pushShareable(ExtendedNodeType t) {
            if (logger.isDebugEnabled()) {
                logger.debug(" push " + currentNode + " , ctx = " + CTX_SHAREABLE);
            }
            contents.push(contents.peek());
            contentsType.push(t);
            propertyNames.push(null);
            acls.push(null);
            properties.push(null);
            ctx.push(CTX_SHAREABLE);
        }

        void pop() {
            contents.pop();
            contentsType.pop();
            propertyNames.pop();
            acls.pop();
            properties.pop();
            ctx.pop();
        }

    }
}