org.jahia.modules.external.ExternalSessionImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.jahia.modules.external.ExternalSessionImpl.java

Source

/**
 * ==========================================================================================
 * =                   JAHIA'S DUAL LICENSING - IMPORTANT INFORMATION                       =
 * ==========================================================================================
 *
 *                                 http://www.jahia.com
 *
 *     Copyright (C) 2002-2016 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.modules.external;

import org.apache.commons.lang.StringUtils;
import org.apache.jackrabbit.core.security.JahiaLoginModule;
import org.jahia.modules.external.acl.ExternalDataAce;
import org.jahia.modules.external.acl.ExternalDataAcl;
import org.jahia.services.content.JCRStoreProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;

import javax.jcr.*;
import javax.jcr.lock.LockException;
import javax.jcr.lock.LockManager;
import javax.jcr.nodetype.ConstraintViolationException;
import javax.jcr.nodetype.NoSuchNodeTypeException;
import javax.jcr.retention.RetentionManager;
import javax.jcr.version.VersionException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.AccessControlException;
import java.util.*;

/**
 * Implementation of the {@link javax.jcr.Session} for the {@link org.jahia.modules.external.ExternalData}.
 *
 * @author Thomas Draier
 */
public class ExternalSessionImpl implements Session {

    static final String TRANSLATION_PREFIX = "translation:";
    static final String TRANSLATION_NODE_NAME_BASE = "j:translation_";
    static final String ACE_PREFIX = "j:ace:";

    private static final Logger logger = LoggerFactory.getLogger(ExternalSessionImpl.class);

    private ExternalRepositoryImpl repository;
    private ExternalWorkspaceImpl workspace;
    private Credentials credentials;
    private Map<String, ExternalNodeImpl> nodesByPath = new HashMap<String, ExternalNodeImpl>();
    private Map<String, ExternalNodeImpl> nodesByIdentifier = new HashMap<String, ExternalNodeImpl>();
    private Set<ExternalItemImpl> newItems = new HashSet<ExternalItemImpl>();
    private Map<String, ExternalData> changedData = new LinkedHashMap<String, ExternalData>();
    private Map<String, ExternalData> deletedData = new LinkedHashMap<String, ExternalData>();
    private Map<String, List<String>> orderedData = new LinkedHashMap<String, List<String>>();
    private Set<Binary> tempBinaries = new HashSet<Binary>();
    private Session extensionSession;
    private List<String> extensionAllowedTypes;
    private List<String> extensionForbiddenMixins;
    private Map<String, List<String>> overridableProperties;
    private Map<String, List<String>> nonOverridableProperties;
    private Map<String, Object> sessionVariables = new HashMap<String, Object>();
    private ExternalAccessControlManager accessControlManager;

    private static volatile int fromCacheCount = 0;
    private static volatile int totalCacheChecks = 0;
    private static volatile int totalSavedCalls = 0;
    private static final boolean DEBUG = false;

    public ExternalSessionImpl(ExternalRepositoryImpl repository, Credentials credentials, String workspaceName) {
        this.repository = repository;
        this.workspace = new ExternalWorkspaceImpl(this, workspaceName);
        this.credentials = credentials;
    }

    public ExternalRepositoryImpl getRepository() {
        return repository;
    }

    public String getUserID() {
        return ((SimpleCredentials) credentials).getUserID();
    }

    public String getRealm() {
        return (String) ((SimpleCredentials) credentials).getAttribute(JahiaLoginModule.REALM_ATTRIBUTE);
    }

    public Object getAttribute(String s) {
        return null;
    }

    public String[] getAttributeNames() {
        return new String[0];
    }

    public Workspace getWorkspace() {
        return workspace;
    }

    public Session impersonate(Credentials credentials) throws LoginException, RepositoryException {
        return this;
    }

    private ExternalNodeImpl getFromCacheByPath(String path) {
        return getFromCache(path, nodesByPath);
    }

    private ExternalNodeImpl getFromCacheById(String id) {
        return getFromCache(id, nodesByIdentifier);
    }

    private static ExternalNodeImpl getFromCache(String key, Map<String, ExternalNodeImpl> cache) {

        totalCacheChecks++;

        if (cache.isEmpty()) {
            totalSavedCalls += fromCacheCount;
            if (DEBUG) {
                logger.debug("Saved " + fromCacheCount + " calls out of " + totalCacheChecks
                        + " since last cache reset. Total saved calls: " + totalSavedCalls);
            }
            fromCacheCount = 0;
            totalCacheChecks = 0;
            return null;
        }

        final ExternalNodeImpl node = cache.get(key);
        if (node != null) {
            fromCacheCount++;
        }

        return node;
    }

    public Node getRootNode() throws RepositoryException {
        getAccessControlManager().checkRead("/");
        final Node fromCache = getFromCacheByPath("/");
        if (fromCache != null) {
            return fromCache;
        }

        ExternalContentStoreProvider.setCurrentSession(this);
        try {
            // check provider availability if possible
            if (repository.getDataSource() instanceof ExternalDataSource.CanCheckAvailability) {
                boolean available = ((ExternalDataSource.CanCheckAvailability) repository.getDataSource())
                        .isAvailable();
                if (!available) {
                    throw new RepositoryException(
                            "Provider '" + repository.getProviderKey() + "' is currently unavailable");
                }
            }
            ExternalData rootFileObject = repository.getDataSource().getItemByPath("/");
            final ExternalNodeImpl externalNode = new ExternalNodeImpl(rootFileObject, this);
            registerNode(externalNode);
            return externalNode;
        } finally {
            ExternalContentStoreProvider.removeCurrentSession();
        }
    }

    public Node getNodeByUUID(String uuid) throws ItemNotFoundException, RepositoryException {
        final ExternalNodeImpl fromCacheById = getFromCacheById(uuid);
        if (fromCacheById != null) {
            try {
                getAccessControlManager().checkRead(fromCacheById.getPath());
            } catch (PathNotFoundException e) {
                throw new ItemNotFoundException(fromCacheById.getPath());
            }
            return fromCacheById;
        }

        if (!repository.getDataSource().isSupportsUuid() || uuid.startsWith(TRANSLATION_PREFIX)) {
            if (!uuid.startsWith(getRepository().getStoreProvider().getId())) {
                throw new ItemNotFoundException("Item " + uuid + " could not be found in this repository");
            }
            // Translate UUID to external mapping
            String externalId = repository.getStoreProvider().getExternalProviderInitializerService()
                    .getExternalIdentifier(uuid);
            if (externalId == null) {
                throw new ItemNotFoundException("Item " + uuid + " could not be found in this repository");
            }
            uuid = externalId;
        }
        Node n = getNodeByLocalIdentifier(uuid);
        try {
            getAccessControlManager().checkRead(n.getPath());
        } catch (PathNotFoundException e) {
            throw new ItemNotFoundException(n.getPath());
        }
        return n;
    }

    private Node getNodeByLocalIdentifier(String uuid) throws RepositoryException {
        for (ExternalItemImpl i : newItems) {
            if (i instanceof ExternalNodeImpl) {
                ExternalNodeImpl n = (ExternalNodeImpl) i;
                if (uuid.equals(n.getIdentifier())) {
                    return n;
                }
            }
        }

        for (ExternalData d : changedData.values()) {
            if (uuid.equals(d.getId())) {
                return new ExternalNodeImpl(d, this);
            }
        }

        if (uuid.startsWith(TRANSLATION_PREFIX)) {
            String u = StringUtils.substringAfter(uuid, TRANSLATION_PREFIX);
            String lang = StringUtils.substringBefore(u, ":");
            u = StringUtils.substringAfter(u, ":");
            return getNodeByLocalIdentifier(u).getNode(TRANSLATION_NODE_NAME_BASE + lang);
        }

        if (uuid.startsWith(ExternalDataAcl.ACL_NODE_NAME)) {
            String u = StringUtils.substringAfter(uuid, ExternalDataAcl.ACL_NODE_NAME + ":");
            return getNodeByLocalIdentifier(u).getNode(ExternalDataAcl.ACL_NODE_NAME);
        }

        if (uuid.startsWith(ACE_PREFIX)) {
            String u = StringUtils.substringAfter(uuid, ACE_PREFIX);
            String ace = StringUtils.substringBefore(u, ":");
            u = StringUtils.substringAfter(u, ":");
            return getNodeByLocalIdentifier(u).getNode(ExternalDataAcl.ACL_NODE_NAME + "/" + ace);
        }

        try {
            if (getExtensionSession() != null) {
                Node n = getExtensionSession().getNodeByIdentifier(uuid);
                return new ExtensionNode(n,
                        StringUtils.substringAfter(n.getPath(), repository.getStoreProvider().getMountPoint()),
                        this);
            }
        } catch (RepositoryException e) {
            // do nothing
        }
        ExternalContentStoreProvider.setCurrentSession(this);
        try {
            Node n = new ExternalNodeImpl(repository.getDataSource().getItemByIdentifier(uuid), this);
            if (deletedData.containsKey(n.getPath())) {
                throw new ItemNotFoundException("This node has been deleted");
            }
            return n;
        } finally {
            ExternalContentStoreProvider.removeCurrentSession();
        }
    }

    public Item getItem(String path) throws PathNotFoundException, RepositoryException {
        Item itemWithNoCheck = getItemWithNoCheck(path);
        getAccessControlManager().checkRead(path);
        return itemWithNoCheck;

    }

    protected Item getItemWithNoCheck(String path) throws PathNotFoundException, RepositoryException {
        path = path.length() > 1 && path.endsWith("/") ? path.substring(0, path.length() - 1) : path;
        if (deletedData.containsKey(path)) {
            throw new PathNotFoundException("This node has been deleted");
        }

        final Node fromCache = getFromCacheByPath(path);
        if (fromCache != null) {
            return fromCache;
        }

        String parentPath = StringUtils.substringBeforeLast(path, "/");
        if (parentPath.equals("")) {
            parentPath = "/";
        }
        try {
            if (StringUtils.substringAfterLast(parentPath, "/").startsWith(TRANSLATION_NODE_NAME_BASE)) {
                // Getting a translation property
                return getNode(parentPath).getProperty(StringUtils.substringAfterLast(path, "/"));
            } else if (StringUtils.substringAfterLast(path, "/").startsWith(TRANSLATION_NODE_NAME_BASE)) {
                // Getting translation node
                return handleI18nNode(parentPath, path);
            } else if (StringUtils.substringAfterLast(parentPath, "/").equals(ExternalDataAcl.ACL_NODE_NAME)
                    && repository.getDataSource() instanceof ExternalDataSource.AccessControllable) {
                // Getting ace node or acl property
                String last = StringUtils.substringAfterLast(path, "/");
                if (last.startsWith(ExternalDataAce.Type.DENY.toString())
                        || last.startsWith(ExternalDataAce.Type.GRANT.toString())) {
                    // get the ace node
                    return handleAceNode(parentPath, path, last);
                } else {
                    // get the property
                    return getNode(parentPath).getProperty(last);
                }
            } else if (StringUtils.substringAfterLast(path, "/").equals(ExternalDataAcl.ACL_NODE_NAME)
                    && repository.getDataSource() instanceof ExternalDataSource.AccessControllable) {
                // Getting acl node
                return handleAclNode(parentPath, path);
            } else if ((StringUtils.substringAfterLast(parentPath, "/")
                    .startsWith(ExternalDataAce.Type.GRANT.toString())
                    || StringUtils.substringAfterLast(path, "/").startsWith(ExternalDataAce.Type.DENY.toString()))
                    && StringUtils.substringBeforeLast(parentPath, "/").endsWith("/j:acl")
                    && repository.getDataSource() instanceof ExternalDataSource.AccessControllable) {
                // Getting ace node property
                return getNode(parentPath).getProperty(StringUtils.substringAfterLast(path, "/"));
            } else {
                String itemName = StringUtils.substringAfterLast(path, "/");
                if (getRepository().getStoreProvider().getReservedNodes().contains(itemName)) {
                    throw new PathNotFoundException(path);
                }
                // Try to get the item as a node
                ExternalContentStoreProvider.setCurrentSession(this);
                try {
                    ExternalData data = repository.getDataSource().getItemByPath(path);
                    final ExternalNodeImpl node = new ExternalNodeImpl(data, this);
                    registerNode(node);
                    return node;
                } catch (PathNotFoundException e) {
                    // Or a property in the parent node
                    ExternalNodeImpl parentFromPath = getFromCacheByPath(parentPath);
                    if (parentFromPath == null) {
                        ExternalData data = repository.getDataSource().getItemByPath(parentPath);
                        final ExternalNodeImpl node = new ExternalNodeImpl(data, this);
                        registerNode(node);
                        parentFromPath = node;
                    }
                    return parentFromPath.getProperty(itemName);
                } finally {
                    ExternalContentStoreProvider.removeCurrentSession();
                }
            }
        } catch (PathNotFoundException e) {
            // In case item is not found in provider, lookup in extension provider if available
            if (getExtensionSession() != null && !StringUtils.equals("/", path)) {
                Item item = getExtensionSession().getItem(repository.getStoreProvider().getMountPoint() + path);
                if ((item.isNode() ? (Node) item : item.getParent()).isNodeType("jnt:externalProviderExtension")) {
                    throw e;
                }
                return item.isNode() ? new ExtensionNode((Node) item, path, this)
                        : new ExtensionProperty((Property) item, path, this,
                                new ExtensionNode(item.getParent(), parentPath, this));
            } else {
                throw e;
            }
        }
    }

    private Item handleAclNode(String parentPath, String path) throws RepositoryException {
        // Getting acl node
        ExternalData parentObject = getParent(parentPath);
        if (parentObject.getExternalDataAcl() == null) {
            throw new PathNotFoundException(path);
        }

        ExternalData acl = new ExternalData(ExternalDataAcl.ACL_NODE_NAME + ":" + parentObject.getId(), path,
                ExternalDataAcl.ACL_NODE_TYPE, parentObject.getExternalDataAcl().getProperties());

        final ExternalNodeImpl node = new ExternalNodeImpl(acl, this);
        registerNode(node);
        return node;
    }

    private Item handleAceNode(String parentPath, String path, String last) throws RepositoryException {
        ExternalData parentObject = getParent(StringUtils.substringBeforeLast(parentPath, "/"));
        if (parentObject.getExternalDataAcl() == null || parentObject.getExternalDataAcl().getAce(last) == null) {
            throw new PathNotFoundException(path);
        }

        ExternalDataAce externalDataAce = parentObject.getExternalDataAcl().getAce(last);
        ExternalData ace = new ExternalData(ACE_PREFIX + last + ":" + parentObject.getId(), path,
                ExternalDataAce.ACE_NODE_TYPE, externalDataAce.getProperties());

        final ExternalNodeImpl node = new ExternalNodeImpl(ace, this);
        registerNode(node);
        return node;
    }

    private Item handleI18nNode(String parentPath, String path) throws RepositoryException {
        ExternalData parentObject = getParent(parentPath);
        String lang = StringUtils.substringAfterLast(path, TRANSLATION_NODE_NAME_BASE);

        if ((parentObject.getI18nProperties() == null || !parentObject.getI18nProperties().containsKey(lang))
                && (parentObject.getLazyI18nProperties() == null
                        || !parentObject.getLazyI18nProperties().containsKey(lang))) {
            throw new PathNotFoundException(path);
        }
        Map<String, String[]> i18nProps = new HashMap<String, String[]>();
        if (parentObject.getI18nProperties() != null && parentObject.getI18nProperties().containsKey(lang)) {
            i18nProps.putAll(parentObject.getI18nProperties().get(lang));
        }
        i18nProps.put("jcr:language", new String[] { lang });
        ExternalData i18n = new ExternalData(TRANSLATION_PREFIX + lang + ":" + parentObject.getId(), path,
                "jnt:translation", i18nProps);
        if (parentObject.getLazyI18nProperties() != null
                && parentObject.getLazyI18nProperties().containsKey(lang)) {
            i18n.setLazyProperties(parentObject.getLazyI18nProperties().get(lang));
        }

        final ExternalNodeImpl node = new ExternalNodeImpl(i18n, this);
        registerNode(node);
        return node;
    }

    private ExternalData getParent(String parentPath) throws RepositoryException {
        ExternalData parentObject;
        final ExternalNodeImpl parentFromCache = getFromCacheByPath(parentPath);
        if (parentFromCache != null) {
            parentObject = parentFromCache.getData();
        } else {
            ExternalContentStoreProvider.setCurrentSession(this);
            try {
                parentObject = repository.getDataSource().getItemByPath(parentPath);
                final ExternalNodeImpl node = new ExternalNodeImpl(parentObject, this);
                registerNode(node);
            } finally {
                ExternalContentStoreProvider.removeCurrentSession();
            }
        }
        return parentObject;
    }

    protected String[] getPropertyValues(ExternalData data, String propertyName) throws PathNotFoundException {
        ExternalDataSource dataSource = repository.getDataSource();
        if (dataSource instanceof ExternalDataSource.LazyProperty) {
            ExternalContentStoreProvider.setCurrentSession(this);
            try {
                return ((ExternalDataSource.LazyProperty) dataSource).getPropertyValues(data.getPath(),
                        propertyName);
            } finally {
                ExternalContentStoreProvider.removeCurrentSession();
            }
        } else {
            throw new PathNotFoundException(repository.getProviderKey() + " doesn't support lazy properties");
        }
    }

    protected String[] getI18nPropertyValues(ExternalData data, String lang, String propertyName)
            throws PathNotFoundException {
        ExternalDataSource dataSource = repository.getDataSource();
        if (dataSource instanceof ExternalDataSource.LazyProperty) {
            ExternalContentStoreProvider.setCurrentSession(this);
            try {
                return ((ExternalDataSource.LazyProperty) dataSource).getI18nPropertyValues(
                        StringUtils.substringBeforeLast(data.getPath(), "/"), lang, propertyName);
            } finally {
                ExternalContentStoreProvider.removeCurrentSession();
            }
        } else {
            throw new PathNotFoundException(repository.getProviderKey() + " doesn't support lazy properties");
        }
    }

    protected Binary[] getBinaryPropertyValues(ExternalData data, String propertyName)
            throws PathNotFoundException {
        ExternalDataSource dataSource = repository.getDataSource();
        if (dataSource instanceof ExternalDataSource.LazyProperty) {
            ExternalContentStoreProvider.setCurrentSession(this);
            try {
                return ((ExternalDataSource.LazyProperty) dataSource).getBinaryPropertyValues(data.getPath(),
                        propertyName);
            } finally {
                ExternalContentStoreProvider.removeCurrentSession();
            }
        } else {
            throw new PathNotFoundException(repository.getProviderKey() + " doesn't support lazy properties");
        }
    }

    @Override
    public boolean itemExists(String path) throws RepositoryException {
        // todo : use repository.getDataSource().itemExists(path)
        try {
            getItem(path);
        } catch (PathNotFoundException e) {
            return false;
        }
        return true;
    }

    public void move(String source, String dest) throws ItemExistsException, PathNotFoundException,
            VersionException, ConstraintViolationException, LockException, RepositoryException {
        getAccessControlManager().checkRemoveNode(source);
        getAccessControlManager().checkAddChildNodes(StringUtils.substringBeforeLast(dest, "/"));
        Item sourceNode = getItem(source);
        if (!sourceNode.isNode()) {
            throw new PathNotFoundException(source);
        }
        if (sourceNode instanceof ExtensionNode) {
            String targetName = StringUtils.substringAfterLast(dest, "/");
            String parentPath = StringUtils.substringBeforeLast(dest, "/");
            Node targetNode = (Node) getItem(parentPath);
            final String srcAbsPath = ((ExtensionNode) sourceNode).getJcrNode().getPath();
            Node jcrNode = null;
            if (targetNode instanceof ExtensionNode) {
                jcrNode = ((ExtensionNode) targetNode).getJcrNode();
            } else if (targetNode instanceof ExternalNodeImpl) {
                final ExternalNodeImpl externalNode = (ExternalNodeImpl) targetNode;
                Node extendedNode = externalNode.getExtensionNode(true);
                if (extendedNode != null && externalNode.canItemBeExtended(targetName,
                        ((ExtensionNode) sourceNode).getPrimaryNodeType().getName())) {
                    jcrNode = extendedNode;
                }
            }
            if (jcrNode != null) {
                getExtensionSession().move(srcAbsPath, jcrNode.getPath() + "/" + targetName);
                return;
            }
        } else if (sourceNode instanceof ExternalNodeImpl) {
            if (!(repository.getDataSource() instanceof ExternalDataSource.Writable)) {
                throw new UnsupportedRepositoryOperationException();
            }

            if (source.equals(dest)) {
                return;
            }

            final ExternalNodeImpl externalNode = (ExternalNodeImpl) sourceNode;

            final ExternalNodeImpl previousParent = (ExternalNodeImpl) externalNode.getParent();
            final List<String> previousParentChildren = previousParent.getExternalChildren();

            ExternalContentStoreProvider.setCurrentSession(this);
            try {
                //todo : store move in session and move node in save
                ((ExternalDataSource.Writable) repository.getDataSource()).move(source, dest);

                int oldIndex = previousParentChildren.indexOf(externalNode.getName());
                previousParentChildren.remove(externalNode.getName());
                unregisterNode(externalNode);

                ExternalData newData = repository.getDataSource().getItemByPath(dest);

                final ExternalNodeImpl newExternalNode = new ExternalNodeImpl(newData, this);
                registerNode(newExternalNode);

                final ExternalNodeImpl newParent = (ExternalNodeImpl) newExternalNode.getParent();
                if (newParent.equals(previousParent)) {
                    previousParentChildren.add(oldIndex, newExternalNode.getName());
                } else if (!newParent.getExternalChildren().contains(newExternalNode.getName())) {
                    newParent.getExternalChildren().add(newExternalNode.getName());
                }

                final ExternalData oldData = externalNode.getData();
                if (oldData.getId().equals(newData.getId())) {
                    return;
                }
                getRepository().getStoreProvider().getExternalProviderInitializerService().updateExternalIdentifier(
                        oldData.getId(), newData.getId(), getRepository().getProviderKey(),
                        getRepository().getDataSource().isSupportsHierarchicalIdentifiers());
                return;
            } finally {
                ExternalContentStoreProvider.removeCurrentSession();
            }
        }

        throw new UnsupportedRepositoryOperationException();
    }

    public void save() throws AccessDeniedException, ItemExistsException, ConstraintViolationException,
            InvalidItemStateException, VersionException, LockException, NoSuchNodeTypeException,
            RepositoryException {
        if (extensionSession != null && extensionSession.hasPendingChanges()) {
            extensionSession.save();
        }
        if (!(repository.getDataSource() instanceof ExternalDataSource.Writable)) {
            deletedData.clear();
            changedData.clear();
            orderedData.clear();
            return;
        }
        ExternalContentStoreProvider.setCurrentSession(this);
        try {
            Map<String, ExternalData> changedDataWithI18n = new LinkedHashMap<String, ExternalData>();
            for (Map.Entry<String, ExternalData> entry : changedData.entrySet()) {
                String path = entry.getKey();
                ExternalData externalData = entry.getValue();
                if (path.startsWith(TRANSLATION_NODE_NAME_BASE, path.lastIndexOf("/") + 1)) {
                    String lang = StringUtils.substringAfterLast(path, TRANSLATION_NODE_NAME_BASE);
                    String parentPath = StringUtils.substringBeforeLast(path, "/");
                    ExternalData parentData;
                    if (changedDataWithI18n.containsKey(parentPath)) {
                        parentData = changedDataWithI18n.get(parentPath);
                    } else {
                        parentData = repository.getDataSource().getItemByPath(parentPath);
                    }
                    Map<String, Map<String, String[]>> i18nProperties = parentData.getI18nProperties();
                    if (i18nProperties == null) {
                        i18nProperties = new HashMap<String, Map<String, String[]>>();
                        parentData.setI18nProperties(i18nProperties);
                    }
                    i18nProperties.put(lang, externalData.getProperties());

                    if (externalData.getLazyProperties() != null) {
                        Map<String, Set<String>> lazyI18nProperties = parentData.getLazyI18nProperties();
                        if (lazyI18nProperties == null) {
                            lazyI18nProperties = new HashMap<String, Set<String>>();
                            parentData.setLazyI18nProperties(lazyI18nProperties);
                        }
                        lazyI18nProperties.put(lang, externalData.getLazyProperties());
                    }

                    changedDataWithI18n.put(parentPath, parentData);
                } else {
                    changedDataWithI18n.put(path, externalData);
                }
            }
            ExternalDataSource.Writable writableDataSource = (ExternalDataSource.Writable) repository
                    .getDataSource();
            for (String path : orderedData.keySet()) {
                writableDataSource.order(path, orderedData.get(path));
            }
            orderedData.clear();
            for (ExternalData data : changedDataWithI18n.values()) {
                writableDataSource.saveItem(data);
                // when data contain binaries we flush the nodes so the binary will be load
                // from the external data source after an upload, avoid to cache a tmp binary after upload for exemple
                if (data.getBinaryProperties() != null && data.getBinaryProperties().size() > 0) {
                    ExternalNodeImpl cachedNode = nodesByPath.get(data.getPath());
                    if (cachedNode != null) {
                        nodesByPath.remove(data.getPath());
                        nodesByIdentifier.remove(cachedNode.getIdentifier());
                    }
                }
            }

            changedData.clear();
            if (!deletedData.isEmpty()) {
                List<String> toBeDeleted = new LinkedList<String>();
                for (String path : deletedData.keySet()) {
                    writableDataSource.removeItemByPath(path);
                    toBeDeleted.add(deletedData.get(path).getId());
                }
                getRepository().getStoreProvider().getExternalProviderInitializerService().delete(toBeDeleted,
                        getRepository().getStoreProvider().getKey(),
                        getRepository().getDataSource().isSupportsHierarchicalIdentifiers());
                deletedData.clear();
            }
            for (ExternalItemImpl newItem : newItems) {
                newItem.setNew(false);
            }
            newItems.clear();
        } finally {
            ExternalContentStoreProvider.removeCurrentSession();
        }
    }

    public void refresh(boolean keepChanges) throws RepositoryException {
        if (!keepChanges) {
            deletedData.clear();
            changedData.clear();
            orderedData.clear();
            newItems.clear();
            nodesByPath.clear();
            nodesByIdentifier.clear();
        } else {
            List<String> pathsToKeep = new ArrayList<String>();
            pathsToKeep.addAll(changedData.keySet());
            pathsToKeep.addAll(deletedData.keySet());
            pathsToKeep.addAll(orderedData.keySet());
            List<String> idsToKeep = new ArrayList<String>();
            for (String s : pathsToKeep) {
                final ExternalNodeImpl node = getFromCacheByPath(s);
                if (node != null) {
                    idsToKeep.add(node.getIdentifier());
                }
            }
            nodesByPath.keySet().retainAll(pathsToKeep);
            nodesByIdentifier.keySet().retainAll(idsToKeep);
        }
    }

    public boolean hasPendingChanges() throws RepositoryException {
        return extensionSession != null && extensionSession.hasPendingChanges()
                || repository.getDataSource() instanceof ExternalDataSource.Writable
                        && (!deletedData.isEmpty() || !changedData.isEmpty() || !orderedData.isEmpty());
    }

    public ExternalValueFactoryImpl getValueFactory()
            throws UnsupportedRepositoryOperationException, RepositoryException {
        return new ExternalValueFactoryImpl(this);
    }

    public void checkPermission(String s, String s1) throws AccessControlException, RepositoryException {

    }

    public ContentHandler getImportContentHandler(String s, int i) throws PathNotFoundException,
            ConstraintViolationException, VersionException, LockException, RepositoryException {
        return null;
    }

    public void importXML(String s, InputStream inputStream, int i)
            throws IOException, PathNotFoundException, ItemExistsException, ConstraintViolationException,
            VersionException, InvalidSerializedDataException, LockException, RepositoryException {

    }

    public void exportSystemView(String s, ContentHandler contentHandler, boolean b, boolean b1)
            throws PathNotFoundException, SAXException, RepositoryException {

    }

    public void exportSystemView(String s, OutputStream outputStream, boolean b, boolean b1)
            throws IOException, PathNotFoundException, RepositoryException {

    }

    public void exportDocumentView(String s, ContentHandler contentHandler, boolean b, boolean b1)
            throws PathNotFoundException, SAXException, RepositoryException {

    }

    public void exportDocumentView(String s, OutputStream outputStream, boolean b, boolean b1)
            throws IOException, PathNotFoundException, RepositoryException {

    }

    public void setNamespacePrefix(String s, String s1) throws NamespaceException, RepositoryException {

    }

    public String[] getNamespacePrefixes() throws RepositoryException {
        return workspace.getNamespaceRegistry().getPrefixes();
    }

    public String getNamespaceURI(String s) throws NamespaceException, RepositoryException {
        return workspace.getNamespaceRegistry().getURI(s);
    }

    public String getNamespacePrefix(String s) throws NamespaceException, RepositoryException {
        return workspace.getNamespaceRegistry().getPrefix(s);
    }

    public void logout() {
        if (extensionSession != null && extensionSession.isLive()) {
            extensionSession.logout();
            extensionSession = null;
        }

        for (Binary binary : tempBinaries) {
            binary.dispose();
        }
        accessControlManager = null;
    }

    public boolean isLive() {
        return true;
    }

    public void addLockToken(String s) {
        try {
            LockManager extensionLockManager = getExtensionSession().getWorkspace().getLockManager();
            if (extensionLockManager != null) {
                extensionLockManager.addLockToken(s);
            }
        } catch (RepositoryException e) {
            logger.error("Unable to add lock token " + s, e.getMessage());
        }
    }

    public String[] getLockTokens() {
        try {
            if (getExtensionSession() == null) {
                return new String[0];
            }
            return getExtensionSession().getWorkspace().getLockManager().getLockTokens();
        } catch (RepositoryException e) {
            return new String[0];
        }
    }

    public void removeLockToken(String s) {
        try {
            LockManager extensionLockManager = getExtensionSession().getWorkspace().getLockManager();
            if (extensionLockManager != null) {
                extensionLockManager.removeLockToken(s);
            }
        } catch (RepositoryException e) {
            logger.error("Unable to remove lock token " + s, e.getMessage());
        }

    }

    public Map<String, ExternalData> getChangedData() {
        return changedData;
    }

    public Map<String, ExternalData> getDeletedData() {
        return deletedData;
    }

    public Map<String, List<String>> getOrderedData() {
        return orderedData;
    }

    public void registerNode(ExternalNodeImpl node) throws RepositoryException {
        nodesByPath.put(node.getPath(), node);
        nodesByIdentifier.put(node.getIdentifier(), node);
    }

    public void unregisterNode(ExternalNodeImpl node) throws RepositoryException {
        nodesByPath.remove(node.getPath());
        nodesByIdentifier.remove(node.getIdentifier());
        changedData.remove(node.getPath());
        orderedData.remove(node.getPath());
        newItems.remove(node);
    }

    public void registerTemporaryBinary(Binary binary) throws RepositoryException {
        tempBinaries.add(binary);
    }

    public Set<ExternalItemImpl> getNewItems() {
        return newItems;
    }

    public void setNewItem(ExternalItemImpl newItem) throws RepositoryException {
        newItem.setNew(true);
        newItems.add(newItem);
    }

    public Node getNodeByIdentifier(String id) throws ItemNotFoundException, RepositoryException {
        return getNodeByUUID(id);
    }

    public Node getNode(String absPath) throws PathNotFoundException, RepositoryException {
        return (Node) getItem(absPath);
    }

    public Property getProperty(String absPath) throws PathNotFoundException, RepositoryException {
        return (Property) getItem(absPath);
    }

    public boolean nodeExists(String absPath) throws RepositoryException {
        return itemExists(absPath);
    }

    public boolean propertyExists(String absPath) throws RepositoryException {
        return itemExists(absPath);
    }

    public void removeItem(String absPath) throws VersionException, LockException, ConstraintViolationException,
            AccessDeniedException, RepositoryException {
        getItem(absPath).remove();
    }

    public boolean hasPermission(String absPath, String actions) throws RepositoryException {
        // TODO implement me
        return false;
    }

    public boolean hasCapability(String s, Object o, Object[] objects) throws RepositoryException {
        // TODO implement me
        return false;
    }

    public ExternalAccessControlManager getAccessControlManager()
            throws UnsupportedRepositoryOperationException, RepositoryException {
        if (accessControlManager == null) {
            accessControlManager = new ExternalAccessControlManager(repository.getNamespaceRegistry(), this,
                    repository.getDataSource());
        }
        return accessControlManager;
    }

    public RetentionManager getRetentionManager()
            throws UnsupportedRepositoryOperationException, RepositoryException {
        return null;
    }

    public Session getExtensionSession() throws RepositoryException {
        if (extensionSession == null) {
            JCRStoreProvider extensionProvider = getRepository().getStoreProvider().getExtensionProvider();
            if (extensionProvider != null) {
                extensionSession = extensionProvider.getSession(
                        JahiaLoginModule.getSystemCredentials(
                                StringUtils.removeStart(getUserID(), JahiaLoginModule.SYSTEM), getRealm()),
                        "default");
            }
        }
        return extensionSession;
    }

    public List<String> getExtensionAllowedTypes() throws RepositoryException {
        if (extensionAllowedTypes == null) {
            extensionAllowedTypes = getRepository().getStoreProvider().getExtendableTypes();
            if (extensionAllowedTypes == null) {
                extensionAllowedTypes = Arrays.asList("nt:base");
            }
        }
        return extensionAllowedTypes;
    }

    /**
     * Return the properties that can be overridden for extendable nodetypes
     * @return a map of list of properties (value) by nodetype (key)
     */
    public Map<String, List<String>> getOverridableProperties() {
        if (overridableProperties == null) {
            overridableProperties = new HashMap<String, List<String>>();
            List<String> overridablePropertiesString = getRepository().getStoreProvider().getOverridableItems();
            if (overridablePropertiesString != null) {
                for (String s : overridablePropertiesString) {
                    String nodeType = StringUtils.substringBefore(s, ".");
                    String property = StringUtils.substringAfter(s, ".");
                    if (!overridableProperties.containsKey(nodeType)) {
                        overridableProperties.put(nodeType, new ArrayList<String>());
                    }
                    overridableProperties.get(nodeType).add(property);
                }
            }
        }
        return overridableProperties;
    }

    /**
     * Return the properties that can NOT be overridden for extendable nodetypes
     * @return a map of list of properties (value) by nodetype (key)
     */
    public Map<String, List<String>> getNonOverridableProperties() {
        if (nonOverridableProperties == null) {
            nonOverridableProperties = new HashMap<String, List<String>>();
            List<String> nonOverridablePropertiesString = getRepository().getStoreProvider()
                    .getNonOverridableItems();
            if (nonOverridablePropertiesString != null) {
                for (String s : nonOverridablePropertiesString) {
                    String nodeType = StringUtils.substringBefore(s, ".");
                    String property = StringUtils.substringAfter(s, ".");
                    if (!nonOverridableProperties.containsKey(nodeType)) {
                        nonOverridableProperties.put(nodeType, new ArrayList<String>());
                    }
                    nonOverridableProperties.get(nodeType).add(property);
                }
            }
        }
        return nonOverridableProperties;
    }

    public List<String> getExtensionForbiddenMixins() throws RepositoryException {
        if (extensionForbiddenMixins == null) {
            extensionForbiddenMixins = getRepository().getStoreProvider().getNonExtendableMixins();
            if (extensionForbiddenMixins == null) {
                extensionForbiddenMixins = Collections.emptyList();
            }
        }
        return extensionForbiddenMixins;
    }

    public Map<String, Object> getSessionVariables() {
        return sessionVariables;
    }

}