org.apache.jackrabbit.spi2dav.RepositoryServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.jackrabbit.spi2dav.RepositoryServiceImpl.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.jackrabbit.spi2dav;

import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HostConfiguration;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpConnectionManager;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.URI;
import org.apache.commons.httpclient.URIException;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.InputStreamRequestEntity;
import org.apache.commons.httpclient.methods.RequestEntity;
import org.apache.commons.httpclient.methods.StringRequestEntity;
import org.apache.jackrabbit.spi.Batch;
import org.apache.jackrabbit.spi.ChildInfo;
import org.apache.jackrabbit.spi.Event;
import org.apache.jackrabbit.spi.EventBundle;
import org.apache.jackrabbit.spi.EventFilter;
import org.apache.jackrabbit.spi.IdFactory;
import org.apache.jackrabbit.spi.ItemId;
import org.apache.jackrabbit.spi.LockInfo;
import org.apache.jackrabbit.spi.Name;
import org.apache.jackrabbit.spi.NameFactory;
import org.apache.jackrabbit.spi.NodeId;
import org.apache.jackrabbit.spi.NodeInfo;
import org.apache.jackrabbit.spi.Path;
import org.apache.jackrabbit.spi.PathFactory;
import org.apache.jackrabbit.spi.PropertyId;
import org.apache.jackrabbit.spi.PropertyInfo;
import org.apache.jackrabbit.spi.QItemDefinition;
import org.apache.jackrabbit.spi.QNodeDefinition;
import org.apache.jackrabbit.spi.QNodeTypeDefinition;
import org.apache.jackrabbit.spi.QPropertyDefinition;
import org.apache.jackrabbit.spi.QValue;
import org.apache.jackrabbit.spi.QValueFactory;
import org.apache.jackrabbit.spi.QueryInfo;
import org.apache.jackrabbit.spi.RepositoryService;
import org.apache.jackrabbit.spi.SessionInfo;
import org.apache.jackrabbit.spi.Subscription;
import org.apache.jackrabbit.spi.commons.ChildInfoImpl;
import org.apache.jackrabbit.spi.commons.EventBundleImpl;
import org.apache.jackrabbit.spi.commons.EventFilterImpl;
import org.apache.jackrabbit.spi.commons.conversion.IllegalNameException;
import org.apache.jackrabbit.spi.commons.conversion.MalformedPathException;
import org.apache.jackrabbit.spi.commons.conversion.NameException;
import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
import org.apache.jackrabbit.spi.commons.conversion.NameResolver;
import org.apache.jackrabbit.spi.commons.conversion.ParsingNameResolver;
import org.apache.jackrabbit.spi.commons.conversion.ParsingPathResolver;
import org.apache.jackrabbit.spi.commons.conversion.PathResolver;
import org.apache.jackrabbit.spi.commons.conversion.IdentifierResolver;
import org.apache.jackrabbit.spi.commons.name.NameConstants;
import org.apache.jackrabbit.spi.commons.namespace.AbstractNamespaceResolver;
import org.apache.jackrabbit.spi.commons.namespace.NamespaceResolver;
import org.apache.jackrabbit.spi.commons.value.QValueValue;
import org.apache.jackrabbit.spi.commons.value.ValueFactoryQImpl;
import org.apache.jackrabbit.spi.commons.value.ValueFormat;
import org.apache.jackrabbit.util.Text;
import org.apache.jackrabbit.uuid.UUID;
import org.apache.jackrabbit.webdav.DavConstants;
import org.apache.jackrabbit.webdav.DavException;
import org.apache.jackrabbit.webdav.DavMethods;
import org.apache.jackrabbit.webdav.DavServletResponse;
import org.apache.jackrabbit.webdav.MultiStatus;
import org.apache.jackrabbit.webdav.MultiStatusResponse;
import org.apache.jackrabbit.webdav.client.methods.CheckinMethod;
import org.apache.jackrabbit.webdav.client.methods.CheckoutMethod;
import org.apache.jackrabbit.webdav.client.methods.CopyMethod;
import org.apache.jackrabbit.webdav.client.methods.DavMethod;
import org.apache.jackrabbit.webdav.client.methods.DavMethodBase;
import org.apache.jackrabbit.webdav.client.methods.DeleteMethod;
import org.apache.jackrabbit.webdav.client.methods.LabelMethod;
import org.apache.jackrabbit.webdav.client.methods.LockMethod;
import org.apache.jackrabbit.webdav.client.methods.MergeMethod;
import org.apache.jackrabbit.webdav.client.methods.MkColMethod;
import org.apache.jackrabbit.webdav.client.methods.MoveMethod;
import org.apache.jackrabbit.webdav.client.methods.OptionsMethod;
import org.apache.jackrabbit.webdav.client.methods.OrderPatchMethod;
import org.apache.jackrabbit.webdav.client.methods.PollMethod;
import org.apache.jackrabbit.webdav.client.methods.PropFindMethod;
import org.apache.jackrabbit.webdav.client.methods.PropPatchMethod;
import org.apache.jackrabbit.webdav.client.methods.PutMethod;
import org.apache.jackrabbit.webdav.client.methods.ReportMethod;
import org.apache.jackrabbit.webdav.client.methods.SearchMethod;
import org.apache.jackrabbit.webdav.client.methods.SubscribeMethod;
import org.apache.jackrabbit.webdav.client.methods.UnLockMethod;
import org.apache.jackrabbit.webdav.client.methods.UnSubscribeMethod;
import org.apache.jackrabbit.webdav.client.methods.UpdateMethod;
import org.apache.jackrabbit.webdav.header.CodedUrlHeader;
import org.apache.jackrabbit.webdav.header.IfHeader;
import org.apache.jackrabbit.webdav.jcr.ItemResourceConstants;
import org.apache.jackrabbit.webdav.jcr.JcrValueType;
import org.apache.jackrabbit.webdav.jcr.nodetype.NodeTypeConstants;
import org.apache.jackrabbit.webdav.jcr.nodetype.NodeTypeProperty;
import org.apache.jackrabbit.webdav.jcr.observation.SubscriptionImpl;
import org.apache.jackrabbit.webdav.jcr.property.NamespacesProperty;
import org.apache.jackrabbit.webdav.jcr.property.ValuesProperty;
import org.apache.jackrabbit.webdav.jcr.version.report.JcrPrivilegeReport;
import org.apache.jackrabbit.webdav.jcr.version.report.NodeTypesReport;
import org.apache.jackrabbit.webdav.jcr.version.report.RegisteredNamespacesReport;
import org.apache.jackrabbit.webdav.jcr.version.report.RepositoryDescriptorsReport;
import org.apache.jackrabbit.webdav.lock.ActiveLock;
import org.apache.jackrabbit.webdav.lock.LockDiscovery;
import org.apache.jackrabbit.webdav.lock.Scope;
import org.apache.jackrabbit.webdav.lock.Type;
import org.apache.jackrabbit.webdav.observation.DefaultEventType;
import org.apache.jackrabbit.webdav.observation.EventDiscovery;
import org.apache.jackrabbit.webdav.observation.EventType;
import org.apache.jackrabbit.webdav.observation.ObservationConstants;
import org.apache.jackrabbit.webdav.observation.SubscriptionInfo;
import org.apache.jackrabbit.webdav.ordering.OrderingConstants;
import org.apache.jackrabbit.webdav.property.DavProperty;
import org.apache.jackrabbit.webdav.property.DavPropertyName;
import org.apache.jackrabbit.webdav.property.DavPropertyNameSet;
import org.apache.jackrabbit.webdav.property.DavPropertySet;
import org.apache.jackrabbit.webdav.property.DefaultDavProperty;
import org.apache.jackrabbit.webdav.property.HrefProperty;
import org.apache.jackrabbit.webdav.search.SearchConstants;
import org.apache.jackrabbit.webdav.search.SearchInfo;
import org.apache.jackrabbit.webdav.security.CurrentUserPrivilegeSetProperty;
import org.apache.jackrabbit.webdav.security.Privilege;
import org.apache.jackrabbit.webdav.security.SecurityConstants;
import org.apache.jackrabbit.webdav.transaction.TransactionConstants;
import org.apache.jackrabbit.webdav.transaction.TransactionInfo;
import org.apache.jackrabbit.webdav.version.DeltaVConstants;
import org.apache.jackrabbit.webdav.version.LabelInfo;
import org.apache.jackrabbit.webdav.version.MergeInfo;
import org.apache.jackrabbit.webdav.version.UpdateInfo;
import org.apache.jackrabbit.webdav.version.VersionControlledResource;
import org.apache.jackrabbit.webdav.version.report.ReportInfo;
import org.apache.jackrabbit.webdav.xml.DomUtil;
import org.apache.jackrabbit.webdav.xml.ElementIterator;
import org.apache.jackrabbit.webdav.xml.Namespace;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;

import javax.jcr.AccessDeniedException;
import javax.jcr.Credentials;
import javax.jcr.InvalidItemStateException;
import javax.jcr.ItemNotFoundException;
import javax.jcr.LoginException;
import javax.jcr.MergeException;
import javax.jcr.NamespaceException;
import javax.jcr.NoSuchWorkspaceException;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.UnsupportedRepositoryOperationException;
import javax.jcr.Value;
import javax.jcr.ValueFactory;
import javax.jcr.ValueFormatException;
import javax.jcr.lock.LockException;
import javax.jcr.nodetype.ConstraintViolationException;
import javax.jcr.nodetype.NoSuchNodeTypeException;
import javax.jcr.nodetype.InvalidNodeTypeDefinitionException;
import javax.jcr.nodetype.NodeTypeExistsException;
import javax.jcr.query.InvalidQueryException;
import javax.jcr.version.VersionException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * <code>RepositoryServiceImpl</code>...
 */
// TODO: encapsulate URI building, escaping, unescaping...
// TODO: TO-BE-FIXED. caches don't get adjusted upon removal/move of items
public class RepositoryServiceImpl implements RepositoryService, DavConstants {

    private static Logger log = LoggerFactory.getLogger(RepositoryServiceImpl.class);

    private static final EventType[] ALL_EVENTS = new EventType[7];
    static {
        ALL_EVENTS[0] = SubscriptionImpl.getEventType(javax.jcr.observation.Event.NODE_ADDED);
        ALL_EVENTS[1] = SubscriptionImpl.getEventType(javax.jcr.observation.Event.NODE_REMOVED);
        ALL_EVENTS[2] = SubscriptionImpl.getEventType(javax.jcr.observation.Event.PROPERTY_ADDED);
        ALL_EVENTS[3] = SubscriptionImpl.getEventType(javax.jcr.observation.Event.PROPERTY_CHANGED);
        ALL_EVENTS[4] = SubscriptionImpl.getEventType(javax.jcr.observation.Event.PROPERTY_REMOVED);
        ALL_EVENTS[5] = SubscriptionImpl.getEventType(javax.jcr.observation.Event.NODE_MOVED);
        ALL_EVENTS[6] = SubscriptionImpl.getEventType(javax.jcr.observation.Event.PERSIST);
    }
    private static final SubscriptionInfo S_INFO = new SubscriptionInfo(ALL_EVENTS, true, INFINITE_TIMEOUT);

    private final IdFactory idFactory;
    private final NameFactory nameFactory;
    private final PathFactory pathFactory;
    private final QValueFactory qValueFactory;
    private final ValueFactory valueFactory;

    private final Document domFactory;
    private final NamespaceCache nsCache;
    private final URIResolverImpl uriResolver;

    private final HostConfiguration hostConfig;
    private final HashMap clients = new HashMap();
    private final HttpConnectionManager connectionManager;

    private final Map nodeTypeDefinitions = new HashMap();

    private Map descriptors;

    public RepositoryServiceImpl(String uri, IdFactory idFactory, NameFactory nameFactory, PathFactory pathFactory,
            QValueFactory qValueFactory) throws RepositoryException {
        if (uri == null || "".equals(uri)) {
            throw new RepositoryException("Invalid repository uri '" + uri + "'.");
        }

        if (idFactory == null || qValueFactory == null) {
            throw new RepositoryException("IdFactory and QValueFactory may not be null.");
        }
        this.idFactory = idFactory;
        this.nameFactory = nameFactory;
        this.pathFactory = pathFactory;
        this.qValueFactory = qValueFactory;

        try {
            domFactory = DomUtil.BUILDER_FACTORY.newDocumentBuilder().newDocument();
        } catch (ParserConfigurationException e) {
            throw new RepositoryException(e);
        }

        try {
            URI repositoryUri = new URI((uri.endsWith("/")) ? uri : uri + "/", true);
            hostConfig = new HostConfiguration();
            hostConfig.setHost(repositoryUri);

            nsCache = new NamespaceCache();
            uriResolver = new URIResolverImpl(repositoryUri, this, domFactory);
            NamePathResolver resolver = new NamePathResolverImpl(nsCache);
            valueFactory = new ValueFactoryQImpl(qValueFactory, resolver);

        } catch (URIException e) {
            throw new RepositoryException(e);
        }
        connectionManager = new MultiThreadedHttpConnectionManager();
    }

    private static void checkSessionInfo(SessionInfo sessionInfo) throws RepositoryException {
        if (!(sessionInfo instanceof SessionInfoImpl)) {
            throw new RepositoryException("Unknown SessionInfo implementation.");
        }
    }

    private static void checkSubscription(Subscription subscription) throws RepositoryException {
        if (!(subscription instanceof EventSubscriptionImpl)) {
            throw new RepositoryException("Unknown Subscription implementation.");
        }
    }

    private static boolean isUnLockMethod(DavMethod method) {
        int code = DavMethods.getMethodCode(method.getName());
        return DavMethods.DAV_UNLOCK == code;
    }

    private static void initMethod(DavMethod method, SessionInfo sessionInfo, boolean addIfHeader)
            throws RepositoryException {
        if (addIfHeader) {
            checkSessionInfo(sessionInfo);
            String[] locktokens = ((SessionInfoImpl) sessionInfo).getAllLockTokens();
            // TODO: ev. build tagged if header
            if (locktokens != null && locktokens.length > 0) {
                IfHeader ifH = new IfHeader(locktokens);
                method.setRequestHeader(ifH.getHeaderName(), ifH.getHeaderValue());
            }
        }
    }

    private static void initMethod(DavMethod method, BatchImpl batchImpl, boolean addIfHeader)
            throws RepositoryException {
        initMethod(method, batchImpl.sessionInfo, addIfHeader);

        // add batchId as separate header
        CodedUrlHeader ch = new CodedUrlHeader(TransactionConstants.HEADER_TRANSACTIONID, batchImpl.batchId);
        method.setRequestHeader(ch.getHeaderName(), ch.getHeaderValue());
    }

    private static boolean isSameResource(String requestURI, MultiStatusResponse response) {
        String href = response.getHref();
        if (href.endsWith("/") && !requestURI.endsWith("/")) {
            href = href.substring(0, href.length() - 1);
        }
        return requestURI.equals(href);
    }

    private String saveGetIdString(ItemId id, SessionInfo sessionInfo) {
        NamePathResolver resolver = null;
        try {
            resolver = getNamePathResolver(sessionInfo);
        } catch (RepositoryException e) {
            // ignore.
        }
        return saveGetIdString(id, resolver);
    }

    private String saveGetIdString(ItemId id, NamePathResolver resolver) {
        StringBuffer bf = new StringBuffer();
        String uid = id.getUniqueID();
        if (uid != null) {
            bf.append(uid);
        }
        Path p = id.getPath();
        if (p != null) {
            if (resolver == null) {
                bf.append(p.toString());
            } else {
                try {
                    bf.append(resolver.getJCRPath(p));
                } catch (NamespaceException e) {
                    bf.append(p.toString());
                }
            }
        }
        return bf.toString();
    }

    protected NamePathResolver getNamePathResolver(SessionInfo sessionInfo) throws RepositoryException {
        checkSessionInfo(sessionInfo);
        return getNamePathResolver(((SessionInfoImpl) sessionInfo));
    }

    private NamePathResolver getNamePathResolver(SessionInfoImpl sessionInfo) {
        NamePathResolver resolver = sessionInfo.getNamePathResolver();
        if (resolver == null) {
            resolver = new NamePathResolverImpl(sessionInfo);
            ((SessionInfoImpl) sessionInfo).setNamePathResolver(resolver);
        }
        return resolver;
    }

    protected HttpClient getClient(SessionInfo sessionInfo) throws RepositoryException {
        HttpClient client = (HttpClient) clients.get(sessionInfo);
        if (client == null) {
            client = new HttpClient(connectionManager);
            client.setHostConfiguration(hostConfig);
            // NOTE: null credentials only work if 'missing-auth-mapping' param is
            // set on the server
            org.apache.commons.httpclient.Credentials creds = null;
            if (sessionInfo != null) {
                checkSessionInfo(sessionInfo);
                creds = ((SessionInfoImpl) sessionInfo).getCredentials().getCredentials();
                // always send authentication not waiting for 401
                client.getParams().setAuthenticationPreemptive(true);
            }
            client.getState().setCredentials(AuthScope.ANY, creds);
            clients.put(sessionInfo, client);
            log.debug("Created Client " + client + " for SessionInfo " + sessionInfo);
        }
        return client;
    }

    private void removeClient(SessionInfo sessionInfo) {
        HttpClient cl = (HttpClient) clients.remove(sessionInfo);
        log.debug("Removed Client " + cl + " for SessionInfo " + sessionInfo);
    }

    protected String getItemUri(ItemId itemId, SessionInfo sessionInfo) throws RepositoryException {
        return uriResolver.getItemUri(itemId, sessionInfo.getWorkspaceName(), sessionInfo);
    }

    private String getItemUri(NodeId parentId, Name childName, SessionInfo sessionInfo) throws RepositoryException {
        String parentUri = uriResolver.getItemUri(parentId, sessionInfo.getWorkspaceName(), sessionInfo);
        NamePathResolver resolver = getNamePathResolver(sessionInfo);
        return parentUri + "/" + Text.escape(resolver.getJCRName(childName));
    }

    private NodeId getParentId(DavPropertySet propSet, SessionInfo sessionInfo) throws RepositoryException {
        NodeId parentId = null;
        if (propSet.contains(ItemResourceConstants.JCR_PARENT)) {
            HrefProperty parentProp = new HrefProperty(propSet.get(ItemResourceConstants.JCR_PARENT));
            String parentHref = parentProp.getHrefs().get(0).toString();
            if (parentHref != null && parentHref.length() > 0) {
                parentId = uriResolver.getNodeId(parentHref, sessionInfo);
            }
        }
        return parentId;
    }

    String getUniqueID(DavPropertySet propSet) {
        if (propSet.contains(ItemResourceConstants.JCR_UUID)) {
            return propSet.get(ItemResourceConstants.JCR_UUID).getValue().toString();
        } else {
            return null;
        }
    }

    Name getQName(DavPropertySet propSet, NamePathResolver resolver) throws RepositoryException {
        DavProperty nameProp = propSet.get(ItemResourceConstants.JCR_NAME);
        if (nameProp != null && nameProp.getValue() != null) {
            // not root node. Note that 'unespacing' is not required since
            // the jcr:name property does not provide the value in escaped form.
            String jcrName = nameProp.getValue().toString();
            try {
                return resolver.getQName(jcrName);
            } catch (NameException e) {
                throw new RepositoryException(e);
            }
        } else {
            return NameConstants.ROOT;
        }
    }

    int getIndex(DavPropertySet propSet) {
        int index = Path.INDEX_UNDEFINED;
        DavProperty indexProp = propSet.get(ItemResourceConstants.JCR_INDEX);
        if (indexProp != null && indexProp.getValue() != null) {
            index = Integer.parseInt(indexProp.getValue().toString());
        }
        return index;
    }

    //--------------------------------------------------------------------------
    /**
     * Execute a 'Workspace' operation.
     *
     * @param method
     * @param sessionInfo
     * @throws RepositoryException
     */
    private void execute(DavMethod method, SessionInfo sessionInfo) throws RepositoryException {
        try {
            initMethod(method, sessionInfo, !isUnLockMethod(method));

            getClient(sessionInfo).executeMethod(method);
            method.checkSuccess();

        } catch (IOException e) {
            throw new RepositoryException(e);
        } catch (DavException e) {
            throw ExceptionConverter.generate(e);
        } finally {
            if (method != null) {
                method.releaseConnection();
            }
        }
    }

    //--------------------------------------------------< RepositoryService >---
    /**
     * @see RepositoryService#getIdFactory()
     */
    public IdFactory getIdFactory() {
        return idFactory;
    }

    /**
     * @see RepositoryService#getNameFactory()
     */
    public NameFactory getNameFactory() {
        return nameFactory;
    }

    /**
     * @see RepositoryService#getPathFactory()
     */
    public PathFactory getPathFactory() {
        return pathFactory;
    }

    public QValueFactory getQValueFactory() {
        return qValueFactory;
    }

    /**
     * @see RepositoryService#getRepositoryDescriptors()
     */
    public Map getRepositoryDescriptors() throws RepositoryException {
        if (descriptors == null) {
            ReportInfo info = new ReportInfo(RepositoryDescriptorsReport.REPOSITORY_DESCRIPTORS_REPORT, DEPTH_0);
            ReportMethod method = null;
            try {
                method = new ReportMethod(uriResolver.getRepositoryUri(), info);
                getClient(null).executeMethod(method);
                method.checkSuccess();
                Document doc = method.getResponseBodyAsDocument();

                descriptors = new HashMap();
                if (doc != null) {
                    Element rootElement = doc.getDocumentElement();
                    ElementIterator nsElems = DomUtil.getChildren(rootElement, ItemResourceConstants.XML_DESCRIPTOR,
                            ItemResourceConstants.NAMESPACE);
                    while (nsElems.hasNext()) {
                        Element elem = nsElems.nextElement();
                        String key = DomUtil.getChildText(elem, ItemResourceConstants.XML_DESCRIPTORKEY,
                                ItemResourceConstants.NAMESPACE);
                        String descriptor = DomUtil.getChildText(elem, ItemResourceConstants.XML_DESCRIPTORVALUE,
                                ItemResourceConstants.NAMESPACE);
                        if (key != null && descriptor != null) {
                            descriptors.put(key, descriptor);
                        } else {
                            log.error("Invalid descriptor key / value pair: " + key + " -> " + descriptor);
                        }
                    }
                }
            } catch (IOException e) {
                throw new RepositoryException(e);
            } catch (DavException e) {
                throw ExceptionConverter.generate(e);
            } finally {
                if (method != null) {
                    method.releaseConnection();
                }
            }
        }
        return descriptors;
    }

    /**
     * @see RepositoryService#obtain(Credentials, String)
     */
    public SessionInfo obtain(Credentials credentials, String workspaceName)
            throws LoginException, NoSuchWorkspaceException, RepositoryException {
        CredentialsWrapper dc = new CredentialsWrapper(credentials);
        return obtain(dc, workspaceName);
    }

    public SessionInfo obtain(SessionInfo sessionInfo, String workspaceName)
            throws LoginException, NoSuchWorkspaceException, RepositoryException {
        checkSessionInfo(sessionInfo);
        return obtain(((SessionInfoImpl) sessionInfo).getCredentials(), workspaceName);
    }

    public SessionInfo impersonate(SessionInfo sessionInfo, Credentials credentials)
            throws LoginException, RepositoryException {
        throw new UnsupportedOperationException("Not implemented yet.");
    }

    private SessionInfo obtain(CredentialsWrapper credentials, String workspaceName)
            throws LoginException, NoSuchWorkspaceException, RepositoryException {
        // check if the workspace with the given name is accessible
        PropFindMethod method = null;
        SessionInfoImpl sessionInfo = new SessionInfoImpl(credentials, workspaceName);
        try {
            DavPropertyNameSet nameSet = new DavPropertyNameSet();
            // for backwards compat. -> retrieve DAV:workspace if the newly
            // added property (workspaceName) is not supported by the server.
            nameSet.add(DeltaVConstants.WORKSPACE);
            nameSet.add(ItemResourceConstants.JCR_WORKSPACE_NAME);

            method = new PropFindMethod(uriResolver.getWorkspaceUri(workspaceName), nameSet, DEPTH_0);
            getClient(sessionInfo).executeMethod(method);

            MultiStatusResponse[] responses = method.getResponseBodyAsMultiStatus().getResponses();
            if (responses.length != 1) {
                throw new LoginException("Login failed: Unknown workspace '" + workspaceName + "'.");
            }

            boolean success = false;
            DavPropertySet props = responses[0].getProperties(DavServletResponse.SC_OK);
            if (props.contains(ItemResourceConstants.JCR_WORKSPACE_NAME)) {
                String wspName = props.get(ItemResourceConstants.JCR_WORKSPACE_NAME).getValue().toString();
                if (workspaceName == null) {
                    // login with 'null' workspace name -> retrieve the effective
                    // workspace name from the property and recreate the SessionInfo.
                    sessionInfo = new SessionInfoImpl(credentials, wspName);
                } else if (!wspName.equals(workspaceName)) {
                    throw new LoginException("Login failed: Invalid workspace name '" + workspaceName + "'.");
                }
            } else if (props.contains(DeltaVConstants.WORKSPACE)) {
                String wspHref = new HrefProperty(props.get(DeltaVConstants.WORKSPACE)).getHrefs().get(0)
                        .toString();
                String wspName = Text.unescape(Text.getName(wspHref, true));
                if (!wspName.equals(workspaceName)) {
                    throw new LoginException("Login failed: Invalid workspace name " + workspaceName);
                }
            } else {
                throw new LoginException("Login failed: Unknown workspace '" + workspaceName + "'.");
            }
        } catch (IOException e) {
            throw new RepositoryException(e.getMessage());
        } catch (DavException e) {
            throw ExceptionConverter.generate(e);
        } finally {
            if (method != null) {
                method.releaseConnection();
            }
        }

        // make sure the general namespace mappings have been loaded once
        // before additional requests are executed that rely on the namespace
        // mappings.
        if (nsCache.prefixToURI.isEmpty()) {
            try {
                getRegisteredNamespaces(sessionInfo);
            } catch (RepositoryException e) {
                // ignore
            }
        }

        // return the sessionInfo
        return sessionInfo;
    }

    public void dispose(SessionInfo sessionInfo) throws RepositoryException {
        checkSessionInfo(sessionInfo);
        removeClient(sessionInfo);
    }

    /**
     * @see RepositoryService#getWorkspaceNames(SessionInfo)
     */
    public String[] getWorkspaceNames(SessionInfo sessionInfo) throws RepositoryException {
        DavPropertyNameSet nameSet = new DavPropertyNameSet();
        nameSet.add(DeltaVConstants.WORKSPACE);
        PropFindMethod method = null;
        try {
            method = new PropFindMethod(uriResolver.getRepositoryUri(), nameSet, DEPTH_1);
            getClient(sessionInfo).executeMethod(method);
            MultiStatusResponse[] responses = method.getResponseBodyAsMultiStatus().getResponses();
            Set wspNames = new HashSet();
            for (int i = 0; i < responses.length; i++) {
                DavPropertySet props = responses[i].getProperties(DavServletResponse.SC_OK);
                if (props.contains(DeltaVConstants.WORKSPACE)) {
                    HrefProperty hp = new HrefProperty(props.get(DeltaVConstants.WORKSPACE));
                    String wspHref = hp.getHrefs().get(0).toString();
                    String name = Text.unescape(Text.getName(wspHref, true));
                    wspNames.add(name);
                }
            }
            return (String[]) wspNames.toArray(new String[wspNames.size()]);
        } catch (IOException e) {
            throw new RepositoryException(e);
        } catch (DavException e) {
            throw ExceptionConverter.generate(e);
        } finally {
            if (method != null) {
                method.releaseConnection();
            }
        }
    }

    /**
     * @see RepositoryService#isGranted(SessionInfo, ItemId, String[] actions)
     */
    public boolean isGranted(SessionInfo sessionInfo, ItemId itemId, String[] actions) throws RepositoryException {
        ReportMethod method = null;
        try {
            String uri = getItemUri(itemId, sessionInfo);
            ReportInfo reportInfo = new ReportInfo(JcrPrivilegeReport.PRIVILEGES_REPORT);
            reportInfo.setContentElement(DomUtil.hrefToXml(uri, domFactory));

            method = new ReportMethod(uriResolver.getWorkspaceUri(sessionInfo.getWorkspaceName()), reportInfo);
            getClient(sessionInfo).executeMethod(method);
            method.checkSuccess();

            MultiStatusResponse[] responses = method.getResponseBodyAsMultiStatus().getResponses();
            if (responses.length < 1) {
                throw new ItemNotFoundException(
                        "Unable to retrieve permissions for item " + saveGetIdString(itemId, sessionInfo));
            }
            DavProperty p = responses[0].getProperties(DavServletResponse.SC_OK)
                    .get(SecurityConstants.CURRENT_USER_PRIVILEGE_SET);
            if (p == null) {
                return false;
            }
            // build set of privileges from given actions. NOTE: since the actions
            // have no qualifying namespace, the {@link ItemResourceConstants#NAMESPACE}
            // is used.
            Set requiredPrivileges = new HashSet();
            for (int i = 0; i < actions.length; i++) {
                requiredPrivileges.add(Privilege.getPrivilege(actions[i], ItemResourceConstants.NAMESPACE));
            }
            // build set of privileges granted to the current user.
            CurrentUserPrivilegeSetProperty privSet = new CurrentUserPrivilegeSetProperty(p);
            Collection privileges = (Collection) privSet.getValue();

            // check privileges present against required privileges.
            return privileges.containsAll(requiredPrivileges);
        } catch (IOException e) {
            throw new RepositoryException(e);
        } catch (DavException e) {
            throw ExceptionConverter.generate(e);
        } finally {
            if (method != null) {
                method.releaseConnection();
            }
        }
    }

    /**
     * @see RepositoryService#getNodeDefinition(SessionInfo, NodeId)
     */
    public QNodeDefinition getNodeDefinition(SessionInfo sessionInfo, NodeId nodeId) throws RepositoryException {
        return (QNodeDefinition) getItemDefinition(sessionInfo, nodeId);
    }

    /**
     * @see RepositoryService#getPropertyDefinition(SessionInfo, PropertyId)
     */
    public QPropertyDefinition getPropertyDefinition(SessionInfo sessionInfo, PropertyId propertyId)
            throws RepositoryException {
        return (QPropertyDefinition) getItemDefinition(sessionInfo, propertyId);
    }

    /**
     *
     * @param sessionInfo
     * @param itemId
     * @return
     * @throws RepositoryException
     */
    private QItemDefinition getItemDefinition(SessionInfo sessionInfo, ItemId itemId) throws RepositoryException {
        // set of properties to be retrieved
        DavPropertyNameSet nameSet = new DavPropertyNameSet();
        nameSet.add(ItemResourceConstants.JCR_DEFINITION);
        nameSet.add(DavPropertyName.RESOURCETYPE);

        DavMethodBase method = null;
        try {
            String uri = getItemUri(itemId, sessionInfo);
            method = new PropFindMethod(uri, nameSet, DEPTH_0);
            getClient(sessionInfo).executeMethod(method);

            MultiStatusResponse[] responses = method.getResponseBodyAsMultiStatus().getResponses();
            if (responses.length < 1) {
                throw new ItemNotFoundException(
                        "Unable to retrieve the item definition for " + saveGetIdString(itemId, sessionInfo));
            }
            if (responses.length > 1) {
                throw new RepositoryException("Internal error: ambigous item definition found '"
                        + saveGetIdString(itemId, sessionInfo) + "'.");
            }
            DavPropertySet propertySet = responses[0].getProperties(DavServletResponse.SC_OK);

            // check if definition matches the type of the id
            DavProperty rType = propertySet.get(DavPropertyName.RESOURCETYPE);
            if (rType.getValue() == null && itemId.denotesNode()) {
                throw new RepositoryException(
                        "Internal error: requested node definition and got property definition.");
            }

            NamePathResolver resolver = getNamePathResolver(sessionInfo);

            // build the definition
            QItemDefinition definition = null;
            if (propertySet.contains(ItemResourceConstants.JCR_DEFINITION)) {
                DavProperty prop = propertySet.get(ItemResourceConstants.JCR_DEFINITION);
                Object value = prop.getValue();
                if (value != null && value instanceof Element) {
                    Element idfElem = (Element) value;
                    if (itemId.denotesNode()) {
                        definition = new QNodeDefinitionImpl(null, idfElem, resolver);
                    } else {
                        definition = new QPropertyDefinitionImpl(null, idfElem, resolver, getQValueFactory());
                    }
                }
            }
            if (definition == null) {
                throw new RepositoryException("Unable to retrieve definition for item with id '"
                        + saveGetIdString(itemId, resolver) + "'.");
            }
            return definition;
        } catch (IOException e) {
            throw new RepositoryException(e);
        } catch (DavException e) {
            throw ExceptionConverter.generate(e);
        } finally {
            if (method != null) {
                method.releaseConnection();
            }
        }
    }

    /**
     * @see RepositoryService#getNodeInfo(SessionInfo, NodeId)
     */
    public NodeInfo getNodeInfo(SessionInfo sessionInfo, NodeId nodeId) throws RepositoryException {
        // set of properties to be retrieved
        DavPropertyNameSet nameSet = new DavPropertyNameSet();
        nameSet.add(ItemResourceConstants.JCR_INDEX);
        nameSet.add(ItemResourceConstants.JCR_PARENT);
        nameSet.add(ItemResourceConstants.JCR_NAME);
        nameSet.add(ItemResourceConstants.JCR_PRIMARYNODETYPE);
        nameSet.add(ItemResourceConstants.JCR_MIXINNODETYPES);
        nameSet.add(ItemResourceConstants.JCR_REFERENCES);
        nameSet.add(ItemResourceConstants.JCR_UUID);
        nameSet.add(ItemResourceConstants.JCR_PATH);
        nameSet.add(DavPropertyName.RESOURCETYPE);

        DavMethodBase method = null;
        try {
            String uri = getItemUri(nodeId, sessionInfo);
            method = new PropFindMethod(uri, nameSet, DEPTH_1);
            getClient(sessionInfo).executeMethod(method);
            method.checkSuccess();

            MultiStatusResponse[] responses = method.getResponseBodyAsMultiStatus().getResponses();
            if (responses.length < 1) {
                throw new ItemNotFoundException(
                        "Unable to retrieve the node with id " + saveGetIdString(nodeId, sessionInfo));
            }

            MultiStatusResponse nodeResponse = null;
            List childResponses = new ArrayList();
            for (int i = 0; i < responses.length; i++) {
                if (isSameResource(uri, responses[i])) {
                    nodeResponse = responses[i];
                } else {
                    childResponses.add(responses[i]);
                }
            }

            if (nodeResponse == null) {
                throw new ItemNotFoundException(
                        "Unable to retrieve the node " + saveGetIdString(nodeId, sessionInfo));
            }

            DavPropertySet propSet = nodeResponse.getProperties(DavServletResponse.SC_OK);
            Object type = propSet.get(DavPropertyName.RESOURCETYPE).getValue();
            if (type == null) {
                // the given id points to a Property instead of a Node
                throw new ItemNotFoundException("No node for id " + saveGetIdString(nodeId, sessionInfo));
            }

            NamePathResolver resolver = getNamePathResolver(sessionInfo);
            NodeId parentId = getParentId(propSet, sessionInfo);

            NodeInfoImpl nInfo = buildNodeInfo(nodeResponse, parentId, propSet, sessionInfo, resolver);

            for (Iterator it = childResponses.iterator(); it.hasNext();) {
                MultiStatusResponse resp = (MultiStatusResponse) it.next();
                DavPropertySet childProps = resp.getProperties(DavServletResponse.SC_OK);
                if (childProps.contains(DavPropertyName.RESOURCETYPE)
                        && childProps.get(DavPropertyName.RESOURCETYPE).getValue() != null) {
                    // any other resource type than default (empty) is represented by a node item
                    // --> build child info object
                    nInfo.addChildInfo(buildChildInfo(childProps, sessionInfo));
                } else {
                    PropertyId childId = uriResolver.buildPropertyId(nInfo.getId(), resp,
                            sessionInfo.getWorkspaceName(), getNamePathResolver(sessionInfo));
                    nInfo.addPropertyId(childId);
                }
            }
            return nInfo;
        } catch (IOException e) {
            throw new RepositoryException(e);
        } catch (DavException e) {
            throw ExceptionConverter.generate(e);
        } catch (NameException e) {
            throw new RepositoryException(e);
        } finally {
            if (method != null) {
                method.releaseConnection();
            }
        }
    }

    /**
     * @see RepositoryService#getItemInfos(SessionInfo, NodeId)
     */
    public Iterator getItemInfos(SessionInfo sessionInfo, NodeId nodeId) throws RepositoryException {
        // TODO: implement batch read properly:
        // currently: missing 'value/values' property PropertyInfo cannot be built
        // currently: missing prop-names with child-NodeInfo
        List l = new ArrayList();
        NodeInfo nInfo = getNodeInfo(sessionInfo, nodeId);
        l.add(nInfo);
        // at least add propertyInfos for the meta-props already known from the
        // nodeInfo.
        l.addAll(buildPropertyInfos(nInfo));
        return l.iterator();
    }

    private NodeInfoImpl buildNodeInfo(MultiStatusResponse nodeResponse, NodeId parentId, DavPropertySet propSet,
            SessionInfo sessionInfo, NamePathResolver resolver) throws NameException, RepositoryException {
        NodeId id = uriResolver.buildNodeId(parentId, nodeResponse, sessionInfo.getWorkspaceName(),
                getNamePathResolver(sessionInfo));
        NodeInfoImpl nInfo = new NodeInfoImpl(id, propSet, resolver);
        if (propSet.contains(ItemResourceConstants.JCR_REFERENCES)) {
            HrefProperty refProp = new HrefProperty(propSet.get(ItemResourceConstants.JCR_REFERENCES));
            Iterator hrefIter = refProp.getHrefs().iterator();
            while (hrefIter.hasNext()) {
                String propertyHref = hrefIter.next().toString();
                PropertyId propertyId = uriResolver.getPropertyId(propertyHref, sessionInfo);
                nInfo.addReference(propertyId);
            }
        }
        return nInfo;
    }

    private List buildPropertyInfos(NodeInfo nInfo) throws RepositoryException {
        List l = new ArrayList(3);
        NodeId nid = nInfo.getId();
        Path nPath = nInfo.getPath();

        if (nid.getPath() == null) {
            PropertyId id = getIdFactory().createPropertyId(nid, NameConstants.JCR_UUID);
            QValue[] vs = new QValue[] { getQValueFactory().create(nid.getUniqueID(), PropertyType.STRING) };
            Path p = getPathFactory().create(nPath, NameConstants.JCR_UUID, true);
            PropertyInfo pi = new PropertyInfoImpl(id, p, PropertyType.STRING, false, vs);
            l.add(pi);
        }

        Name pName = NameConstants.JCR_PRIMARYTYPE;
        QValue[] vs = new QValue[] { getQValueFactory().create(nInfo.getNodetype()) };
        PropertyInfo pi = new PropertyInfoImpl(getIdFactory().createPropertyId(nid, pName),
                getPathFactory().create(nPath, pName, true), PropertyType.NAME, false, vs);
        l.add(pi);

        Name[] mixins = nInfo.getMixins();
        if (mixins.length > 0) {
            pName = NameConstants.JCR_MIXINTYPES;
            vs = new QValue[mixins.length];
            for (int i = 0; i < mixins.length; i++) {
                vs[i] = getQValueFactory().create(mixins[i]);
            }
            pi = new PropertyInfoImpl(getIdFactory().createPropertyId(nid, pName),
                    getPathFactory().create(nPath, pName, true), PropertyType.NAME, true, vs);
            l.add(pi);
        }

        return l;
    }

    /**
     * @see RepositoryService#getChildInfos(SessionInfo, NodeId)
     */
    public Iterator getChildInfos(SessionInfo sessionInfo, NodeId parentId) throws RepositoryException {
        // set of properties to be retrieved
        DavPropertyNameSet nameSet = new DavPropertyNameSet();
        nameSet.add(ItemResourceConstants.JCR_NAME);
        nameSet.add(ItemResourceConstants.JCR_INDEX);
        nameSet.add(ItemResourceConstants.JCR_PARENT);
        nameSet.add(ItemResourceConstants.JCR_UUID);
        nameSet.add(DavPropertyName.RESOURCETYPE);

        DavMethodBase method = null;
        try {
            String uri = getItemUri(parentId, sessionInfo);
            method = new PropFindMethod(uri, nameSet, DEPTH_1);
            getClient(sessionInfo).executeMethod(method);
            method.checkSuccess();

            MultiStatusResponse[] responses = method.getResponseBodyAsMultiStatus().getResponses();
            if (responses.length < 1) {
                throw new ItemNotFoundException(
                        "Unable to retrieve the node with id " + saveGetIdString(parentId, sessionInfo));
            } else if (responses.length == 1) {
                // no child nodes nor properties
                return Collections.EMPTY_LIST.iterator();
            }

            List childEntries = new ArrayList();
            for (int i = 0; i < responses.length; i++) {
                if (!isSameResource(uri, responses[i])) {
                    MultiStatusResponse resp = responses[i];
                    DavPropertySet childProps = resp.getProperties(DavServletResponse.SC_OK);
                    if (childProps.contains(DavPropertyName.RESOURCETYPE)
                            && childProps.get(DavPropertyName.RESOURCETYPE).getValue() != null) {
                        childEntries.add(buildChildInfo(childProps, sessionInfo));
                    } // else: property -> ignore
                } // else: ignore the response related to the parent
            }
            return childEntries.iterator();
        } catch (IOException e) {
            throw new RepositoryException(e);
        } catch (DavException e) {
            throw ExceptionConverter.generate(e);
        } finally {
            if (method != null) {
                method.releaseConnection();
            }
        }
    }

    private ChildInfo buildChildInfo(DavPropertySet properties, SessionInfo sessionInfo)
            throws RepositoryException {
        Name qName = getQName(properties, getNamePathResolver(sessionInfo));
        int index = getIndex(properties);
        String uuid = getUniqueID(properties);

        return new ChildInfoImpl(qName, uuid, index);
    }

    /**
     * @see RepositoryService#getReferences(SessionInfo, NodeId, Name, boolean)
     */
    public Iterator<PropertyId> getReferences(SessionInfo sessionInfo, NodeId nodeId, Name propertyName,
            boolean weakReferences) throws ItemNotFoundException, RepositoryException {
        // set of properties to be retrieved
        DavPropertyNameSet nameSet = new DavPropertyNameSet();
        if (weakReferences) {
            nameSet.add(ItemResourceConstants.JCR_WEAK_REFERENCES);
        } else {
            nameSet.add(ItemResourceConstants.JCR_REFERENCES);
        }

        DavMethodBase method = null;
        try {
            String uri = getItemUri(nodeId, sessionInfo);
            method = new PropFindMethod(uri, nameSet, DEPTH_0);
            getClient(sessionInfo).executeMethod(method);
            method.checkSuccess();

            MultiStatusResponse[] responses = method.getResponseBodyAsMultiStatus().getResponses();
            if (responses.length < 1) {
                throw new ItemNotFoundException(
                        "Unable to retrieve the node with id " + saveGetIdString(nodeId, sessionInfo));
            }

            List<PropertyId> refIds = new ArrayList<PropertyId>();
            for (int i = 0; i < responses.length; i++) {
                if (isSameResource(uri, responses[i])) {
                    MultiStatusResponse resp = responses[i];
                    DavPropertySet props = resp.getProperties(DavServletResponse.SC_OK);
                    DavProperty p;
                    if (weakReferences) {
                        p = props.get(ItemResourceConstants.JCR_WEAK_REFERENCES);
                    } else {
                        p = props.get(ItemResourceConstants.JCR_REFERENCES);
                    }

                    if (p == null) {
                        return Collections.EMPTY_LIST.iterator();
                    } else {
                        HrefProperty hp = new HrefProperty(p);
                        for (Iterator it = hp.getHrefs().iterator(); it.hasNext();) {
                            String propHref = it.next().toString();
                            PropertyId propId = uriResolver.getPropertyId(propHref, sessionInfo);
                            if (propertyName == null || propertyName.equals(propId.getName())) {
                                refIds.add(propId);
                            }
                        }
                    }
                }
            }
            return refIds.iterator();
        } catch (IOException e) {
            throw new RepositoryException(e);
        } catch (DavException e) {
            throw ExceptionConverter.generate(e);
        } finally {
            if (method != null) {
                method.releaseConnection();
            }
        }
    }

    /**
     * @see RepositoryService#getPropertyInfo(SessionInfo, PropertyId)
     */
    public PropertyInfo getPropertyInfo(SessionInfo sessionInfo, PropertyId propertyId) throws RepositoryException {
        GetMethod method = null;
        try {
            String uri = getItemUri(propertyId, sessionInfo);
            method = new GetMethod(uri);
            HttpClient client = getClient(sessionInfo);
            client.executeMethod(method);

            int status = method.getStatusCode();
            if (status != DavServletResponse.SC_OK) {
                throw ExceptionConverter.generate(new DavException(status, method.getStatusText()));
            }

            Path path = uriResolver.getQPath(uri, sessionInfo);

            String ct = null;
            Header hd = method.getResponseHeader(HEADER_CONTENT_TYPE);
            if (hd != null) {
                ct = hd.getValue();
            }

            boolean isMultiValued;
            QValue[] values;
            int type = JcrValueType.typeFromContentType(ct);

            NamePathResolver resolver = getNamePathResolver(sessionInfo);
            if (type != PropertyType.UNDEFINED) {
                QValue v;
                if (type == PropertyType.BINARY) {
                    v = getQValueFactory().create(method.getResponseBodyAsStream());
                } else {
                    Reader reader = new InputStreamReader(method.getResponseBodyAsStream(),
                            method.getResponseCharSet());
                    StringBuffer sb = new StringBuffer();
                    int c;
                    while ((c = reader.read()) > -1) {
                        sb.append((char) c);
                    }
                    Value jcrValue = valueFactory.createValue(sb.toString(), type);
                    if (jcrValue instanceof QValueValue) {
                        v = ((QValueValue) jcrValue).getQValue();
                    } else {
                        v = ValueFormat.getQValue(jcrValue, resolver, getQValueFactory());
                    }
                }
                values = new QValue[] { v };
                isMultiValued = false;
            } else if (ct.startsWith("text/xml")) {
                // jcr:values property spooled
                values = getValues(method.getResponseBodyAsStream(), resolver, propertyId);
                type = (values.length > 0) ? values[0].getType() : loadType(uri, client, propertyId, resolver);
                isMultiValued = true;
            } else {
                throw new ItemNotFoundException(
                        "Unable to retrieve the property with id " + saveGetIdString(propertyId, resolver));
            }

            PropertyInfo pInfo = new PropertyInfoImpl(propertyId, path, type, isMultiValued, values);
            return pInfo;
        } catch (IOException e) {
            throw new RepositoryException(e);
        } catch (DavException e) {
            throw ExceptionConverter.generate(e);
        } catch (NameException e) {
            throw new RepositoryException(e);
        } finally {
            if (method != null) {
                method.releaseConnection();
            }
        }
    }

    private QValue[] getValues(InputStream response, NamePathResolver resolver, ItemId id)
            throws RepositoryException {
        try {
            DocumentBuilder db = DomUtil.BUILDER_FACTORY.newDocumentBuilder();
            Document doc = db.parse(response);
            Element prop = DomUtil.getChildElement(doc, ItemResourceConstants.JCR_VALUES.getName(),
                    ItemResourceConstants.JCR_VALUES.getNamespace());
            if (prop == null) {
                // no jcr-values present in the response body -> apparently
                // not representation of a jcr-property
                throw new ItemNotFoundException("No property found at " + saveGetIdString(id, resolver));
            } else {
                DavProperty p = DefaultDavProperty.createFromXml(prop);
                ValuesProperty vp = new ValuesProperty(p, PropertyType.STRING, valueFactory);

                Value[] jcrVs = vp.getJcrValues();
                QValue[] qvs = new QValue[jcrVs.length];
                int type = (jcrVs.length > 0) ? jcrVs[0].getType() : PropertyType.STRING;

                for (int i = 0; i < jcrVs.length; i++) {
                    if (jcrVs[i] instanceof QValueValue) {
                        qvs[i] = ((QValueValue) jcrVs[i]).getQValue();
                    } else if (type == PropertyType.BINARY) {
                        qvs[i] = qValueFactory.create(jcrVs[i].getStream());
                    } else {
                        qvs[i] = ValueFormat.getQValue(jcrVs[i], resolver, qValueFactory);
                    }
                }
                return qvs;
            }
        } catch (SAXException e) {
            log.warn("Internal error: ", e.getMessage());
            throw new RepositoryException(e);
        } catch (IOException e) {
            log.warn("Internal error: ", e.getMessage());
            throw new RepositoryException(e);
        } catch (ParserConfigurationException e) {
            log.warn("Internal error: ", e.getMessage());
            throw new RepositoryException(e);
        } catch (DavException e) {
            throw ExceptionConverter.generate(e);
        }
    }

    private int loadType(String propertyURI, HttpClient client, PropertyId propertyId, NamePathResolver resolver)
            throws IOException, DavException, RepositoryException {
        DavPropertyNameSet nameSet = new DavPropertyNameSet();
        nameSet.add(ItemResourceConstants.JCR_TYPE);

        DavMethodBase method = null;
        try {
            method = new PropFindMethod(propertyURI, nameSet, DEPTH_0);
            client.executeMethod(method);
            method.checkSuccess();

            MultiStatusResponse[] responses = method.getResponseBodyAsMultiStatus().getResponses();
            if (responses.length == 1) {
                DavPropertySet props = responses[0].getProperties(DavServletResponse.SC_OK);
                DavProperty type = props.get(ItemResourceConstants.JCR_TYPE);
                if (type != null) {
                    return PropertyType.valueFromName(type.getValue().toString());
                } else {
                    throw new RepositoryException("Internal error. Cannot retrieve property type at "
                            + saveGetIdString(propertyId, resolver));
                }
            } else {
                throw new ItemNotFoundException("Internal error. Cannot retrieve property type at "
                        + saveGetIdString(propertyId, resolver));
            }
        } finally {
            method.releaseConnection();
        }
    }

    /**
     * @see RepositoryService#createBatch(SessionInfo,ItemId)
     */
    public Batch createBatch(SessionInfo sessionInfo, ItemId itemId) throws RepositoryException {
        checkSessionInfo(sessionInfo);
        return new BatchImpl(itemId, sessionInfo);
    }

    /**
     * @see RepositoryService#submit(Batch)
     */
    public void submit(Batch batch) throws RepositoryException {
        if (!(batch instanceof BatchImpl)) {
            throw new RepositoryException("Unknown Batch implementation.");
        }
        BatchImpl batchImpl = (BatchImpl) batch;
        if (batchImpl.isEmpty()) {
            batchImpl.dispose();
            return;
        }

        DavMethod method = null;
        try {
            HttpClient client = batchImpl.start();
            boolean success = false;

            try {
                Iterator it = batchImpl.methods();
                while (it.hasNext()) {
                    method = (DavMethod) it.next();
                    initMethod(method, batchImpl, true);

                    client.executeMethod(method);
                    method.checkSuccess();
                    method.releaseConnection();
                }
                success = true;
            } finally {
                // make sure the lock is removed. if any of the methods
                // failed the unlock is used to abort any pending changes
                // on the server.
                batchImpl.end(client, success);
            }
        } catch (IOException e) {
            throw new RepositoryException(e);
        } catch (DavException e) {
            throw ExceptionConverter.generate(e, method);
        } finally {
            batchImpl.dispose();
        }
    }

    /**
     * @see RepositoryService#importXml(SessionInfo, NodeId, InputStream, int)
     */
    public void importXml(SessionInfo sessionInfo, NodeId parentId, InputStream xmlStream, int uuidBehaviour)
            throws RepositoryException {
        // TODO: improve. currently random name is built instead of retrieving name of new resource from top-level xml element within stream
        Name nodeName = getNameFactory().create(Name.NS_DEFAULT_URI, UUID.randomUUID().toString());
        String uri = getItemUri(parentId, nodeName, sessionInfo);
        MkColMethod method = new MkColMethod(uri);
        method.addRequestHeader(ItemResourceConstants.IMPORT_UUID_BEHAVIOR, new Integer(uuidBehaviour).toString());
        method.setRequestEntity(new InputStreamRequestEntity(xmlStream, "text/xml"));
        execute(method, sessionInfo);
    }

    /**
     * @see RepositoryService#move(SessionInfo, NodeId, NodeId, Name)
     */
    public void move(SessionInfo sessionInfo, NodeId srcNodeId, NodeId destParentNodeId, Name destName)
            throws RepositoryException {
        String uri = getItemUri(srcNodeId, sessionInfo);
        String destUri = getItemUri(destParentNodeId, destName, sessionInfo);
        MoveMethod method = new MoveMethod(uri, destUri, true);
        execute(method, sessionInfo);
    }

    /**
     * @see RepositoryService#copy(SessionInfo, String, NodeId, NodeId, Name)
     */
    public void copy(SessionInfo sessionInfo, String srcWorkspaceName, NodeId srcNodeId, NodeId destParentNodeId,
            Name destName) throws RepositoryException {
        String uri = uriResolver.getItemUri(srcNodeId, srcWorkspaceName, sessionInfo);
        String destUri = getItemUri(destParentNodeId, destName, sessionInfo);
        CopyMethod method = new CopyMethod(uri, destUri, true, false);
        execute(method, sessionInfo);
    }

    /**
     * @see RepositoryService#update(SessionInfo, NodeId, String)
     */
    public void update(SessionInfo sessionInfo, NodeId nodeId, String srcWorkspaceName) throws RepositoryException {
        String uri = getItemUri(nodeId, sessionInfo);
        String workspUri = uriResolver.getWorkspaceUri(srcWorkspaceName);

        update(uri, new String[] { workspUri }, UpdateInfo.UPDATE_BY_WORKSPACE, false, sessionInfo);
    }

    /**
     * @see RepositoryService#clone(SessionInfo, String, NodeId, NodeId, Name, boolean)
     */
    public void clone(SessionInfo sessionInfo, String srcWorkspaceName, NodeId srcNodeId, NodeId destParentNodeId,
            Name destName, boolean removeExisting) throws RepositoryException {
        // TODO: missing implementation
        throw new UnsupportedOperationException("Missing implementation");
    }

    /**
     * @see RepositoryService#getLockInfo(SessionInfo, NodeId)
     */
    public LockInfo getLockInfo(SessionInfo sessionInfo, NodeId nodeId) throws RepositoryException {
        // set of Dav-properties to be retrieved
        DavPropertyNameSet nameSet = new DavPropertyNameSet();
        nameSet.add(DavPropertyName.LOCKDISCOVERY);
        nameSet.add(ItemResourceConstants.JCR_PARENT);

        PropFindMethod method = null;
        try {
            String uri = getItemUri(nodeId, sessionInfo);
            method = new PropFindMethod(uri, nameSet, DEPTH_0);
            // TODO: not correct. pass tokens in order avoid new session to be created TOBEFIXED
            initMethod(method, sessionInfo, true);

            getClient(sessionInfo).executeMethod(method);
            method.checkSuccess();

            MultiStatusResponse[] responses = method.getResponseBodyAsMultiStatus().getResponses();
            if (responses.length != 1) {
                throw new ItemNotFoundException(
                        "Unable to retrieve the LockInfo. No such node " + saveGetIdString(nodeId, sessionInfo));
            }

            DavPropertySet ps = responses[0].getProperties(DavServletResponse.SC_OK);
            if (ps.contains(DavPropertyName.LOCKDISCOVERY)) {
                DavProperty p = ps.get(DavPropertyName.LOCKDISCOVERY);
                LockDiscovery ld = LockDiscovery.createFromXml(p.toXml(domFactory));
                NodeId parentId = getParentId(ps, sessionInfo);
                return retrieveLockInfo(ld, sessionInfo, nodeId, parentId);
            } else {
                // no lock present
                log.debug("No Lock present on node with id " + saveGetIdString(nodeId, sessionInfo));
                return null;
            }
        } catch (IOException e) {
            throw new RepositoryException(e);
        } catch (DavException e) {
            throw ExceptionConverter.generate(e);
        } finally {
            if (method != null) {
                method.releaseConnection();
            }
        }
    }

    /**
     * @see RepositoryService#lock(SessionInfo, NodeId, boolean, boolean)
     */
    public LockInfo lock(SessionInfo sessionInfo, NodeId nodeId, boolean deep, boolean sessionScoped)
            throws RepositoryException {
        return lock(sessionInfo, nodeId, deep, sessionScoped, Long.MAX_VALUE, null);
    }

    /**
     * @see RepositoryService#lock(SessionInfo, NodeId, boolean, boolean, long, String)
     */
    public LockInfo lock(SessionInfo sessionInfo, NodeId nodeId, boolean deep, boolean sessionScoped,
            long timeoutHint, String ownerHint) throws UnsupportedRepositoryOperationException, LockException,
            AccessDeniedException, RepositoryException {
        try {
            checkSessionInfo(sessionInfo);
            long davTimeout = (timeoutHint == Long.MAX_VALUE) ? INFINITE_TIMEOUT : timeoutHint * 1000;
            String ownerInfo = (ownerHint == null) ? sessionInfo.getUserID() : ownerHint;

            String uri = getItemUri(nodeId, sessionInfo);
            Scope scope = (sessionScoped) ? ItemResourceConstants.EXCLUSIVE_SESSION : Scope.EXCLUSIVE;
            LockMethod method = new LockMethod(uri, scope, Type.WRITE, ownerInfo, davTimeout, deep);
            execute(method, sessionInfo);

            String lockToken = method.getLockToken();
            ((SessionInfoImpl) sessionInfo).addLockToken(lockToken, sessionScoped);

            LockDiscovery disc = method.getResponseAsLockDiscovery();
            return retrieveLockInfo(disc, sessionInfo, nodeId, null);
        } catch (IOException e) {
            throw new RepositoryException(e);
        } catch (DavException e) {
            throw ExceptionConverter.generate(e);
        }
    }

    /**
     * @see RepositoryService#refreshLock(SessionInfo, NodeId)
     */
    public void refreshLock(SessionInfo sessionInfo, NodeId nodeId) throws RepositoryException {
        checkSessionInfo(sessionInfo);
        String uri = getItemUri(nodeId, sessionInfo);
        // since sessionInfo does not allow to retrieve token by NodeId,
        // pass all available lock tokens to the LOCK method (TODO: correct?)
        LockMethod method = new LockMethod(uri, INFINITE_TIMEOUT,
                ((SessionInfoImpl) sessionInfo).getAllLockTokens());
        execute(method, sessionInfo);
    }

    /**
     * @see RepositoryService#unlock(SessionInfo, NodeId)
     */
    public void unlock(SessionInfo sessionInfo, NodeId nodeId) throws RepositoryException {
        checkSessionInfo(sessionInfo);
        String uri = getItemUri(nodeId, sessionInfo);
        // Note: since sessionInfo does not allow to identify the id of the
        // lock holding node, we need to access the token via lockInfo
        // TODO: review this.
        LockInfoImpl lInfo = (LockInfoImpl) getLockInfo(sessionInfo, nodeId);
        if (lInfo == null) {
            throw new LockException("No Lock present on Node with id " + saveGetIdString(nodeId, sessionInfo));
        }

        String lockToken = lInfo.getActiveLock().getToken();
        boolean isSessionScoped = lInfo.isSessionScoped();

        UnLockMethod method = new UnLockMethod(uri, lockToken);
        execute(method, sessionInfo);

        ((SessionInfoImpl) sessionInfo).removeLockToken(lockToken, isSessionScoped);
    }

    private LockInfo retrieveLockInfo(LockDiscovery lockDiscovery, SessionInfo sessionInfo, NodeId nodeId,
            NodeId parentId) throws RepositoryException {
        List activeLocks = (List) lockDiscovery.getValue();
        Iterator it = activeLocks.iterator();
        ActiveLock activeLock = null;
        while (it.hasNext()) {
            ActiveLock l = (ActiveLock) it.next();
            Scope sc = l.getScope();
            if (l.getType() == Type.WRITE
                    && (sc == Scope.EXCLUSIVE || sc == ItemResourceConstants.EXCLUSIVE_SESSION)) {
                if (activeLock != null) {
                    throw new RepositoryException("Node " + saveGetIdString(nodeId, sessionInfo)
                            + " contains multiple exclusive write locks.");
                } else {
                    activeLock = l;
                }
            }
        }
        if (activeLock == null) {
            log.debug("No lock present on node " + saveGetIdString(nodeId, sessionInfo));
            return null;
        }
        if (activeLock.isDeep() && parentId != null) {
            // try if lock is inherited
            LockInfo pLockInfo = getLockInfo(sessionInfo, parentId);
            if (pLockInfo != null) {
                return pLockInfo;
            }
        }
        // no deep lock or parentID == null or lock is not present on parent
        // -> nodeID is lockHolding Id.
        return new LockInfoImpl(activeLock, nodeId);
    }

    /**
     * @see RepositoryService#checkin(SessionInfo, NodeId)
     */
    public NodeId checkin(SessionInfo sessionInfo, NodeId nodeId) throws VersionException,
            UnsupportedRepositoryOperationException, InvalidItemStateException, LockException, RepositoryException {
        String uri = getItemUri(nodeId, sessionInfo);
        CheckinMethod method = new CheckinMethod(uri);
        execute(method, sessionInfo);
        Header rh = method.getResponseHeader(DeltaVConstants.HEADER_LOCATION);
        return uriResolver.getNodeId(rh.getValue(), sessionInfo);
    }

    /**
     * @see RepositoryService#checkout(SessionInfo, NodeId)
     */
    public void checkout(SessionInfo sessionInfo, NodeId nodeId)
            throws UnsupportedRepositoryOperationException, LockException, RepositoryException {
        String uri = getItemUri(nodeId, sessionInfo);
        CheckoutMethod method = new CheckoutMethod(uri);
        execute(method, sessionInfo);
    }

    public NodeId checkpoint(SessionInfo sessionInfo, NodeId nodeId)
            throws UnsupportedRepositoryOperationException, RepositoryException {
        // TODO
        throw new UnsupportedOperationException("JCR-2104: JSR 283 Versioning. Implementation missing");
    }

    /**
     * @see RepositoryService#removeVersion(SessionInfo, NodeId, NodeId)
     */
    public void removeVersion(SessionInfo sessionInfo, NodeId versionHistoryId, NodeId versionId)
            throws RepositoryException {
        String uri = getItemUri(versionId, sessionInfo);
        DeleteMethod method = new DeleteMethod(uri);
        execute(method, sessionInfo);
    }

    /**
     * @see RepositoryService#restore(SessionInfo, NodeId, NodeId, boolean)
     */
    public void restore(SessionInfo sessionInfo, NodeId nodeId, NodeId versionId, boolean removeExisting)
            throws RepositoryException {
        String uri = getItemUri(nodeId, sessionInfo);
        String vUri = getItemUri(versionId, sessionInfo);

        update(uri, new String[] { vUri }, UpdateInfo.UPDATE_BY_VERSION, removeExisting, sessionInfo);
    }

    /**
     * @see RepositoryService#restore(SessionInfo, NodeId[], boolean)
     */
    public void restore(SessionInfo sessionInfo, NodeId[] versionIds, boolean removeExisting)
            throws RepositoryException {
        String uri = uriResolver.getWorkspaceUri(sessionInfo.getWorkspaceName());
        String[] vUris = new String[versionIds.length];
        for (int i = 0; i < versionIds.length; i++) {
            vUris[i] = getItemUri(versionIds[i], sessionInfo);
        }

        update(uri, vUris, UpdateInfo.UPDATE_BY_VERSION, removeExisting, sessionInfo);
    }

    private void update(String uri, String[] updateSource, int updateType, boolean removeExisting,
            SessionInfo sessionInfo) throws RepositoryException {
        try {
            UpdateInfo uInfo;
            if (removeExisting) {
                Element uElem = UpdateInfo.createUpdateElement(updateSource, updateType, domFactory);
                DomUtil.addChildElement(uElem, ItemResourceConstants.XML_REMOVEEXISTING,
                        ItemResourceConstants.NAMESPACE);
                uInfo = new UpdateInfo(uElem);
            } else {
                uInfo = new UpdateInfo(updateSource, updateType, new DavPropertyNameSet());
            }

            UpdateMethod method = new UpdateMethod(uri, uInfo);
            execute(method, sessionInfo);
        } catch (IOException e) {
            throw new RepositoryException(e);
        } catch (DavException e) {
            throw ExceptionConverter.generate(e);
        }
    }

    /**
     * @see RepositoryService#merge(SessionInfo, NodeId, String, boolean)
     */
    public Iterator merge(SessionInfo sessionInfo, NodeId nodeId, String srcWorkspaceName, boolean bestEffort)
            throws NoSuchWorkspaceException, AccessDeniedException, MergeException, LockException,
            InvalidItemStateException, RepositoryException {
        return merge(sessionInfo, nodeId, srcWorkspaceName, bestEffort, false);
    }

    /**
     * @see RepositoryService#merge(SessionInfo, NodeId, String, boolean, boolean)
     */
    public Iterator merge(SessionInfo sessionInfo, NodeId nodeId, String srcWorkspaceName, boolean bestEffort,
            boolean isShallow) throws NoSuchWorkspaceException, AccessDeniedException, MergeException,
            LockException, InvalidItemStateException, RepositoryException {
        if (!isShallow) {
            try {
                String wspHref = uriResolver.getWorkspaceUri(srcWorkspaceName);
                Element mElem = MergeInfo.createMergeElement(new String[] { wspHref }, bestEffort, false,
                        domFactory);
                MergeInfo mInfo = new MergeInfo(mElem);

                MergeMethod method = new MergeMethod(getItemUri(nodeId, sessionInfo), mInfo);
                execute(method, sessionInfo);

                MultiStatusResponse[] resps = method.getResponseBodyAsMultiStatus().getResponses();
                List failedIds = new ArrayList(resps.length);
                for (int i = 0; i < resps.length; i++) {
                    String href = resps[i].getHref();
                    failedIds.add(uriResolver.getNodeId(href, sessionInfo));
                }
                return failedIds.iterator();
            } catch (IOException e) {
                throw new RepositoryException(e);
            } catch (DavException e) {
                throw ExceptionConverter.generate(e);
            }
        } else {
            // TODO
            throw new UnsupportedOperationException("JCR-2104: JSR 283 Versioning. Implementation missing");
        }
    }

    /**
     * @see RepositoryService#resolveMergeConflict(SessionInfo, NodeId, NodeId[], NodeId[])
     */
    public void resolveMergeConflict(SessionInfo sessionInfo, NodeId nodeId, NodeId[] mergeFailedIds,
            NodeId[] predecessorIds) throws VersionException, InvalidItemStateException,
            UnsupportedRepositoryOperationException, RepositoryException {
        try {
            List changeList = new ArrayList();
            String[] mergeFailedHref = new String[mergeFailedIds.length];
            for (int i = 0; i < mergeFailedIds.length; i++) {
                mergeFailedHref[i] = getItemUri(mergeFailedIds[i], sessionInfo);
            }
            changeList.add(new HrefProperty(VersionControlledResource.AUTO_MERGE_SET, mergeFailedHref, false));

            if (predecessorIds != null && predecessorIds.length > 0) {
                String[] pdcHrefs = new String[predecessorIds.length];
                for (int i = 0; i < predecessorIds.length; i++) {
                    pdcHrefs[i] = getItemUri(predecessorIds[i], sessionInfo);
                }
                changeList.add(new HrefProperty(VersionControlledResource.PREDECESSOR_SET, pdcHrefs, false));
            }

            PropPatchMethod method = new PropPatchMethod(getItemUri(nodeId, sessionInfo), changeList);
            execute(method, sessionInfo);
        } catch (IOException e) {
            throw new RepositoryException(e);
        }
    }

    /**
     * @see RepositoryService#addVersionLabel(SessionInfo,NodeId,NodeId,Name,boolean)
     */
    public void addVersionLabel(SessionInfo sessionInfo, NodeId versionHistoryId, NodeId versionId, Name label,
            boolean moveLabel) throws VersionException, RepositoryException {
        try {
            String uri = getItemUri(versionId, sessionInfo);
            String strLabel = getNamePathResolver(sessionInfo).getJCRName(label);
            LabelMethod method = new LabelMethod(uri, strLabel,
                    (moveLabel) ? LabelInfo.TYPE_SET : LabelInfo.TYPE_ADD);
            execute(method, sessionInfo);
        } catch (IOException e) {
            throw new RepositoryException(e);
        }
    }

    /**
     * @see RepositoryService#removeVersionLabel(SessionInfo,NodeId,NodeId,Name)
     */
    public void removeVersionLabel(SessionInfo sessionInfo, NodeId versionHistoryId, NodeId versionId, Name label)
            throws VersionException, RepositoryException {
        try {
            String uri = getItemUri(versionId, sessionInfo);
            String strLabel = getNamePathResolver(sessionInfo).getJCRName(label);
            LabelMethod method = new LabelMethod(uri, strLabel, LabelInfo.TYPE_REMOVE);
            execute(method, sessionInfo);
        } catch (IOException e) {
            throw new RepositoryException(e);
        }
    }

    /**
     * @see RepositoryService#createActivity(SessionInfo, String)
     */
    public NodeId createActivity(SessionInfo sessionInfo, String title)
            throws UnsupportedRepositoryOperationException, RepositoryException {
        // TODO
        throw new UnsupportedOperationException("JCR-2104: JSR 283 Versioning. Implementation missing");
    }

    /**
     * @see RepositoryService#removeActivity(SessionInfo, NodeId)
     */
    public void removeActivity(SessionInfo sessionInfo, NodeId activityId)
            throws UnsupportedRepositoryOperationException, RepositoryException {
        // TODO
        throw new UnsupportedOperationException("JCR-2104: JSR 283 Versioning. Implementation missing");
    }

    /**
     * @see RepositoryService#mergeActivity(SessionInfo, NodeId)
     */
    public Iterator mergeActivity(SessionInfo sessionInfo, NodeId activityId)
            throws UnsupportedRepositoryOperationException, RepositoryException {
        // TODO
        throw new UnsupportedOperationException("JCR-2104: JSR 283 Versioning. Implementation missing");
    }

    /**
     * @see RepositoryService#createConfiguration(SessionInfo, NodeId, NodeId)
     */
    public NodeId createConfiguration(SessionInfo sessionInfo, NodeId nodeId, NodeId baselineId)
            throws UnsupportedRepositoryOperationException, RepositoryException {
        // TODO
        throw new UnsupportedOperationException("JCR-2104: JSR 283 Versioning. Implementation missing");
    }

    /**
     * @see RepositoryService#getSupportedQueryLanguages(SessionInfo)
     */
    public String[] getSupportedQueryLanguages(SessionInfo sessionInfo) throws RepositoryException {
        OptionsMethod method = new OptionsMethod(uriResolver.getWorkspaceUri(sessionInfo.getWorkspaceName()));
        try {
            getClient(sessionInfo).executeMethod(method);
            method.checkSuccess();

            Header daslHeader = method.getResponseHeader(SearchConstants.HEADER_DASL);
            CodedUrlHeader h = new CodedUrlHeader(daslHeader.getName(), daslHeader.getValue());
            return h.getCodedUrls();
        } catch (IOException e) {
            throw new RepositoryException(e);
        } catch (DavException e) {
            throw ExceptionConverter.generate(e);
        } finally {
            method.releaseConnection();
        }
    }

    public void checkQueryStatement(SessionInfo sessionInfo, String statement, String language, Map namespaces)
            throws InvalidQueryException, RepositoryException {
        // TODO implement
    }

    /**
     * @see RepositoryService#executeQuery(SessionInfo, String, String, Map)
     */
    public QueryInfo executeQuery(SessionInfo sessionInfo, String statement, String language, Map namespaces)
            throws RepositoryException {
        SearchMethod method = null;
        try {
            String uri = uriResolver.getWorkspaceUri(sessionInfo.getWorkspaceName());
            SearchInfo sInfo = new SearchInfo(language, Namespace.EMPTY_NAMESPACE, statement, namespaces);
            method = new SearchMethod(uri, sInfo);
            getClient(sessionInfo).executeMethod(method);
            method.checkSuccess();

            MultiStatus ms = method.getResponseBodyAsMultiStatus();
            NamePathResolver resolver = getNamePathResolver(sessionInfo);
            return new QueryInfoImpl(ms, sessionInfo, uriResolver, resolver, valueFactory, getQValueFactory());
        } catch (IOException e) {
            throw new RepositoryException(e);
        } catch (DavException e) {
            throw ExceptionConverter.generate(e);
        } finally {
            if (method != null) {
                method.releaseConnection();
            }
        }
    }

    /**
     * @see RepositoryService#executeQuery(SessionInfo, String, String, Map, long, long)
     */
    public QueryInfo executeQuery(SessionInfo sessionInfo, String statement, String language, Map namespaces,
            long limit, long offset) throws RepositoryException {
        SearchMethod method = null;
        try {
            String uri = uriResolver.getWorkspaceUri(sessionInfo.getWorkspaceName());
            SearchInfo sInfo = new SearchInfo(language, Namespace.EMPTY_NAMESPACE, statement, namespaces);

            sInfo.setNumberResults(limit);
            sInfo.setOffset(offset);

            method = new SearchMethod(uri, sInfo);
            getClient(sessionInfo).executeMethod(method);
            method.checkSuccess();

            MultiStatus ms = method.getResponseBodyAsMultiStatus();
            NamePathResolver resolver = getNamePathResolver(sessionInfo);
            return new QueryInfoImpl(ms, sessionInfo, uriResolver, resolver, valueFactory, getQValueFactory());
        } catch (IOException e) {
            throw new RepositoryException(e);
        } catch (DavException e) {
            throw ExceptionConverter.generate(e);
        } finally {
            if (method != null) {
                method.releaseConnection();
            }
        }
    }

    /**
     * @see RepositoryService#createEventFilter(SessionInfo, int, Path, boolean, String[], Name[], boolean)
     */
    public EventFilter createEventFilter(SessionInfo sessionInfo, int eventTypes, Path absPath, boolean isDeep,
            String[] uuids, Name[] nodeTypeNames, boolean noLocal)
            throws UnsupportedRepositoryOperationException, RepositoryException {
        // resolve node type names
        // todo what if new node types become available while event filter is still in use?
        Set resolvedTypeNames = null;
        if (nodeTypeNames != null) {
            resolvedTypeNames = new HashSet();
            // make sure node type definitions are available
            if (nodeTypeDefinitions.size() == 0) {
                getQNodeTypeDefinitions(sessionInfo);
            }
            synchronized (nodeTypeDefinitions) {
                for (int i = 0; i < nodeTypeNames.length; i++) {
                    resolveNodeType(resolvedTypeNames, nodeTypeNames[i]);
                }
            }
        }
        return new EventFilterImpl(eventTypes, absPath, isDeep, uuids, resolvedTypeNames, noLocal);
    }

    /**
     * @see RepositoryService#getEvents(Subscription, long)
     */
    public EventBundle[] getEvents(Subscription subscription, long timeout)
            throws RepositoryException, UnsupportedRepositoryOperationException {
        checkSubscription(subscription);

        EventSubscriptionImpl subscr = (EventSubscriptionImpl) subscription;
        String rootUri = uriResolver.getRootItemUri(subscr.getSessionInfo().getWorkspaceName());

        return poll(rootUri, subscr.getId(), timeout, subscr.getSessionInfo());
    }

    /**
     * @see RepositoryService#createSubscription(SessionInfo, EventFilter[])
     */
    public Subscription createSubscription(SessionInfo sessionInfo, EventFilter[] filters)
            throws UnsupportedRepositoryOperationException, RepositoryException {
        checkSessionInfo(sessionInfo);
        String rootUri = uriResolver.getRootItemUri(sessionInfo.getWorkspaceName());
        String subscriptionId = subscribe(rootUri, S_INFO, null, sessionInfo, null);
        log.debug("Subscribed on server for session info " + sessionInfo);
        return new EventSubscriptionImpl(subscriptionId, (SessionInfoImpl) sessionInfo);
    }

    /**
     * @see RepositoryService#updateEventFilters(Subscription, EventFilter[])
     */
    public void updateEventFilters(Subscription subscription, EventFilter[] filters) throws RepositoryException {
        // do nothing ...
        // this is actually not correct because we listen for everything and
        // rely on the client of the repository service to filter the events
    }

    public void dispose(Subscription subscription) throws RepositoryException {
        checkSubscription(subscription);
        EventSubscriptionImpl subscr = (EventSubscriptionImpl) subscription;
        String rootUri = uriResolver.getRootItemUri(subscr.getSessionInfo().getWorkspaceName());
        unsubscribe(rootUri, subscr.getId(), subscr.getSessionInfo());
    }

    private String subscribe(String uri, SubscriptionInfo subscriptionInfo, String subscriptionId,
            SessionInfo sessionInfo, String batchId) throws RepositoryException {
        SubscribeMethod method = null;
        try {
            if (subscriptionId != null) {
                method = new SubscribeMethod(uri, subscriptionInfo, subscriptionId);
            } else {
                method = new SubscribeMethod(uri, subscriptionInfo);
            }

            if (batchId != null) {
                // add batchId as separate header
                CodedUrlHeader ch = new CodedUrlHeader(TransactionConstants.HEADER_TRANSACTIONID, batchId);
                method.setRequestHeader(ch.getHeaderName(), ch.getHeaderValue());
            }

            getClient(sessionInfo).executeMethod(method);
            method.checkSuccess();
            return method.getSubscriptionId();
        } catch (IOException e) {
            throw new RepositoryException(e);
        } catch (DavException e) {
            throw ExceptionConverter.generate(e);
        } finally {
            if (method != null) {
                method.releaseConnection();
            }
        }
    }

    private void unsubscribe(String uri, String subscriptionId, SessionInfo sessionInfo)
            throws RepositoryException {
        UnSubscribeMethod method = null;
        try {
            method = new UnSubscribeMethod(uri, subscriptionId);
            getClient(sessionInfo).executeMethod(method);
            method.checkSuccess();
        } catch (IOException e) {
            throw new RepositoryException(e);
        } catch (DavException e) {
            throw ExceptionConverter.generate(e);
        } finally {
            if (method != null) {
                method.releaseConnection();
            }
        }
    }

    private void resolveNodeType(Set resolved, Name ntName) {
        if (!resolved.add(ntName)) {
            return;
        }
        QNodeTypeDefinition def = (QNodeTypeDefinition) nodeTypeDefinitions.get(ntName);
        if (def != null) {
            Name[] supertypes = def.getSupertypes();
            for (int i = 0; i < supertypes.length; i++) {
                resolveNodeType(resolved, supertypes[i]);
            }
        }
    }

    private EventBundle[] poll(String uri, String subscriptionId, long timeout, SessionInfoImpl sessionInfo)
            throws RepositoryException {
        PollMethod method = null;
        try {
            method = new PollMethod(uri, subscriptionId, timeout);
            getClient(sessionInfo).executeMethod(method);
            method.checkSuccess();

            EventDiscovery disc = method.getResponseAsEventDiscovery();
            EventBundle[] events;
            if (disc.isEmpty()) {
                events = new EventBundle[0];
            } else {
                Element discEl = disc.toXml(domFactory);
                ElementIterator it = DomUtil.getChildren(discEl, ObservationConstants.XML_EVENTBUNDLE,
                        ObservationConstants.NAMESPACE);
                List bundles = new ArrayList();
                while (it.hasNext()) {
                    Element bundleElement = it.nextElement();
                    String value = DomUtil.getAttribute(bundleElement,
                            ObservationConstants.XML_EVENT_TRANSACTION_ID, ObservationConstants.NAMESPACE);
                    // check if it matches a batch id recently submitted
                    boolean isLocal = false;
                    if (value != null) {
                        isLocal = value.equals(sessionInfo.getLastBatchId());
                    }
                    bundles.add(new EventBundleImpl(buildEventList(bundleElement, sessionInfo), isLocal));
                }
                events = (EventBundle[]) bundles.toArray(new EventBundle[bundles.size()]);
            }
            return events;
        } catch (IOException e) {
            throw new RepositoryException(e);
        } catch (DavException e) {
            throw ExceptionConverter.generate(e);
        } finally {
            if (method != null) {
                method.releaseConnection();
            }
        }
    }

    private List buildEventList(Element bundleElement, SessionInfoImpl sessionInfo) {
        List events = new ArrayList();
        ElementIterator eventElementIterator = DomUtil.getChildren(bundleElement, ObservationConstants.XML_EVENT,
                ObservationConstants.NAMESPACE);
        while (eventElementIterator.hasNext()) {
            Element evElem = eventElementIterator.nextElement();
            Element typeEl = DomUtil.getChildElement(evElem, ObservationConstants.XML_EVENTTYPE,
                    ObservationConstants.NAMESPACE);
            EventType[] et = DefaultEventType.createFromXml(typeEl);
            if (et.length == 0 || et.length > 1) {
                // should not occur.
                log.error("Ambigous event type definition: expected one single eventtype.");
                continue;
            }

            String href = DomUtil.getChildTextTrim(evElem, XML_HREF, NAMESPACE);

            int type;
            Path eventPath;
            try {
                type = SubscriptionImpl.getJcrEventType(et[0]);
                eventPath = uriResolver.getQPath(href, sessionInfo);
            } catch (DavException e) {
                // should not occur
                log.error("Internal error while building Event", e.getMessage());
                continue;
            } catch (RepositoryException e) {
                // should not occur
                log.error("Internal error while building Event", e.getMessage());
                continue;
            }

            ItemId eventId = null;
            try {
                if (type == Event.NODE_ADDED || type == Event.NODE_REMOVED) {
                    eventId = uriResolver.getNodeId(href, sessionInfo);
                } else {
                    eventId = uriResolver.getPropertyId(href, sessionInfo);
                }
            } catch (RepositoryException e) {
                log.warn("Unable to build event itemId: ", e.getMessage());
            }
            String parentHref = Text.getRelativeParent(href, 1, true);
            NodeId parentId = null;
            try {
                parentId = uriResolver.getNodeId(parentHref, sessionInfo);
            } catch (RepositoryException e) {
                log.warn("Unable to build event parentId: ", e.getMessage());
            }

            events.add(new EventImpl(eventId, eventPath, parentId, type, evElem, getNamePathResolver(sessionInfo),
                    getQValueFactory()));
        }

        return events;
    }

    /**
     * @see RepositoryService#getRegisteredNamespaces(SessionInfo)
     */
    public Map getRegisteredNamespaces(SessionInfo sessionInfo) throws RepositoryException {
        ReportInfo info = new ReportInfo(RegisteredNamespacesReport.REGISTERED_NAMESPACES_REPORT, DEPTH_0);
        ReportMethod method = null;
        try {
            method = new ReportMethod(uriResolver.getWorkspaceUri(sessionInfo.getWorkspaceName()), info);
            getClient(sessionInfo).executeMethod(method);
            method.checkSuccess();

            Document doc = method.getResponseBodyAsDocument();
            Map namespaces = new HashMap();
            if (doc != null) {
                Element rootElement = doc.getDocumentElement();
                ElementIterator nsElems = DomUtil.getChildren(rootElement, ItemResourceConstants.XML_NAMESPACE,
                        ItemResourceConstants.NAMESPACE);
                while (nsElems.hasNext()) {
                    Element elem = nsElems.nextElement();
                    String prefix = DomUtil.getChildText(elem, ItemResourceConstants.XML_PREFIX,
                            ItemResourceConstants.NAMESPACE);
                    String uri = DomUtil.getChildText(elem, ItemResourceConstants.XML_URI,
                            ItemResourceConstants.NAMESPACE);
                    // default namespace
                    if (prefix == null && uri == null) {
                        prefix = uri = "";
                    }
                    // any other uri must not be null
                    if (uri != null) {
                        namespaces.put(prefix, uri);
                        // TODO: not correct since nsRegistry is retrieved from each session
                        nsCache.add(prefix, uri);
                    } else {
                        log.error("Invalid prefix / uri pair: " + prefix + " -> " + uri);
                    }
                }
            }
            return namespaces;
        } catch (IOException e) {
            throw new RepositoryException(e);
        } catch (DavException e) {
            throw ExceptionConverter.generate(e);
        } finally {
            if (method != null) {
                method.releaseConnection();
            }
        }
    }

    /**
     * @see RepositoryService#getNamespaceURI(SessionInfo, String)
     */
    public String getNamespaceURI(SessionInfo sessionInfo, String prefix)
            throws NamespaceException, RepositoryException {
        try {
            return nsCache.getURI(prefix);
        } catch (NamespaceException e) {
            // refresh namespaces and try again
            getRegisteredNamespaces(sessionInfo);
            return nsCache.getURI(prefix);
        }
    }

    /**
     * @see RepositoryService#getNamespacePrefix(SessionInfo, String)
     */
    public String getNamespacePrefix(SessionInfo sessionInfo, String uri)
            throws NamespaceException, RepositoryException {
        try {
            return nsCache.getPrefix(uri);
        } catch (NamespaceException e) {
            // refresh namespaces and try again
            getRegisteredNamespaces(sessionInfo);
            return nsCache.getPrefix(uri);
        }
    }

    /**
     * @see RepositoryService#registerNamespace(SessionInfo, String, String)
     */
    public void registerNamespace(SessionInfo sessionInfo, String prefix, String uri) throws NamespaceException,
            UnsupportedRepositoryOperationException, AccessDeniedException, RepositoryException {
        // make sure we have them all
        getRegisteredNamespaces(sessionInfo);

        Map namespaces = new HashMap(nsCache.getNamespaces());
        // add new pair that needs to be registered.
        namespaces.put(prefix, uri);

        internalSetNamespaces(sessionInfo, namespaces);
        // adjust internal mappings:
        // TODO: not correct since nsRegistry is retrieved from each session
        nsCache.add(prefix, uri);
    }

    /**
     * @see RepositoryService#unregisterNamespace(SessionInfo, String)
     */
    public void unregisterNamespace(SessionInfo sessionInfo, String uri) throws NamespaceException,
            UnsupportedRepositoryOperationException, AccessDeniedException, RepositoryException {
        // make sure we have them all
        getRegisteredNamespaces(sessionInfo);

        String prefix = nsCache.getPrefix(uri);
        Map namespaces = new HashMap(nsCache.getNamespaces());
        // remove pair that needs to be unregistered
        namespaces.remove(prefix);

        internalSetNamespaces(sessionInfo, namespaces);
        // adjust internal mappings:
        nsCache.remove(prefix, uri);
    }

    /**
     *
     * @param sessionInfo
     * @param namespaces
     * @throws NamespaceException
     * @throws UnsupportedRepositoryOperationException
     * @throws AccessDeniedException
     * @throws RepositoryException
     */
    private void internalSetNamespaces(SessionInfo sessionInfo, Map namespaces) throws NamespaceException,
            UnsupportedRepositoryOperationException, AccessDeniedException, RepositoryException {
        DavPropertySet setProperties = new DavPropertySet();
        setProperties.add(new NamespacesProperty(namespaces));

        PropPatchMethod method = null;
        try {
            String uri = uriResolver.getWorkspaceUri(sessionInfo.getWorkspaceName());

            method = new PropPatchMethod(uri, setProperties, new DavPropertyNameSet());
            initMethod(method, sessionInfo, true);

            getClient(sessionInfo).executeMethod(method);
            method.checkSuccess();
        } catch (IOException e) {
            throw new RepositoryException(e);
        } catch (DavException e) {
            throw ExceptionConverter.generate(e);
        } finally {
            if (method != null) {
                method.releaseConnection();
            }
        }
    }

    /**
     * @see RepositoryService#getQNodeTypeDefinitions(SessionInfo)
     */
    public Iterator getQNodeTypeDefinitions(SessionInfo sessionInfo) throws RepositoryException {
        ReportInfo info = new ReportInfo(NodeTypesReport.NODETYPES_REPORT, DEPTH_0);
        info.setContentElement(DomUtil.createElement(domFactory, NodeTypeConstants.XML_REPORT_ALLNODETYPES,
                NodeTypeConstants.NAMESPACE));

        ReportMethod method = null;
        try {
            String workspaceUri = uriResolver.getWorkspaceUri(sessionInfo.getWorkspaceName());
            method = new ReportMethod(workspaceUri, info);
            getClient(sessionInfo).executeMethod(method);
            method.checkSuccess();

            Document reportDoc = method.getResponseBodyAsDocument();
            return retrieveQNodeTypeDefinitions(sessionInfo, reportDoc);
        } catch (IOException e) {
            throw new RepositoryException(e);
        } catch (DavException e) {
            throw ExceptionConverter.generate(e);
        } finally {
            if (method != null) {
                method.releaseConnection();
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    public Iterator getQNodeTypeDefinitions(SessionInfo sessionInfo, Name[] nodetypeNames)
            throws RepositoryException {
        // in order to avoid individual calls for every nodetype, retrieve
        // the complete set from the server (again).
        return getQNodeTypeDefinitions(sessionInfo);
    }

    /**
     * {@inheritDoc}
     */
    public void registerNodeTypes(SessionInfo sessionInfo, QNodeTypeDefinition[] nodeTypeDefinitions,
            boolean allowUpdate) throws InvalidNodeTypeDefinitionException, NodeTypeExistsException,
            UnsupportedRepositoryOperationException, RepositoryException {
        // TODO
        throw new UnsupportedOperationException("JCR-2003. Implementation missing");
    }

    /**
     * {@inheritDoc}
     */
    public void unregisterNodeTypes(SessionInfo sessionInfo, Name[] nodeTypeNames)
            throws UnsupportedRepositoryOperationException, NoSuchNodeTypeException, RepositoryException {
        // TODO
        throw new UnsupportedOperationException("JCR-2003. Implementation missing");
    }

    /**
     * {@inheritDoc}
     */
    public void createWorkspace(SessionInfo sessionInfo, String name, String srcWorkspaceName)
            throws AccessDeniedException, UnsupportedRepositoryOperationException, NoSuchWorkspaceException,
            RepositoryException {
        // TODO
        throw new UnsupportedOperationException("JCR-2003. Implementation missing");
    }

    /**
     * {@inheritDoc}
     */
    public void deleteWorkspace(SessionInfo sessionInfo, String name) throws AccessDeniedException,
            UnsupportedRepositoryOperationException, NoSuchWorkspaceException, RepositoryException {
        // TODO
        throw new UnsupportedOperationException("JCR-2003. Implementation missing");
    }

    /**
     *
     * @param sessionInfo
     * @param reportDoc
     * @return
     * @throws RepositoryException
     */
    private Iterator retrieveQNodeTypeDefinitions(SessionInfo sessionInfo, Document reportDoc)
            throws RepositoryException {
        ElementIterator it = DomUtil.getChildren(reportDoc.getDocumentElement(), NodeTypeConstants.NODETYPE_ELEMENT,
                null);
        List ntDefs = new ArrayList();
        NamePathResolver resolver = getNamePathResolver(sessionInfo);
        while (it.hasNext()) {
            ntDefs.add(new QNodeTypeDefinitionImpl(it.nextElement(), resolver, getQValueFactory()));
        }
        // refresh node type definitions map
        synchronized (nodeTypeDefinitions) {
            nodeTypeDefinitions.clear();
            for (Iterator defIt = ntDefs.iterator(); defIt.hasNext();) {
                QNodeTypeDefinition def = (QNodeTypeDefinition) defIt.next();
                nodeTypeDefinitions.put(def.getName(), def);
            }
        }
        return ntDefs.iterator();
    }

    /**
     * The XML elements and attributes used in serialization
     */
    private static final Namespace SV_NAMESPACE = Namespace.getNamespace(Name.NS_SV_PREFIX, Name.NS_SV_URI);
    private static final String NODE_ELEMENT = "node";
    private static final String PROPERTY_ELEMENT = "property";
    private static final String VALUE_ELEMENT = "value";
    private static final String NAME_ATTRIBUTE = "name";
    private static final String TYPE_ATTRIBUTE = "type";

    //------------------------------------------------< Inner Class 'Batch' >---
    private class BatchImpl implements Batch {

        private final SessionInfo sessionInfo;
        private final ItemId targetId;
        private final List methods = new ArrayList();
        private final NamePathResolver resolver;

        private String batchId;

        private boolean isConsumed = false;

        private BatchImpl(ItemId targetId, SessionInfo sessionInfo) throws RepositoryException {
            this.targetId = targetId;
            this.sessionInfo = sessionInfo;
            this.resolver = getNamePathResolver(sessionInfo);
        }

        private HttpClient start() throws RepositoryException {
            checkConsumed();
            String uri = getItemUri(targetId, sessionInfo);
            try {
                // start special 'lock'
                LockMethod method = new LockMethod(uri, TransactionConstants.LOCAL,
                        TransactionConstants.TRANSACTION, null, INFINITE_TIMEOUT, true);
                initMethod(method, sessionInfo, true);

                HttpClient client = getClient(sessionInfo);
                client.executeMethod(method);
                if (method.getStatusCode() == DavServletResponse.SC_PRECONDITION_FAILED) {
                    throw new InvalidItemStateException("Unable to persist transient changes.");
                }
                method.checkSuccess();

                batchId = method.getLockToken();

                return client;
            } catch (IOException e) {
                throw new RepositoryException(e);
            } catch (DavException e) {
                throw ExceptionConverter.generate(e);
            }
        }

        private void end(HttpClient client, boolean commit) throws RepositoryException {
            checkConsumed();

            String uri = getItemUri(targetId, sessionInfo);
            UnLockMethod method = null;
            try {
                // make sure the lock initially created is removed again on the
                // server, asking the server to persist the modifications
                method = new UnLockMethod(uri, batchId);
                initMethod(method, sessionInfo, true);

                // in contrast to standard UNLOCK, the tx-unlock provides a
                // request body.
                method.setRequestBody(new TransactionInfo(commit));
                client.executeMethod(method);
                method.checkSuccess();
                if (sessionInfo instanceof SessionInfoImpl) {
                    ((SessionInfoImpl) sessionInfo).setLastBatchId(batchId);
                }
            } catch (IOException e) {
                throw new RepositoryException(e);
            } catch (DavException e) {
                throw ExceptionConverter.generate(e);
            } finally {
                if (method != null) {
                    // release UNLOCK method
                    method.releaseConnection();
                }
            }
        }

        private void dispose() {
            methods.clear();
            isConsumed = true;
        }

        private void checkConsumed() {
            if (isConsumed) {
                throw new IllegalStateException("Batch has already been consumed.");
            }
        }

        private boolean isEmpty() {
            return methods.isEmpty();
        }

        private Iterator methods() {
            return methods.iterator();
        }

        //----------------------------------------------------------< Batch >---
        /**
         * @see Batch#addNode(NodeId, Name, Name, String)
         */
        public void addNode(NodeId parentId, Name nodeName, Name nodetypeName, String uuid)
                throws RepositoryException {
            checkConsumed();
            try {
                // TODO: TOBEFIXED. WebDAV does not allow MKCOL for existing resource -> problem with SNS
                // use fake name instead (see also #importXML)
                Name fakeName = getNameFactory().create(Name.NS_DEFAULT_URI, UUID.randomUUID().toString());
                String uri = getItemUri(parentId, fakeName, sessionInfo);
                MkColMethod method = new MkColMethod(uri);

                // build 'sys-view' for the node to create and append it as request body
                Document body = DomUtil.BUILDER_FACTORY.newDocumentBuilder().newDocument();
                Element nodeElement = DomUtil.addChildElement(body, NODE_ELEMENT, SV_NAMESPACE);
                String nameAttr = resolver.getJCRName(nodeName);
                DomUtil.setAttribute(nodeElement, NAME_ATTRIBUTE, SV_NAMESPACE, nameAttr);

                // nodetype must never be null
                Element propElement = DomUtil.addChildElement(nodeElement, PROPERTY_ELEMENT, SV_NAMESPACE);
                String name = resolver.getJCRName(NameConstants.JCR_PRIMARYTYPE);
                DomUtil.setAttribute(propElement, NAME_ATTRIBUTE, SV_NAMESPACE, name);
                DomUtil.setAttribute(propElement, TYPE_ATTRIBUTE, SV_NAMESPACE,
                        PropertyType.nameFromValue(PropertyType.NAME));
                name = resolver.getJCRName(nodetypeName);
                DomUtil.addChildElement(propElement, VALUE_ELEMENT, SV_NAMESPACE, name);
                // optional uuid
                if (uuid != null) {
                    propElement = DomUtil.addChildElement(nodeElement, PROPERTY_ELEMENT, SV_NAMESPACE);
                    name = resolver.getJCRName(NameConstants.JCR_UUID);
                    DomUtil.setAttribute(propElement, NAME_ATTRIBUTE, SV_NAMESPACE, name);
                    DomUtil.setAttribute(propElement, TYPE_ATTRIBUTE, SV_NAMESPACE,
                            PropertyType.nameFromValue(PropertyType.STRING));
                    DomUtil.addChildElement(propElement, VALUE_ELEMENT, SV_NAMESPACE, uuid);
                }
                method.setRequestBody(body);

                methods.add(method);
            } catch (IOException e) {
                throw new RepositoryException(e);
            } catch (ParserConfigurationException e) {
                throw new RepositoryException(e);
            }
        }

        /**
         * @see Batch#addProperty(NodeId, Name, QValue)
         */
        public void addProperty(NodeId parentId, Name propertyName, QValue value) throws RepositoryException {
            checkConsumed();
            String uri = getItemUri(parentId, propertyName, sessionInfo);

            PutMethod method = new PutMethod(uri);
            method.setRequestHeader(HEADER_CONTENT_TYPE, JcrValueType.contentTypeFromType(value.getType()));
            method.setRequestEntity(getEntity(value));
            methods.add(method);
        }

        /**
         * @see Batch#addProperty(NodeId, Name, QValue[])
         */
        public void addProperty(NodeId parentId, Name propertyName, QValue[] values) throws RepositoryException {
            checkConsumed();
            // TODO: avoid usage of the ValuesProperty. specially for binary props.
            // TODO: replace by a multipart-POST
            try {
                String uri = getItemUri(parentId, propertyName, sessionInfo);
                Value[] jcrValues = new Value[values.length];
                for (int i = 0; i < values.length; i++) {
                    jcrValues[i] = ValueFormat.getJCRValue(values[i], resolver, valueFactory);
                }
                ValuesProperty vp = new ValuesProperty(jcrValues);
                PutMethod method = new PutMethod(uri);
                method.setRequestBody(vp);

                methods.add(method);
            } catch (IOException e) {
                throw new RepositoryException(e);
            }
        }

        /**
         * @see Batch#setValue(PropertyId, QValue)
         */
        public void setValue(PropertyId propertyId, QValue value)
                throws ValueFormatException, VersionException, LockException, ConstraintViolationException,
                AccessDeniedException, UnsupportedRepositoryOperationException, RepositoryException {
            checkConsumed();
            if (value == null) {
                // setting property value to 'null' is identical to a removal
                remove(propertyId);
            } else {
                RequestEntity ent = getEntity(value);
                String uri = getItemUri(propertyId, sessionInfo);
                // TODO: use PUT in order to avoid the ValuesProperty-PROPPATCH call.
                // TODO: actually not quite correct for PROPPATCH assert that prop really exists.
                PutMethod method = new PutMethod(uri);
                method.setRequestHeader(HEADER_CONTENT_TYPE, JcrValueType.contentTypeFromType(value.getType()));
                method.setRequestEntity(ent);
                methods.add(method);
            }
        }

        /**
         * @see Batch#setValue(PropertyId, QValue[])
         */
        public void setValue(PropertyId propertyId, QValue[] values)
                throws ValueFormatException, VersionException, LockException, ConstraintViolationException,
                AccessDeniedException, UnsupportedRepositoryOperationException, RepositoryException {
            checkConsumed();
            if (values == null) {
                // setting property value to 'null' is identical to a removal
                remove(propertyId);
            } else {
                // TODO: use multipart-POST instead of ValuesProperty
                DavPropertySet setProperties = new DavPropertySet();
                // qualified values must be converted to jcr values
                Value[] jcrValues = new Value[values.length];
                for (int i = 0; i < values.length; i++) {
                    jcrValues[i] = ValueFormat.getJCRValue(values[i], resolver, valueFactory);
                }
                setProperties.add(new ValuesProperty(jcrValues));
                try {
                    String uri = getItemUri(propertyId, sessionInfo);
                    PropPatchMethod method = new PropPatchMethod(uri, setProperties, new DavPropertyNameSet());
                    methods.add(method);
                } catch (IOException e) {
                    throw new RepositoryException(e);
                }
            }
        }

        private RequestEntity getEntity(QValue value) throws RepositoryException {
            // qualified value must be converted to jcr value
            InputStream in;
            int type = value.getType();
            String contentType = JcrValueType.contentTypeFromType(type);
            RequestEntity ent;
            try {
                switch (type) {
                case PropertyType.NAME:
                case PropertyType.PATH:
                    Value v = ValueFormat.getJCRValue(value, resolver, valueFactory);
                    ent = new StringRequestEntity(v.getString(), contentType, "UTF-8");
                    break;
                case PropertyType.BINARY:
                    in = value.getStream();
                    ent = new InputStreamRequestEntity(in, contentType);
                    break;
                default:
                    String str = value.getString();
                    ent = new StringRequestEntity(str, contentType, "UTF-8");
                    break;
                }
            } catch (UnsupportedEncodingException e) {
                // should never get here
                throw new RepositoryException(e.getMessage());
            }
            return ent;
        }

        /**
         * @see Batch#remove(ItemId)
         */
        public void remove(ItemId itemId) throws RepositoryException {
            checkConsumed();
            String uri = getItemUri(itemId, sessionInfo);
            DeleteMethod method = new DeleteMethod(uri);

            methods.add(method);
        }

        /**
         * @see Batch#reorderNodes(NodeId, NodeId, NodeId)
         */
        public void reorderNodes(NodeId parentId, NodeId srcNodeId, NodeId beforeNodeId)
                throws RepositoryException {
            checkConsumed();
            try {
                String uri = getItemUri(parentId, sessionInfo);
                String srcUri = getItemUri(srcNodeId, sessionInfo);
                String srcSegment = Text.getName(srcUri, true);

                OrderPatchMethod method;
                if (beforeNodeId == null) {
                    // move src to the end
                    method = new OrderPatchMethod(uri, OrderingConstants.ORDERING_TYPE_CUSTOM, srcSegment, false);
                } else {
                    // insert src before the targetSegment
                    String beforeUri = getItemUri(beforeNodeId, sessionInfo);
                    String targetSegment = Text.getName(beforeUri, true);
                    method = new OrderPatchMethod(uri, OrderingConstants.ORDERING_TYPE_CUSTOM, srcSegment,
                            targetSegment, true);
                }
                methods.add(method);
            } catch (IOException e) {
                throw new RepositoryException(e);
            }
        }

        /**
         * @see Batch#setMixins(NodeId, Name[])
         */
        public void setMixins(NodeId nodeId, Name[] mixinNodeTypeIds)
                throws NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException,
                AccessDeniedException, UnsupportedRepositoryOperationException, RepositoryException {
            checkConsumed();
            try {
                DavPropertySet setProperties;
                DavPropertyNameSet removeProperties;
                if (mixinNodeTypeIds == null || mixinNodeTypeIds.length == 0) {
                    setProperties = new DavPropertySet();
                    removeProperties = new DavPropertyNameSet();
                    removeProperties.add(ItemResourceConstants.JCR_MIXINNODETYPES);
                } else {
                    String[] ntNames = new String[mixinNodeTypeIds.length];
                    for (int i = 0; i < mixinNodeTypeIds.length; i++) {
                        ntNames[i] = resolver.getJCRName(mixinNodeTypeIds[i]);
                    }
                    setProperties = new DavPropertySet();
                    setProperties
                            .add(new NodeTypeProperty(ItemResourceConstants.JCR_MIXINNODETYPES, ntNames, false));
                    removeProperties = new DavPropertyNameSet();
                }

                String uri = getItemUri(nodeId, sessionInfo);
                PropPatchMethod method = new PropPatchMethod(uri, setProperties, removeProperties);

                methods.add(method);
            } catch (IOException e) {
                throw new RepositoryException(e);
            }
        }

        /**
         * @see Batch#setPrimaryType(NodeId, Name)
         */
        public void setPrimaryType(NodeId nodeId, Name primaryNodeTypeName) throws RepositoryException {
            checkConsumed();
            try {
                DavPropertySet setProperties = new DavPropertySet();
                setProperties.add(new NodeTypeProperty(ItemResourceConstants.JCR_PRIMARYNODETYPE,
                        new String[] { resolver.getJCRName(primaryNodeTypeName) }, false));

                String uri = getItemUri(nodeId, sessionInfo);
                PropPatchMethod method = new PropPatchMethod(uri, setProperties, new DavPropertyNameSet());

                methods.add(method);
            } catch (IOException e) {
                throw new RepositoryException(e);
            }
        }

        /**
         * @see Batch#move(NodeId, NodeId, Name)
         */
        public void move(NodeId srcNodeId, NodeId destParentNodeId, Name destName) throws RepositoryException {
            checkConsumed();
            String uri = getItemUri(srcNodeId, sessionInfo);
            String destUri = getItemUri(destParentNodeId, destName, sessionInfo);
            MoveMethod method = new MoveMethod(uri, destUri, true);

            methods.add(method);
        }
    }

    //----------------------------------------------< NamespaceResolverImpl >---
    /**
     * NamespaceResolver implementation that uses a sessionInfo to determine
     * namespace mappings either from cache or from the server.
     */
    private class NamespaceResolverImpl implements NamespaceResolver {

        private final SessionInfo sessionInfo;

        /**
         * Creates a new namespace resolver using the given session info.
         *
         * @param sessionInfo the session info to contact the repository.
         */
        private NamespaceResolverImpl(SessionInfo sessionInfo) {
            this.sessionInfo = sessionInfo;
        }

        /**
         * @inheritDoc
         */
        public String getURI(String prefix) throws NamespaceException {
            try {
                return getNamespaceURI(sessionInfo, prefix);
            } catch (RepositoryException e) {
                String msg = "Error retrieving namespace uri";
                throw new NamespaceException(msg, e);
            }
        }

        /**
         * @inheritDoc
         */
        public String getPrefix(String uri) throws NamespaceException {
            try {
                return getNamespacePrefix(sessionInfo, uri);
            } catch (RepositoryException e) {
                String msg = "Error retrieving namespace prefix";
                throw new NamespaceException(msg, e);
            }
        }
    }

    //---------------------------------------------< IdentifierResolverImpl >---
    private class IdentifierResolverImpl implements IdentifierResolver {

        private final SessionInfo sessionInfo;

        private IdentifierResolverImpl(SessionInfo sessionInfo) {
            this.sessionInfo = sessionInfo;
        }

        private Path buildPath(String uniqueID) throws RepositoryException {
            String uri = uriResolver.getItemUri(getIdFactory().createNodeId(uniqueID),
                    sessionInfo.getWorkspaceName(), sessionInfo);
            return uriResolver.getQPath(uri, sessionInfo);
        }

        private Path resolvePath(String jcrPath) throws RepositoryException {
            return ((SessionInfoImpl) sessionInfo).getNamePathResolver().getQPath(jcrPath);
        }

        /**
         * @inheritDoc
         */
        public Path getPath(String identifier) throws MalformedPathException {
            try {
                int pos = identifier.indexOf('/');
                if (pos == -1) {
                    // unique id identifier
                    return buildPath(identifier);
                } else if (pos == 0) {
                    // jcr-path identifier
                    return resolvePath(identifier);
                } else {
                    Path p1 = buildPath(identifier.substring(0, pos));
                    Path p2 = resolvePath(identifier.substring(pos));
                    return getPathFactory().create(p1, p2, true);
                }
            } catch (RepositoryException e) {
                throw new MalformedPathException(identifier);
            }
        }

        /**
         * @inheritDoc
         */
        public void checkFormat(String identifier) throws MalformedPathException {
            // cannot be determined. assume ok.
        }
    }

    //-----------------------------------------------< NamePathResolverImpl >---
    /**
     * Implements a namespace resolver based on a session info.
     */
    private class NamePathResolverImpl implements NamePathResolver {

        private final NameResolver nResolver;
        private final PathResolver pResolver;

        private NamePathResolverImpl(SessionInfo sessionInfo) {
            NamespaceResolver nsResolver = new NamespaceResolverImpl(sessionInfo);
            nResolver = new ParsingNameResolver(getNameFactory(), nsResolver);
            IdentifierResolver idResolver = new IdentifierResolverImpl(sessionInfo);
            pResolver = new ParsingPathResolver(getPathFactory(), nResolver, idResolver);
        }

        private NamePathResolverImpl(NamespaceResolver nsResolver) {
            nResolver = new ParsingNameResolver(getNameFactory(), nsResolver);
            pResolver = new ParsingPathResolver(getPathFactory(), nResolver);
        }

        /**
         * @inheritDoc
         */
        public Name getQName(String jcrName) throws IllegalNameException, NamespaceException {
            return nResolver.getQName(jcrName);
        }

        /**
         * @inheritDoc
         */
        public String getJCRName(Name qName) throws NamespaceException {
            return nResolver.getJCRName(qName);
        }

        /**
         * @inheritDoc
         */
        public Path getQPath(String path) throws MalformedPathException, IllegalNameException, NamespaceException {
            return pResolver.getQPath(path);
        }

        /**
         * @inheritDoc
         */
        public Path getQPath(String path, boolean normalizeIdentifier)
                throws MalformedPathException, IllegalNameException, NamespaceException {
            return pResolver.getQPath(path, normalizeIdentifier);
        }

        /**
         * @inheritDoc
         */
        public String getJCRPath(Path path) throws NamespaceException {
            return pResolver.getJCRPath(path);
        }
    }

    /**
     * Namespace Cache
     */
    private static class NamespaceCache extends AbstractNamespaceResolver {

        private final HashMap prefixToURI = new HashMap();
        private final HashMap uriToPrefix = new HashMap();

        public Map getNamespaces() {
            return new HashMap(prefixToURI);
        }

        public void add(String prefix, String uri) {
            prefixToURI.put(prefix, uri);
            uriToPrefix.put(uri, prefix);
        }

        public void remove(String prefix, String uri) {
            prefixToURI.remove(prefix);
            uriToPrefix.remove(uri);
        }

        //----------------------------------------------< NamespaceResolver >---

        public String getURI(String prefix) throws NamespaceException {
            String uri = (String) prefixToURI.get(prefix);
            if (uri != null) {
                return uri;
            } else {
                throw new NamespaceException(prefix + ": is not a registered namespace prefix.");
            }
        }

        public String getPrefix(String uri) throws NamespaceException {
            String prefix = (String) uriToPrefix.get(uri);
            if (prefix != null) {
                return prefix;
            } else {
                throw new NamespaceException(uri + ": is not a registered namespace uri.");
            }
        }
    }
}