org.jahia.services.content.JCRSessionWrapper.java Source code

Java tutorial

Introduction

Here is the source code for org.jahia.services.content.JCRSessionWrapper.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.content;

import org.apache.commons.lang.StringUtils;
import org.apache.jackrabbit.commons.xml.SystemViewExporter;
import org.apache.jackrabbit.core.security.JahiaLoginModule;
import org.jahia.api.Constants;
import org.jahia.services.content.decorator.JCRNodeDecorator;
import org.jahia.services.content.decorator.JCRUserNode;
import org.jahia.services.content.decorator.validation.AdvancedGroup;
import org.jahia.services.content.decorator.validation.AdvancedSkipOnImportGroup;
import org.jahia.services.content.decorator.validation.DefaultSkipOnImportGroup;
import org.jahia.services.content.decorator.validation.JCRNodeValidator;
import org.jahia.services.content.nodetypes.ExtendedPropertyDefinition;
import org.jahia.services.content.nodetypes.NodeTypeRegistry;
import org.jahia.services.importexport.DocumentViewExporter;
import org.jahia.services.importexport.DocumentViewImportHandler;
import org.jahia.services.importexport.ReferencesHelper;
import org.jahia.services.usermanager.JahiaUser;
import org.jahia.services.usermanager.JahiaUserManagerService;
import org.jahia.settings.SettingsBean;
import org.jahia.utils.i18n.Messages;
import org.jahia.utils.xml.JahiaSAXParserFactory;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

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.security.AccessControlManager;
import javax.jcr.version.Version;
import javax.jcr.version.VersionException;
import javax.jcr.version.VersionHistory;
import javax.jcr.version.VersionManager;
import javax.validation.ConstraintViolation;
import javax.validation.groups.Default;
import javax.xml.parsers.SAXParser;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.AccessControlException;
import java.util.*;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Jahia specific wrapper around <code>javax.jcr.Session</code> to be able to inject
 * Jahia specific actions and to manage sessions to multiple repository providers in
 * the backend.
 * <p/>
 * Jahia services should use this wrapper rather than the original session interface to
 * ensure that we manipulate wrapped nodes and not the ones from the underlying
 * implementation.
 *
 * @author toto
 */
public class JCRSessionWrapper implements Session {
    private static Logger logger = org.slf4j.LoggerFactory.getLogger(JCRSessionWrapper.class);
    public static final String DEREF_SEPARATOR = "@/";

    private JCRSessionFactory sessionFactory;
    private JahiaUser user;
    private Credentials credentials;
    private JCRWorkspaceWrapper workspace;
    private boolean isLive = true;
    private Locale locale;
    private List<String> tokens = new ArrayList<String>();

    private Map<JCRStoreProvider, Session> sessions = new HashMap<JCRStoreProvider, Session>();

    private Map<String, JCRNodeWrapper> sessionCacheByPath = new HashMap<String, JCRNodeWrapper>();
    private Map<String, JCRNodeWrapper> sessionCacheByIdentifier = new HashMap<String, JCRNodeWrapper>();
    private Map<String, JCRNodeWrapper> newNodes = new HashMap<String, JCRNodeWrapper>();
    private Map<String, JCRNodeWrapper> changedNodes = new HashMap<String, JCRNodeWrapper>();

    private Map<String, String> nsToPrefix = new HashMap<String, String>();
    private Map<String, String> prefixToNs = new HashMap<String, String>();

    private Map<String, String> uuidMapping = new HashMap<String, String>();
    private Map<String, String> pathMapping = new LinkedHashMap<String, String>();

    private Map<String, Object> resolvedReferences = new HashMap<String, Object>();

    private boolean isSystem;
    private boolean skipValidation;
    private boolean isCurrentUserSession = false;
    private Date versionDate;

    private Locale fallbackLocale;
    private String versionLabel;

    private static AtomicLong activeSessions = new AtomicLong(0L);

    private Exception thisSessionTrace;
    protected UUID uuid;
    private static Map<UUID, JCRSessionWrapper> activeSessionsObjects = new ConcurrentSkipListMap<UUID, JCRSessionWrapper>();

    public JCRSessionWrapper(JahiaUser user, Credentials credentials, boolean isSystem, String workspace,
            Locale locale, JCRSessionFactory sessionFactory, Locale fallbackLocale) {
        uuid = UUID.randomUUID();
        this.user = user;
        this.credentials = credentials;
        this.isSystem = isSystem;
        this.versionDate = null;
        this.versionLabel = null;
        if (workspace == null) {
            this.workspace = new JCRWorkspaceWrapper("default", this, sessionFactory);
        } else {
            this.workspace = new JCRWorkspaceWrapper(workspace, this, sessionFactory);
        }
        this.locale = locale;
        this.fallbackLocale = fallbackLocale;
        this.sessionFactory = sessionFactory;
        if (!isSystem) {
            activeSessions.incrementAndGet();
        }
        if (SettingsBean.getInstance().isDevelopmentMode()) {
            thisSessionTrace = new Exception((isSystem ? "System " : "") + "Session: " + uuid + " Thread: "
                    + Thread.currentThread().getName() + "_" + Thread.currentThread().getId() + " created "
                    + new DateTime().toString());
        } else {
            thisSessionTrace = new Exception((isSystem ? "System " : "") + "Session: " + uuid);
        }
        activeSessionsObjects.put(uuid, this);
    }

    public JCRNodeWrapper getRootNode() throws RepositoryException {
        JCRStoreProvider provider = sessionFactory.getProvider("/");
        return provider.getNodeWrapper(getProviderSession(provider).getRootNode(), "/", null, this);
    }

    public Repository getRepository() {
        return sessionFactory;
    }

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

    public boolean isSystem() {
        return isSystem;
    }

    public boolean isSkipValidation() {
        return skipValidation;
    }

    public void setSkipValidation(boolean skipValidation) {
        this.skipValidation = skipValidation;
    }

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

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

    public JCRWorkspaceWrapper getWorkspace() {
        return workspace;
    }

    public Locale getLocale() {
        return locale;
    }

    //    public void setInterceptorsEnabled(boolean interceptorsEnabled) {
    //        this.interceptorsEnabled = interceptorsEnabled;
    //    }

    public Session impersonate(Credentials credentials) throws LoginException, RepositoryException {
        throw new UnsupportedRepositoryOperationException();
    }

    public JCRNodeWrapper getNodeByUUID(String uuid) throws ItemNotFoundException, RepositoryException {
        return getNodeByUUID(uuid, true);
    }

    public JCRNodeWrapper getNodeByUUID(final String uuid, final boolean checkVersion)
            throws ItemNotFoundException, RepositoryException {
        if (StringUtils.isEmpty(uuid)) {
            throw new RepositoryException("invalid identifier: " + uuid);
        }
        if (sessionCacheByIdentifier.containsKey(uuid)) {
            return sessionCacheByIdentifier.get(uuid);
        }
        RepositoryException originalEx = null;
        for (JCRStoreProvider provider : sessionFactory.getProviderList()) {
            if (!provider.isInitialized()) {
                logger.debug("Provider " + provider.getKey() + " / " + provider.getClass().getName()
                        + " is not yet initialized, skipping...");
                continue;
            }

            try {
                Session session = getProviderSession(provider);
                boolean isAliased = sessionFactory.checkAliasedStatusAndToggleSessionIfNeeded(session, getUser());
                Node n = session.getNodeByIdentifier(uuid);
                JCRNodeWrapper wrapper = null;
                if (checkVersion && (versionDate != null || versionLabel != null)) {
                    JCRNodeWrapper frozen = getFrozenVersionAsRegular(n, provider, false);
                    if (frozen != null) {
                        wrapper = frozen;
                    }
                }
                if (wrapper == null) {
                    wrapper = provider.getNodeWrapper(n, this);
                }
                if (!isAliased) {
                    sessionCacheByIdentifier.put(uuid, wrapper);
                    sessionCacheByPath.put(wrapper.getPath(), wrapper);
                }

                return wrapper;
            } catch (ItemNotFoundException ee) {
                // All good
                if (originalEx == null) {
                    originalEx = ee;
                }
            } catch (UnsupportedRepositoryOperationException uso) {
                logger.debug("getNodeByUUID unsupported by: {} / {}", provider.getKey(),
                        provider.getClass().getName());
                if (originalEx == null) {
                    originalEx = uso;
                }
            } catch (RepositoryException ex) {
                if (originalEx == null) {
                    originalEx = ex;
                }
                logger.warn("repository exception : " + provider.getKey() + " / " + provider.getClass().getName()
                        + " : " + ex.getMessage());
            }
        }
        if (originalEx != null) {
            if (originalEx instanceof ItemNotFoundException) {
                throw originalEx;
            } else {
                throw new ItemNotFoundException(uuid, originalEx);
            }
        }

        throw new ItemNotFoundException(uuid);
    }

    public JCRNodeWrapper getNodeByUUID(String providerKey, String uuid)
            throws ItemNotFoundException, RepositoryException {
        JCRStoreProvider provider = sessionFactory.getProviders().get(providerKey);
        if (provider == null) {
            throw new ItemNotFoundException(uuid);
        }
        Session session = getProviderSession(provider);
        Node n = session.getNodeByIdentifier(uuid);
        return provider.getNodeWrapper(n, this);
    }

    public JCRItemWrapper getItem(String path) throws PathNotFoundException, RepositoryException {
        return getItem(path, true);
    }

    public JCRItemWrapper getItem(String path, final boolean checkVersion)
            throws PathNotFoundException, RepositoryException {
        if (sessionCacheByPath.containsKey(path)) {
            return sessionCacheByPath.get(path);
        }
        if (path.contains(DEREF_SEPARATOR)) {
            JCRNodeWrapper parent = (JCRNodeWrapper) getItem(StringUtils.substringBeforeLast(path, DEREF_SEPARATOR),
                    checkVersion);
            return dereference(parent, StringUtils.substringAfterLast(path, DEREF_SEPARATOR));
        }
        for (Map.Entry<String, JCRStoreProvider> mp : sessionFactory.getMountPoints().entrySet()) {
            String key = mp.getKey();
            JCRStoreProvider provider = mp.getValue();
            if (provider.isDefault() || path.equals(key) || path.startsWith(key + "/")) {
                String localPath = path;
                if (!key.equals("/")) {
                    localPath = localPath.substring(key.length());
                }
                if (localPath.equals("")) {
                    localPath = "/";
                }
                //                Item item = getProviderSession(provider).getItem(localPath);
                Session session = getProviderSession(provider);
                boolean isAliased = sessionFactory.checkAliasedStatusAndToggleSessionIfNeeded(session, getUser());
                Item item = session.getItem(provider.getRelativeRoot() + localPath);
                if (item.isNode()) {
                    final Node node = (Node) item;
                    JCRNodeWrapper wrapper = null;
                    if (checkVersion && (versionDate != null || versionLabel != null)
                            && node.isNodeType("mix:versionable")) {
                        JCRNodeWrapper frozen = getFrozenVersionAsRegular(node, provider, false);
                        if (frozen != null) {
                            wrapper = frozen;
                        }
                    }
                    if (wrapper == null) {
                        wrapper = provider.getNodeWrapper(node, localPath, null, this);
                    }

                    if (!isAliased) {
                        sessionCacheByPath.put(path, wrapper);
                        sessionCacheByIdentifier.put(wrapper.getIdentifier(), wrapper);
                    }

                    return wrapper;
                } else {
                    // because of https://jira.jahia.org/browse/QA-6810, we retrieve the property from the parent
                    // node to make sure that we go through any filtering that is implemented at a node decorator level,
                    // as it is the case for the JCRUserNode. A more complete solution would involve implementing
                    // the same decorator system around properties but this is much more complex and risky to do
                    // than this (simple) method.
                    JCRPropertyWrapper jcrPropertyWrapper = provider.getPropertyWrapper((Property) item, this);
                    return jcrPropertyWrapper.getParent().getProperty(jcrPropertyWrapper.getName());
                }
            }
        }
        throw new PathNotFoundException(path);
    }

    private JCRNodeWrapper dereference(JCRNodeWrapper parent, String refPath) throws RepositoryException {
        JCRNodeWrapper wrapper;
        JCRNodeWrapper referencedNode = ((JCRNodeWrapper) parent.getProperty(Constants.NODE).getNode());
        Node realReferencedNode = referencedNode.getRealNode();
        String fullPath = parent.getPath() + DEREF_SEPARATOR + refPath;
        // check if one of the parent is the same as the referenced Node

        if (checkCyclicReference(parent.getPath(), referencedNode.getPath())) {
            throw new PathNotFoundException(
                    String.format("cyclic reference %s in path %s", referencedNode.getPath(), fullPath));
        }
        String refRootName = StringUtils.substringBefore(refPath, "/");
        if (!realReferencedNode.getName().equals(refRootName)) {
            throw new PathNotFoundException(fullPath);
        }
        refPath = StringUtils.substringAfter(refPath, "/");
        if (refPath.equals("")) {
            wrapper = referencedNode.getProvider().getNodeWrapper(realReferencedNode, fullPath, parent, this);
        } else {
            Node node = realReferencedNode.getNode(refPath);
            fullPath = parent.getPath() + DEREF_SEPARATOR + refRootName
                    + node.getPath().substring(realReferencedNode.getPath().length());
            wrapper = referencedNode.getProvider().getNodeWrapper(node, fullPath, null, this);
        }
        sessionCacheByPath.put(fullPath, wrapper);
        return wrapper;
    }

    private boolean checkCyclicReference(String path, String reference) {
        try {
            int lastIndexOfDeref = StringUtils.lastIndexOf(path, DEREF_SEPARATOR);
            while (lastIndexOfDeref != -1 || StringUtils.startsWith(path, reference + "/")
                    || StringUtils.equals(path, reference)) {
                if (lastIndexOfDeref != -1) {
                    path = StringUtils.substring(path, 0, lastIndexOfDeref);
                    JCRNodeWrapper referencedNode = (JCRNodeWrapper) getNode(path).getProperty(Constants.NODE)
                            .getNode();
                    if (StringUtils.equals(referencedNode.getPath(), reference)) {
                        return true;
                    }
                } else {
                    return true;
                }
                lastIndexOfDeref = StringUtils.lastIndexOf(path, DEREF_SEPARATOR);
            }
        } catch (RepositoryException e) {
            logger.debug("unable to check cyclic reference between node {} and its reference {}",
                    new String[] { path, reference }, e);
            // do nothing
        }
        return false;
    }

    public JCRNodeWrapper getNode(String path) throws PathNotFoundException, RepositoryException {
        return getNode(path, true);
    }

    public JCRNodeWrapper getNode(String path, boolean checkVersion)
            throws PathNotFoundException, RepositoryException {
        JCRItemWrapper item = getItem(path, checkVersion);
        if (item.isNode()) {
            return (JCRNodeWrapper) item;
        } else {
            throw new PathNotFoundException();
        }
    }

    public boolean itemExists(String path) throws RepositoryException {
        try {
            getItem(path);
            return true;
        } catch (RepositoryException e) {
            return false;
        }
    }

    public void move(String source, String dest) throws ItemExistsException, PathNotFoundException,
            VersionException, ConstraintViolationException, LockException, RepositoryException {
        getWorkspace().move(source, dest, true);
        updatePathInCache(source, dest, sessionCacheByPath);
        updatePathInCache(source, dest, newNodes);
        updatePathInCache(source, dest, changedNodes);
    }

    private void updatePathInCache(String source, String dest, Map<String, JCRNodeWrapper> cacheByPath) {
        String sourcePrefix = source + "/";
        Set<String> paths = new HashSet<>(cacheByPath.keySet());
        for (String s : paths) {
            if (s.equals(source) || s.startsWith(sourcePrefix)) {
                JCRNodeWrapper n = cacheByPath.remove(s);
                if (n instanceof JCRNodeDecorator) {
                    n = ((JCRNodeDecorator) n).getDecoratedNode();
                }
                String newPath = dest;
                if (source.length() < n.getPath().length()) {
                    newPath += n.getPath().substring(source.length());
                }
                String localPath = newPath;
                if (n.getProvider().getMountPoint().length() > 1) {
                    localPath = newPath.substring(n.getProvider().getMountPoint().length());
                }
                ((JCRNodeWrapperImpl) n).localPath = localPath;
                ((JCRNodeWrapperImpl) n).localPathInProvider = localPath;
                cacheByPath.put(newPath, n);
            }
        }
    }

    public void save() throws AccessDeniedException, ItemExistsException, ConstraintViolationException,
            InvalidItemStateException, VersionException, LockException, NoSuchNodeTypeException,
            RepositoryException {
        save(JCRObservationManager.SESSION_SAVE);
    }

    void registerNewNode(JCRNodeWrapper node) {
        newNodes.put(node.getPath(), node);
    }

    void registerChangedNode(JCRNodeWrapper node) {
        if (!newNodes.containsKey(node.getPath())) {
            changedNodes.put(node.getPath(), node);
        }
    }

    void unregisterNewNode(JCRNodeWrapper node) {
        if (!newNodes.isEmpty() || !changedNodes.isEmpty()) {
            newNodes.remove(node.getPath());
            changedNodes.remove(node.getPath());
            try {
                if (node.hasNodes()) {
                    NodeIterator it = node.getNodes();
                    while (it.hasNext()) {
                        unregisterNewNode((JCRNodeWrapper) it.next());
                    }
                }
            } catch (RepositoryException e) {
                logger.warn("Error unregistering new nodes", e);
            }
        }
    }

    public void save(final int operationType) throws AccessDeniedException, ItemExistsException,
            ConstraintViolationException, InvalidItemStateException, VersionException, LockException,
            NoSuchNodeTypeException, RepositoryException {
        validate(operationType);
        newNodes.clear();
        changedNodes.clear();

        JCRObservationManager.doWorkspaceWriteCall(this, operationType, new JCRCallback<Object>() {
            public Object doInJCR(JCRSessionWrapper thisSession) throws RepositoryException {
                for (Session session : sessions.values()) {
                    session.save();
                }
                return null;
            }
        });

        if (workspace.getName().equals("default")) {
            // If reference helper found values to update, update them in live too
            ReferencesHelper.updateReferencesInLive(getResolvedReferences());
        }
    }

    public void validate() throws ConstraintViolationException, RepositoryException {
        validate(JCRObservationManager.SESSION_SAVE);
    }

    protected void validate(final int operationType) throws ConstraintViolationException, RepositoryException {
        if (!skipValidation) {
            CompositeConstraintViolationException exception = validateNodes(newNodes.values(), null, operationType);
            exception = validateNodes(changedNodes.values(), exception, operationType);
            if (exception != null) {
                refresh(true);
                throw exception;
            }
        }
    }

    protected CompositeConstraintViolationException validateNodes(Collection<JCRNodeWrapper> nodes,
            CompositeConstraintViolationException ccve, final int operationType)
            throws ConstraintViolationException, RepositoryException {
        boolean isImportOperation = operationType == JCRObservationManager.IMPORT;
        for (JCRNodeWrapper node : nodes) {
            try {
                for (String s : node.getNodeTypes()) {
                    Collection<ExtendedPropertyDefinition> propDefs = NodeTypeRegistry.getInstance().getNodeType(s)
                            .getPropertyDefinitionsAsMap().values();
                    for (ExtendedPropertyDefinition propertyDefinition : propDefs) {
                        String propertyName = propertyDefinition.getName();
                        if (propertyDefinition.isMandatory()
                                && propertyDefinition.getRequiredType() != PropertyType.WEAKREFERENCE
                                && propertyDefinition.getRequiredType() != PropertyType.REFERENCE
                                && !propertyDefinition.isProtected()
                                && (!propertyDefinition.isInternationalized() || getLocale() != null)
                                && (!node.hasProperty(propertyName) || (!propertyDefinition.isMultiple()
                                        && propertyDefinition.getRequiredType() != PropertyType.BINARY
                                        && StringUtils.isEmpty(node.getProperty(propertyName).getString()))

                                )) {

                            Locale errorLocale = null;
                            if (propertyDefinition.isInternationalized()) {
                                errorLocale = getLocale();
                            }

                            ccve = addError(ccve,
                                    new PropertyConstraintViolationException(node,
                                            Messages.getInternal("label.error.mandatoryField",
                                                    LocaleContextHolder.getLocale(), "Field is mandatory"),
                                            errorLocale, propertyDefinition));
                        }
                    }
                }
            } catch (InvalidItemStateException e) {
                logger.debug("A new node can no longer be accessed to run validation checks", e);
            }

            Map<String, Constructor<?>> validators = sessionFactory.getDefaultProvider().getValidators();
            Set<ConstraintViolation<JCRNodeValidator>> constraintViolations = new LinkedHashSet<ConstraintViolation<JCRNodeValidator>>();
            for (Map.Entry<String, Constructor<?>> validatorEntry : validators.entrySet()) {
                if (node.isNodeType(validatorEntry.getKey())) {
                    try {
                        JCRNodeValidator validatorDecoratedNode = (JCRNodeValidator) validatorEntry.getValue()
                                .newInstance(node);
                        LocalValidatorFactoryBean validatorFactoryBean = sessionFactory.getValidatorFactoryBean();

                        // if we are in non-import operation we enforce Default and DefaultSkipOnImportGroup;
                        // if we are in an import operation we do not enforce the DefaultSkipOnImportGroup, but rather only the Default one
                        Set<ConstraintViolation<JCRNodeValidator>> validate = !isImportOperation
                                ? validatorFactoryBean.validate(validatorDecoratedNode, Default.class,
                                        DefaultSkipOnImportGroup.class)
                                : validatorFactoryBean.validate(validatorDecoratedNode, Default.class);

                        if (validate.isEmpty()) {
                            // we enforce advanced validations only in case the default group succeeds

                            // if we are in non-import operation we enforce both AdvancedGroup and AdvancedSkipOnImportGroup;
                            // if we are in an import operation we do not enforce the AdvancedSkipOnImportGroup, but rather only the
                            // AdvancedGroup one
                            validate = !isImportOperation
                                    ? validatorFactoryBean.validate(validatorDecoratedNode, AdvancedGroup.class,
                                            AdvancedSkipOnImportGroup.class)
                                    : validatorFactoryBean.validate(validatorDecoratedNode, AdvancedGroup.class);
                        }

                        constraintViolations.addAll(validate);
                    } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
                        logger.error(e.getMessage(), e);
                    }
                }
            }
            for (ConstraintViolation<JCRNodeValidator> constraintViolation : constraintViolations) {
                String propertyName;
                try {
                    Method propertyNameGetter = constraintViolation.getConstraintDescriptor().getAnnotation()
                            .annotationType().getMethod("propertyName");
                    propertyName = (String) propertyNameGetter
                            .invoke(constraintViolation.getConstraintDescriptor().getAnnotation());
                } catch (Exception e) {
                    propertyName = constraintViolation.getPropertyPath().toString();
                }
                if (StringUtils.isNotBlank(propertyName)) {
                    ExtendedPropertyDefinition propertyDefinition = node
                            .getApplicablePropertyDefinition(propertyName);
                    if (propertyDefinition == null) {
                        propertyDefinition = node
                                .getApplicablePropertyDefinition(propertyName.replaceFirst("_", ":"));
                    }
                    if (propertyDefinition != null) {
                        Locale errorLocale = null;
                        if (propertyDefinition.isInternationalized()) {
                            errorLocale = getLocale();
                            if (errorLocale == null) {
                                continue;
                            }
                        }
                        ccve = addError(ccve, new PropertyConstraintViolationException(node,
                                constraintViolation.getMessage(), errorLocale, propertyDefinition));
                    } else {
                        ccve = addError(ccve,
                                new NodeConstraintViolationException(node, constraintViolation.getMessage(), null));
                    }
                } else {
                    ccve = addError(ccve,
                            new NodeConstraintViolationException(node, constraintViolation.getMessage(), null));
                }
            }
        }

        return ccve;
    }

    private CompositeConstraintViolationException addError(CompositeConstraintViolationException ccve,
            ConstraintViolationException exception) {
        if (ccve == null) {
            ccve = new CompositeConstraintViolationException();
        }
        ccve.addException(exception);
        return ccve;
    }

    public void refresh(boolean b) throws RepositoryException {
        for (Session session : sessions.values()) {
            session.refresh(b);
        }
        if (!b) {
            newNodes.clear();
            changedNodes.clear();
            flushCaches();
        }
    }

    public boolean hasPendingChanges() throws RepositoryException {
        for (Session session : sessions.values()) {
            if (session.hasPendingChanges()) {
                return true;
            }
        }
        return false;
    }

    public ValueFactory getValueFactory() {
        return JCRValueFactoryImpl.getInstance();
    }

    /**
     * Normally determines whether this <code>Session</code> has permission to perform
     * the specified actions at the specified <code>absPath</code>.
     * This method is not supported.
     *
     * @param absPath an absolute path.
     * @param actions a comma separated list of action strings.
     * @throws UnsupportedRepositoryOperationException as long as Jahia doesn't support it
     */
    public void checkPermission(String absPath, String actions) throws AccessControlException, RepositoryException {
        throw new UnsupportedRepositoryOperationException();
    }

    public ContentHandler getImportContentHandler(String s, int i) throws PathNotFoundException,
            ConstraintViolationException, VersionException, LockException, RepositoryException {
        throw new UnsupportedRepositoryOperationException();
    }

    public void importXML(String path, InputStream inputStream, int uuidBehavior)
            throws IOException, PathNotFoundException, ItemExistsException, ConstraintViolationException,
            VersionException, InvalidSerializedDataException, LockException, RepositoryException {
        importXML(path, inputStream, uuidBehavior, DocumentViewImportHandler.ROOT_BEHAVIOUR_REPLACE);
    }

    public void importXML(String path, InputStream inputStream, int uuidBehavior, int rootBehavior)
            throws IOException, InvalidSerializedDataException, RepositoryException {
        Map<String, List<String>> references = new HashMap<String, List<String>>();
        importXML(path, inputStream, uuidBehavior, rootBehavior, null, references);
        ReferencesHelper.resolveCrossReferences(this, references);
    }

    public void importXML(String path, InputStream inputStream, int uuidBehavior, int rootBehavior,
            Map<String, String> replacements, Map<String, List<String>> references)
            throws IOException, InvalidSerializedDataException, RepositoryException {
        JCRNodeWrapper node = getNode(path);
        try {
            if (!node.isCheckedOut()) {
                checkout(node);
            }
        } catch (UnsupportedRepositoryOperationException ex) {
            // versioning not supported
        }

        DocumentViewImportHandler documentViewImportHandler = new DocumentViewImportHandler(this, path);
        documentViewImportHandler.setRootBehavior(rootBehavior);
        documentViewImportHandler.setUuidBehavior(uuidBehavior);
        documentViewImportHandler.setReplacements(replacements);
        if (references != null) {
            documentViewImportHandler.setReferences(references);
        }
        try {
            SAXParser parser = JahiaSAXParserFactory.newInstance().newSAXParser();

            parser.parse(inputStream, documentViewImportHandler);
        } catch (SAXParseException e) {
            logger.error("Cannot import - File contains invalid XML", e);
            throw new RuntimeException("Cannot import file because it contains invalid XML", e);
        } catch (Exception e) {
            logger.error("Cannot import", e);
            throw new RuntimeException("Cannot import file", e);
        }
    }

    /**
     * Applies the namespace prefix to the appropriate sessions, including the underlying provider sessions.
     *
     * @param prefix
     * @param uri
     * @throws NamespaceException
     * @throws RepositoryException
     */
    public void setNamespacePrefix(String prefix, String uri) throws NamespaceException, RepositoryException {
        nsToPrefix.put(uri, prefix);
        prefixToNs.put(prefix, uri);
        for (Session s : sessions.values()) {
            s.setNamespacePrefix(prefix, uri);
            try {
                NamespaceRegistry nsReg = s.getWorkspace().getNamespaceRegistry();
                if (nsReg != null) {
                    nsReg.registerNamespace(prefix, uri);
                }
            } catch (RepositoryException e) {
                if (logger.isDebugEnabled()) {
                    logger.debug(
                            "Prefix/uri could not be registered in workspace's registry- " + prefix + "/" + uri, e);
                }
            }
        }
    }

    public String[] getNamespacePrefixes() throws RepositoryException {
        Set<String> wsPrefixes = new HashSet<String>(
                Arrays.asList(getWorkspace().getNamespaceRegistry().getPrefixes()));
        wsPrefixes.addAll(prefixToNs.keySet());
        return wsPrefixes.toArray(new String[wsPrefixes.size()]);
    }

    public String getNamespaceURI(String prefix) throws NamespaceException, RepositoryException {
        if (prefixToNs.containsKey(prefix)) {
            return prefixToNs.get(prefix);
        }
        return getWorkspace().getNamespaceRegistry().getURI(prefix);
    }

    public String getNamespacePrefix(String uri) throws NamespaceException, RepositoryException {
        if (nsToPrefix.containsKey(uri)) {
            return nsToPrefix.get(uri);
        }
        return getWorkspace().getNamespaceRegistry().getPrefix(uri);
    }

    public void logout() {
        for (Session session : sessions.values()) {
            if (session.isLive()) {
                session.logout();
            }
        }
        sessions.clear();
        if (credentials instanceof SimpleCredentials) {
            SimpleCredentials simpleCredentials = (SimpleCredentials) credentials;
            JahiaLoginModule.removeToken(simpleCredentials.getUserID(),
                    new String(simpleCredentials.getPassword()));
        }
        isLive = false;
        if (activeSessionsObjects.remove(uuid) == null) {
            logger.error("Could not removed session " + this + " opened here \n", thisSessionTrace);
        }
        if (!isSystem) {
            long actives = activeSessions.decrementAndGet();
            if (logger.isDebugEnabled() && actives < activeSessionsObjects.size()) {
                Map<UUID, JCRSessionWrapper> copyActives = new HashMap<UUID, JCRSessionWrapper>(
                        activeSessionsObjects);
                logger.debug("There is " + actives + " sessions but " + copyActives.size() + " is retained");
                for (Map.Entry<UUID, JCRSessionWrapper> entry : copyActives.entrySet()) {
                    logger.debug("Active Session " + entry.getKey() + " is"
                            + (entry.getValue().isLive() ? "" : " not") + " live",
                            entry.getValue().getSessionTrace());
                }
            }
        }
    }

    public boolean isLive() {
        return isLive;
    }

    /**
     * Adds the specified lock token to the wrapped sessions. Holding a
     * lock token makes the <code>Session</code> the owner of the lock
     * specified by that particular lock token.
     *
     * @param token a lock token (a string).
     * @deprecated As of JCR 2.0, {@link LockManager#addLockToken(String)}
     * should be used instead.
     */
    public void addLockToken(String token) {
        tokens.add(token);
        for (Session session : sessions.values()) {
            session.addLockToken(token);
        }
    }

    public String[] getLockTokens() {
        List<String> allTokens = new ArrayList<String>(tokens);
        for (Session session : sessions.values()) {
            String[] tokens = session.getLockTokens();
            for (String token : tokens) {
                if (!allTokens.contains(token)) {
                    allTokens.add(token);
                }
            }
        }
        return allTokens.toArray(new String[allTokens.size()]);
    }

    public void removeLockToken(String token) {
        tokens.remove(token);
        for (Session session : sessions.values()) {
            session.removeLockToken(token);
        }
    }

    /**
     * Get sessions from all providers used in this wrapper.
     *
     * @return a <code>Collection</code> of <code>JCRSessionWrapper</code> objects
     */
    public Collection<Session> getAllSessions() {
        return sessions.values();
    }

    public Session getProviderSession(JCRStoreProvider provider) throws RepositoryException {
        return getProviderSession(provider, true);
    }

    public Session getProviderSession(JCRStoreProvider provider, boolean create) throws RepositoryException {
        if (sessions.get(provider) != null && !sessions.get(provider).isLive()) {
            sessions.remove(provider);
        }
        if (sessions.get(provider) == null && create) {
            Session s = null;

            if (credentials instanceof SimpleCredentials) {
                SimpleCredentials simpleCredentials = (SimpleCredentials) credentials;
                JahiaLoginModule.Token t = JahiaLoginModule.getToken(simpleCredentials.getUserID(),
                        new String(simpleCredentials.getPassword()));
                JahiaUser user = getUser();
                String username;
                if (JahiaUserManagerService.isGuest(user)) {
                    username = JahiaLoginModule.GUEST;
                } else {
                    username = user.getUsername();
                }
                if (isCurrentUserSession() && !simpleCredentials.getUserID().startsWith(JahiaLoginModule.SYSTEM)) {
                    s = provider.getSessionFactory().findSameSession(provider, username, workspace.getName());
                }
                if (s == null) {
                    s = provider.getSession(credentials, workspace.getName());
                }
                JahiaLoginModule.removeToken(simpleCredentials.getUserID(),
                        new String(simpleCredentials.getPassword()));
                credentials = JahiaLoginModule.getCredentials(simpleCredentials.getUserID(),
                        (String) simpleCredentials.getAttribute(JahiaLoginModule.REALM_ATTRIBUTE),
                        t != null ? t.deniedPath : null);
            } else {
                s = provider.getSession(credentials, workspace.getName());
            }

            sessions.put(provider, s);
            for (String token : tokens) {
                s.addLockToken(token);
            }

            for (String prefix : prefixToNs.keySet()) {
                s.setNamespacePrefix(prefix, prefixToNs.get(prefix));
            }
        }
        return sessions.get(provider);
    }

    public JahiaUser getUser() {
        return user;
    }

    public JahiaUser getAliasedUser() {
        return sessionFactory.getCurrentAliasedUser();
    }

    public Calendar getPreviewDate() {
        return sessionFactory.getCurrentPreviewDate();
    }

    /**
     * Generates a document view export using a {@link org.apache.jackrabbit.commons.xml.DocumentViewExporter}
     * instance.
     *
     * @param path       of the node to be exported
     * @param handler    handler for the SAX events of the export
     * @param skipBinary whether binary values should be skipped
     * @param noRecurse  whether to export just the identified node
     * @throws PathNotFoundException if a node at the given path does not exist
     * @throws SAXException          if the SAX event handler failed
     * @throws RepositoryException   if another error occurs
     */
    public void exportDocumentView(String path, ContentHandler handler, boolean skipBinary, boolean noRecurse)
            throws PathNotFoundException, SAXException, RepositoryException {
        DocumentViewExporter exporter = new DocumentViewExporter(this, handler, skipBinary, noRecurse);
        Item item = getItem(path);
        if (item.isNode()) {
            exporter.export((JCRNodeWrapper) item);
        } else {
            throw new PathNotFoundException("XML export is not defined for properties: " + path);
        }
    }

    /**
     * Generates a system view export using a {@link org.apache.jackrabbit.commons.xml.SystemViewExporter}
     * instance.
     *
     * @param path       of the node to be exported
     * @param handler    handler for the SAX events of the export
     * @param skipBinary whether binary values should be skipped
     * @param noRecurse  whether to export just the identified node
     * @throws PathNotFoundException if a node at the given path does not exist
     * @throws SAXException          if the SAX event handler failed
     * @throws RepositoryException   if another error occurs
     */
    public void exportSystemView(String path, ContentHandler handler, boolean skipBinary, boolean noRecurse)
            throws PathNotFoundException, SAXException, RepositoryException {

        //todo implement our own system view .. ?
        SystemViewExporter exporter = new SystemViewExporter(this, handler, !noRecurse, !skipBinary);
        Item item = getItem(path);
        if (item.isNode()) {
            exporter.export((JCRNodeWrapper) item);
        } else {
            throw new PathNotFoundException("XML export is not defined for properties: " + path);
        }
    }

    /**
     * Calls {@link Session#exportDocumentView(String, ContentHandler, boolean, boolean)}
     * with the given arguments and a {@link ContentHandler} that serializes
     * SAX events to the given output stream.
     *
     * @param absPath    passed through
     * @param out        output stream to which the SAX events are serialized
     * @param skipBinary passed through
     * @param noRecurse  passed through
     * @throws IOException         if the SAX serialization failed
     * @throws RepositoryException if another error occurs
     */
    public void exportDocumentView(String absPath, OutputStream out, boolean skipBinary, boolean noRecurse)
            throws IOException, RepositoryException {
        try {
            ContentHandler handler = getExportContentHandler(out);
            exportDocumentView(absPath, handler, skipBinary, noRecurse);
        } catch (SAXException e) {
            Exception exception = e.getException();
            if (exception instanceof RepositoryException) {
                throw (RepositoryException) exception;
            } else if (exception instanceof IOException) {
                throw (IOException) exception;
            } else {
                throw new RepositoryException("Error serializing document view XML", e);
            }
        }
    }

    /**
     * Calls {@link Session#exportSystemView(String, ContentHandler, boolean, boolean)}
     * with the given arguments and a {@link ContentHandler} that serializes
     * SAX events to the given output stream.
     *
     * @param absPath    passed through
     * @param out        output stream to which the SAX events are serialized
     * @param skipBinary passed through
     * @param noRecurse  passed through
     * @throws IOException         if the SAX serialization failed
     * @throws RepositoryException if another error occurs
     */
    public void exportSystemView(String absPath, OutputStream out, boolean skipBinary, boolean noRecurse)
            throws IOException, RepositoryException {
        try {
            ContentHandler handler = getExportContentHandler(out);
            exportSystemView(absPath, handler, skipBinary, noRecurse);
        } catch (SAXException e) {
            Exception exception = e.getException();
            if (exception instanceof RepositoryException) {
                throw (RepositoryException) exception;
            } else if (exception instanceof IOException) {
                throw (IOException) exception;
            } else {
                throw new RepositoryException("Error serializing system view XML", e);
            }
        }
    }

    /**
     * Creates a {@link ContentHandler} instance that serializes the
     * received SAX events to the given output stream.
     *
     * @param stream output stream to which the SAX events are serialized
     * @return SAX content handler
     * @throws RepositoryException if an error occurs
     */
    private ContentHandler getExportContentHandler(OutputStream stream) throws RepositoryException {
        try {
            SAXTransformerFactory stf = (SAXTransformerFactory) SAXTransformerFactory.newInstance();
            TransformerHandler handler = stf.newTransformerHandler();

            Transformer transformer = handler.getTransformer();
            transformer.setOutputProperty(OutputKeys.METHOD, "xml");
            transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
            transformer.setOutputProperty(OutputKeys.INDENT, "no");

            handler.setResult(new StreamResult(stream));
            return handler;
        } catch (TransformerFactoryConfigurationError e) {
            throw new RepositoryException("SAX transformer implementation not available", e);
        } catch (TransformerException e) {
            throw new RepositoryException("Error creating an XML export content handler", e);
        }
    }

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

    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 {
        JCRItemWrapper item = getItem(absPath);
        boolean flushNeeded = false;
        if (item.isNode()) {
            JCRNodeWrapper node = (JCRNodeWrapper) item;
            unregisterNewNode(node);
            if (node.hasNodes()) {
                flushNeeded = true;
            }
        }
        item.remove();
        if (flushNeeded) {
            flushCaches();
        } else {
            removeFromCache(item);
        }
    }

    void removeFromCache(JCRItemWrapper item) throws RepositoryException {
        sessionCacheByPath.remove(item.getPath());
        if (item instanceof JCRNodeWrapper) {
            sessionCacheByIdentifier.remove(((JCRNodeWrapper) item).getIdentifier());
        }
    }

    void removeFromCache(String path) throws RepositoryException {
        JCRNodeWrapper node = sessionCacheByPath.remove(path);
        if (node != null) {
            sessionCacheByIdentifier.remove(node.getIdentifier());
        }
    }

    public boolean hasPermission(String absPath, String actions) throws RepositoryException {
        throw new UnsupportedRepositoryOperationException();
    }

    public boolean hasCapability(String s, Object o, Object[] objects) throws RepositoryException {
        throw new UnsupportedRepositoryOperationException();
    }

    /**
     * Returns the access control manager for this <code>Session</code>.
     * <p/>
     * Jahia throws an <code>UnsupportedRepositoryOperationException</code>.
     *
     * @return the access control manager for this <code>Session</code>
     * @throws UnsupportedRepositoryOperationException if access control
     *                                                 is not supported.
     * @since JCR 2.0
     */
    public AccessControlManager getAccessControlManager()
            throws UnsupportedRepositoryOperationException, RepositoryException {
        throw new UnsupportedRepositoryOperationException();
    }

    public RetentionManager getRetentionManager()
            throws UnsupportedRepositoryOperationException, RepositoryException {
        throw new UnsupportedRepositoryOperationException();
    }

    /**
     * Performs check out of the specified node.
     *
     * @param node the node to perform the check out
     * @see VersionManager#checkout(String) for details
     */
    public void checkout(Node node)
            throws UnsupportedRepositoryOperationException, LockException, RepositoryException {
        while (!node.isCheckedOut()) {
            if (!node.isNodeType(Constants.MIX_VERSIONABLE) && !node.isNodeType(Constants.MIX_SIMPLEVERSIONABLE)) {
                node = node.getParent();
            } else {
                String absPath = node.getPath();
                VersionManager versionManager = getWorkspace().getVersionManager();
                if (!versionManager.isCheckedOut(absPath)) {
                    versionManager.checkout(absPath);
                }
                return;
            }
        }
    }

    public Map<String, String> getUuidMapping() {
        return uuidMapping;
    }

    public Map<String, String> getPathMapping() {
        return pathMapping;
    }

    public Map<String, Object> getResolvedReferences() {
        return resolvedReferences;
    }

    public Locale getFallbackLocale() {
        return fallbackLocale;
    }

    public void setFallbackLocale(Locale fallbackLocale) {
        this.fallbackLocale = fallbackLocale;
    }

    public Date getVersionDate() {
        return versionDate;
    }

    public String getVersionLabel() {
        return versionLabel;
    }

    public void setVersionDate(Date versionDate) {
        if (this.versionDate == null) {
            this.versionDate = versionDate;
        } else {
            throw new RuntimeException("Should not change versionDate on a session in same thread");
        }
    }

    public void setVersionLabel(String versionLabel) {
        if (this.versionLabel == null) {
            if (versionLabel != null && !versionLabel.startsWith(getWorkspace().getName())) {
                throw new RuntimeException(
                        "Cannot use label " + versionLabel + " in workspace " + getWorkspace().getName());
            }
            this.versionLabel = versionLabel;
        } else {
            throw new RuntimeException("Should not change versionLabel on a session in same thread");
        }
    }

    /**
     * Returns the wrapper node which corresponds to the version specified in the current session. If the corresponding version cannot be
     * found for the node a {@link PathNotFoundException} is thrown.
     *
     * @param objectNode the source object to check version node for
     * @param provider   the node provider
     * @return the wrapper node which corresponds to the version specified in the current session
     * @throws RepositoryException in case of a repository operation error
     */
    public JCRNodeWrapper getFrozenVersionAsRegular(Node objectNode, JCRStoreProvider provider)
            throws RepositoryException {
        return getFrozenVersionAsRegular(objectNode, provider, true);
    }

    /**
     * Returns the wrapper node which corresponds to the version specified in the current session. If the corresponding version cannot be
     * found for the node a <b>null</b> is returned in case <code>throwExeptionIfNotFound</code> is set to false. If version is not found
     * and <code>throwExeptionIfNotFound</code> is set to true - throws a {@link PathNotFoundException}.
     *
     * @param objectNode              the source object to check version node for
     * @param provider                the node provider
     * @param throwExeptionIfNotFound if <code>true</code> a {@link PathNotFoundException} is thrown in case the corresponding version cannot be found
     * @return the wrapper node which corresponds to the version specified in the current session
     * @throws RepositoryException in case of a repository operation error
     */
    protected JCRNodeWrapper getFrozenVersionAsRegular(Node objectNode, JCRStoreProvider provider,
            boolean throwExeptionIfNotFound) throws RepositoryException {
        try {
            VersionHistory vh = objectNode.getSession().getWorkspace().getVersionManager()
                    .getVersionHistory(objectNode.getPath());

            Version v = null;
            if (versionLabel != null) {
                v = JCRVersionService.findVersionByLabel(vh, versionLabel);
            }
            if (v == null && versionDate != null) {
                v = JCRVersionService.findClosestVersion(vh, versionDate);
            }

            if (v == null) {
                if (throwExeptionIfNotFound) {
                    throw new PathNotFoundException();
                } else {
                    return null;
                }
            }

            Node frozen = v.getNode(Constants.JCR_FROZENNODE);

            return provider.getNodeWrapper(frozen, this);
        } catch (UnsupportedRepositoryOperationException e) {
            if (getVersionDate() == null && getVersionLabel() == null) {
                logger.error("Error while retrieving frozen version", e);
            }
        }
        return null;
    }

    public static long getActiveSessions() {
        return activeSessions.get();
    }

    public static Map<UUID, JCRSessionWrapper> getActiveSessionsObjects() {
        return Collections.unmodifiableMap(activeSessionsObjects);
    }

    protected void flushCaches() {
        sessionCacheByIdentifier.clear();
        sessionCacheByPath.clear();
    }

    protected JCRNodeWrapper getCachedNode(String uuid) {
        return sessionCacheByIdentifier.get(uuid);
    }

    public boolean isCurrentUserSession() {
        return isCurrentUserSession;
    }

    public void setCurrentUserSession(boolean isCurrentUserSession) {
        this.isCurrentUserSession = isCurrentUserSession;
    }

    public Exception getSessionTrace() {
        return thisSessionTrace;
    }

    /**
     * Get weak references of a node
     *
     * @param node         node
     * @param propertyName name of the property
     * @return an iterator
     * @throws RepositoryException
     */
    public PropertyIterator getWeakReferences(JCRNodeWrapper node, String propertyName) throws RepositoryException {
        List<PropertyIterator> propertyIterators = new ArrayList<PropertyIterator>();
        for (JCRStoreProvider provider : sessionFactory.getProviderList()) {
            Session providerSession = getProviderSession(provider);
            PropertyIterator pi = provider.getWeakReferences(node, propertyName, providerSession);
            if (pi != null) {
                propertyIterators.add(new PropertyIteratorImpl(pi, this, provider));
            }
        }
        return new MultiplePropertyIterator(propertyIterators, -1);
    }

    @Override
    public String toString() {
        return "JCRSessionWrapper (" + workspace.getName() + ", " + locale + ", " + user + " [aliased as "
                + getAliasedUser() + "]) {sessions=" + sessions + '}';
    }

    public JCRUserNode getUserNode() throws RepositoryException {
        return (JCRUserNode) getNode(user.getLocalPath());
    }
}