org.jlibrary.core.jcr.JCRRepositoryService.java Source code

Java tutorial

Introduction

Here is the source code for org.jlibrary.core.jcr.JCRRepositoryService.java

Source

/*
* jLibrary, Open Source Document Management System
* 
* Copyright (c) 2003-2006, Martn Prez Marin, Blandware (represented by
* Andrey Grebnev), and individual contributors as indicated by the
* @authors tag. See copyright.txt in the distribution for a full listing of
* individual contributors. All rights reserved.
* 
* This is free software; you can redistribute it and/or modify it
* under the terms of the Modified BSD License as published by the Free 
* Software Foundation.
* 
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the Modified
* BSD License for more details.
* 
* You should have received a copy of the Modified BSD License along with 
* this software; if not, write to the Free Software Foundation, Inc., 
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA, or see the
* FSF site: http://www.fsf.org.
*/
package org.jlibrary.core.jcr;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import javax.jcr.AccessDeniedException;
import javax.jcr.ItemNotFoundException;
import javax.jcr.NodeIterator;
import javax.jcr.PathNotFoundException;
import javax.jcr.ReferentialIntegrityException;
import javax.jcr.Session;
import javax.jcr.SimpleCredentials;
import javax.jcr.Value;
import javax.jcr.Workspace;
import javax.jcr.query.QueryManager;
import javax.jcr.query.QueryResult;
import javax.jcr.version.Version;
import javax.jcr.version.VersionHistory;
import javax.jcr.version.VersionIterator;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.jackrabbit.JcrConstants;
import org.apache.jackrabbit.core.WorkspaceImpl;
import org.apache.jackrabbit.util.Text;
import org.jlibrary.core.entities.Author;
import org.jlibrary.core.entities.Bookmark;
import org.jlibrary.core.entities.Category;
import org.jlibrary.core.entities.Directory;
import org.jlibrary.core.entities.Document;
import org.jlibrary.core.entities.Favorite;
import org.jlibrary.core.entities.Lock;
import org.jlibrary.core.entities.Node;
import org.jlibrary.core.entities.Note;
import org.jlibrary.core.entities.Relation;
import org.jlibrary.core.entities.Repository;
import org.jlibrary.core.entities.RepositoryInfo;
import org.jlibrary.core.entities.ResourceNode;
import org.jlibrary.core.entities.Ticket;
import org.jlibrary.core.entities.Types;
import org.jlibrary.core.entities.User;
import org.jlibrary.core.jcr.modules.JCRAuthorsModule;
import org.jlibrary.core.jcr.modules.JCRBookmarksModule;
import org.jlibrary.core.jcr.modules.JCRCategoriesModule;
import org.jlibrary.core.jcr.modules.JCRCleanupModule;
import org.jlibrary.core.jcr.modules.JCRFavoritesModule;
import org.jlibrary.core.jcr.modules.JCRImportExportModule;
import org.jlibrary.core.jcr.modules.JCRLocksModule;
import org.jlibrary.core.jcr.modules.JCRResourcesModule;
import org.jlibrary.core.locking.ResourceLockedException;
import org.jlibrary.core.properties.AuthorProperties;
import org.jlibrary.core.properties.CategoryProperties;
import org.jlibrary.core.properties.CustomPropertyDefinition;
import org.jlibrary.core.properties.DirectoryProperties;
import org.jlibrary.core.properties.DocumentProperties;
import org.jlibrary.core.properties.PropertyDef;
import org.jlibrary.core.properties.RepositoryProperties;
import org.jlibrary.core.properties.ResourceNodeProperties;
import org.jlibrary.core.repository.RepositoryService;
import org.jlibrary.core.repository.exception.AuthorAlreadyExistsException;
import org.jlibrary.core.repository.exception.AuthorNotFoundException;
import org.jlibrary.core.repository.exception.CategoryAlreadyExistsException;
import org.jlibrary.core.repository.exception.CategoryNotFoundException;
import org.jlibrary.core.repository.exception.NodeNotFoundException;
import org.jlibrary.core.repository.exception.RepositoryAlreadyExistsException;
import org.jlibrary.core.repository.exception.RepositoryException;
import org.jlibrary.core.repository.exception.RepositoryNotFoundException;
import org.jlibrary.core.security.SecurityException;
import org.jlibrary.core.util.FileUtils;
import org.jlibrary.core.util.SaveUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * <p>This is the default implementation of RepositoryService interface. It works 
 * localy directly talking to the repository. It uses several helper modules 
 * to delegate common operations. Use it on your applications if you wish 
 * good performance as an alternative to the web services-based approach.</p> 
 * 
 * <p>This repository service is completly based on the JSR-170 specification 
 * and also slightly tighted to Apache Jackrabbit reference implementation.</p>
 * 
 * @author mpermar
 *
 */
public class JCRRepositoryService implements RepositoryService {

    static Logger logger = LoggerFactory.getLogger(JCRRepositoryService.class);

    private JCRResourcesModule resourcesModule;
    private JCRAuthorsModule authorsModule;
    private JCRCategoriesModule categoriesModule;
    private JCRLocksModule locksModule;
    private JCRFavoritesModule favoritesModule;
    private JCRBookmarksModule bookmarksModule;
    private JCRImportExportModule importExportModule;
    private JCRCleanupModule cleanupModule;

    private JCRRepositoryBuilder builder;

    public JCRRepositoryService() {

        resourcesModule = new JCRResourcesModule(this);
        authorsModule = new JCRAuthorsModule();
        categoriesModule = new JCRCategoriesModule();
        locksModule = new JCRLocksModule();
        favoritesModule = new JCRFavoritesModule();
        bookmarksModule = new JCRBookmarksModule();
        importExportModule = new JCRImportExportModule();
        cleanupModule = new JCRCleanupModule();
        builder = new JCRRepositoryBuilder();
    }

    public Repository createRepository(Ticket ticket, String name, String description, User creator)
            throws RepositoryAlreadyExistsException, RepositoryException, SecurityException {

        return builder.createRepository(ticket, name, description, creator);
    }

    public Directory createDirectory(Ticket ticket, DirectoryProperties properties)
            throws RepositoryException, SecurityException {

        String name = (String) properties.getProperty(DirectoryProperties.DIRECTORY_NAME).getValue();
        String parentId = (String) properties.getProperty(DirectoryProperties.DIRECTORY_PARENT).getValue();
        String description = (String) properties.getProperty(DirectoryProperties.DIRECTORY_DESCRIPTION).getValue();

        return createDirectory(ticket, name, description, parentId);
    }

    public Directory createDirectory(Ticket ticket, String name, String description, String parentId)
            throws RepositoryException, SecurityException {

        try {

            SessionManager manager = SessionManager.getInstance();
            Session session = manager.getSession(ticket);

            javax.jcr.Node root = JCRUtils.getRootNode(session);
            javax.jcr.Node parent = session.getNodeByUUID(parentId);

            Object syncLock = LockUtility.obtainLock(parent);

            javax.jcr.Node child;
            synchronized (syncLock) {
                if (!JCRSecurityService.canWrite(parent, ticket.getUser().getId())) {
                    throw new SecurityException(SecurityException.NOT_ENOUGH_PERMISSIONS);
                }

                String escapedName = JCRUtils.buildValidChildNodeName(parent, null, name);
                child = parent.addNode(escapedName, JCRConstants.JCR_FOLDER);
                name = Text.unescape(escapedName);
                child.addMixin(JCRConstants.JCR_REFERENCEABLE);
                child.addMixin(JCRConstants.JCR_LOCKABLE);
                child.addMixin(JLibraryConstants.DIRECTORY_MIXIN);

                child.setProperty(JLibraryConstants.JLIBRARY_NAME, name);
                child.setProperty(JLibraryConstants.JLIBRARY_DESCRIPTION, description);
                child.setProperty(JLibraryConstants.JLIBRARY_CREATED, Calendar.getInstance());
                child.setProperty(JLibraryConstants.JLIBRARY_IMPORTANCE, Node.IMPORTANCE_MEDIUM.longValue());
                child.setProperty(JLibraryConstants.JLIBRARY_CREATOR, ticket.getUser().getId());
                child.setProperty(JLibraryConstants.JLIBRARY_TYPECODE, Types.FOLDER.longValue());
                child.setProperty(JLibraryConstants.JLIBRARY_POSITION, 0);
                child.setProperty(JLibraryConstants.JLIBRARY_SIZE, 0);

                String path = getPath(parent) + obtainAvailableDirectoryName(parent, child);

                child.setProperty(JLibraryConstants.JLIBRARY_PATH, path);

                // Handle directory restrictions
                child.setProperty(JLibraryConstants.JLIBRARY_RESTRICTIONS, new Value[] {});
                javax.jcr.Node userNode = JCRSecurityService.getUserNode(session, ticket.getUser().getId());
                addRestrictionsToNode(child, parent, userNode);
                if (ticket.isAutocommit()) {
                    SaveUtils.safeSaveSession(session);
                }
            }
            return JCRAdapter.createDirectory(child, parentId, root.getUUID(), ticket.getUser().getId());
        } catch (Exception e) {
            throw new RepositoryException(e);
        }
    }

    public static void addRestrictionsToNode(javax.jcr.Node node, javax.jcr.Node parent, javax.jcr.Node userNode)
            throws javax.jcr.RepositoryException {

        List restrictions = JCRAdapter.obtainRestrictions(parent);
        if (!restrictions.contains(userNode.getUUID())) {
            restrictions.add(userNode.getUUID());
        }
        for (Object id : restrictions) {
            JCRUtils.addNodeToProperty((String) id, node, JLibraryConstants.JLIBRARY_RESTRICTIONS);
        }
    }

    public String getPath(javax.jcr.Node node) throws RepositoryException {

        try {
            if (node.hasProperty(JLibraryConstants.JLIBRARY_CREATED)) {
                javax.jcr.Property pathProperty = node.getProperty(JLibraryConstants.JLIBRARY_PATH);

                if (pathProperty == null) {
                    return "/";
                } else {
                    String path = pathProperty.getValue().getString();
                    if (!path.endsWith("/")) {
                        return path + "/";
                    }
                    return path;
                }
            } else {
                return "/";
            }
        } catch (PathNotFoundException e) {
            logger.error(e.getMessage(), e);
            throw new RepositoryException(e);
        } catch (javax.jcr.RepositoryException e) {
            logger.error(e.getMessage(), e);
            throw new RepositoryException(e);
        }

    }

    public void removeDirectory(Ticket ticket, String directoryId) throws RepositoryException, SecurityException {

        SessionManager manager = SessionManager.getInstance();
        Session session = manager.getSession(ticket);

        try {
            javax.jcr.Node directory = session.getNodeByUUID(directoryId);

            if (!JCRSecurityService.canWrite(directory, ticket.getUser().getId())) {
                throw new SecurityException(SecurityException.NOT_ENOUGH_PERMISSIONS);
            }

            // Remove favorites
            favoritesModule.removeFavoriteReferences(directory);

            // process references
            JCRUtils.removeReferences(directory);

            if (JCRUtils.allowsPhysicalDeletes(session)) {
                directory.remove();
            } else {
                JCRUtils.deactivate(directory);
            }

            if (ticket.isAutocommit()) {
                SaveUtils.safeSaveSession(session);
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            throw new RepositoryException(e);
        }

    }

    public List findAllRepositoriesInfo(Ticket ticket) throws RepositoryException {

        try {
            javax.jcr.Session systemSession = SessionManager.getInstance().getSystemSession();
            String[] workspaces = systemSession.getWorkspace().getAccessibleWorkspaceNames();

            ArrayList list = new ArrayList();
            for (int i = 0; i < workspaces.length; i++) {
                RepositoryInfo info = new RepositoryInfo();
                info.setId(workspaces[i]);
                info.setName(workspaces[i]);
                list.add(info);
            }

            return list;
        } catch (javax.jcr.RepositoryException e) {
            logger.error(e.getMessage(), e);
            throw new RepositoryException(e);
        }
    }

    public Repository findRepository(String name, Ticket ticket)
            throws RepositoryNotFoundException, RepositoryException, SecurityException {

        javax.jcr.Session session = null;
        try {
            SimpleCredentials creds = new SimpleCredentials(ticket.getUser().getName(),
                    ticket.getUser().getPassword().toCharArray());

            javax.jcr.Repository repository = SessionManager.getInstance().getRepository();
            // Change the name to lowercase
            name = name.toLowerCase();
            session = repository.login(creds, name);

            Repository jLibraryRepository = null;

            // Normal case, we want to load a jlibrary repository
            javax.jcr.Node jLibraryRootNode = JCRUtils.getRootNode(session);

            if (!JCRSecurityService.canRead(jLibraryRootNode, ticket.getUser().getId())) {
                throw new SecurityException(SecurityException.NOT_ENOUGH_PERMISSIONS);
            }

            jLibraryRepository = JCRAdapter.createRepository(ticket, name, jLibraryRootNode);

            return jLibraryRepository;

        } catch (javax.jcr.NoSuchWorkspaceException nswe) {
            throw new RepositoryNotFoundException();
        } catch (javax.jcr.RepositoryException e) {
            logger.error(e.getMessage(), e);
            throw new RepositoryException(e);
        } finally {
            if (session != null) {
                session.logout();
            }
        }
    }

    public void deleteRepository(Ticket ticket) throws RepositoryException, SecurityException {

        //TODO: Improve this method when Jackrabbit support workspace deletes

        try {
            javax.jcr.Session session = SessionManager.getInstance().getSession(ticket);
            if (session == null) {
                throw new RepositoryException("Session has expired. Please log in again.");
            }
            javax.jcr.Node repositoryNode = JCRUtils.getRootNode(session);
            String name = repositoryNode.getProperty(JLibraryConstants.JLIBRARY_NAME).getString();

            String workspacePath = ((WorkspaceImpl) session.getWorkspace()).getConfig().getHomeDir();
            session.logout();
            SessionManager.getInstance().dettach(ticket);

            File f = new File(workspacePath);
            try {
                org.apache.commons.io.FileUtils.forceDelete(f);
            } catch (IOException e) {
                //logger.error(e.getMessage(),e);
            }
            javax.jcr.Session systemSession = SessionManager.getInstance().getSystemSession();
            cleanupModule.scheduleForDelete(systemSession, name, workspacePath);
        } catch (javax.jcr.RepositoryException e) {
            logger.error(e.getMessage(), e);
            throw new RepositoryException(e);
        }
    }

    /**
     * Note that destinationRepository is not used on this implementation
     * 
     * @see RepositoryService#copyDirectory(Ticket, String, String, String)
     */
    public Directory copyDirectory(Ticket ticket, String sourceId, String destinationId,
            String destinationRepository) throws RepositoryException, SecurityException {

        try {
            javax.jcr.Node node = internalCopyNode(ticket, sourceId, destinationId);
            javax.jcr.Node root = JCRUtils.getRootNode(node.getSession());
            return JCRAdapter.createDirectory(node, node.getParent().getUUID(), root.getUUID(),
                    ticket.getUser().getId());
        } catch (javax.jcr.RepositoryException e) {
            logger.error(e.getMessage(), e);
            throw new RepositoryException(e);
        }
    }

    /**
     * Note that destinationRepository is not used on this implementation
     * 
     * @see RepositoryService#copyDocument(Ticket, String, String, String)
     */
    public Document copyDocument(Ticket ticket, String sourceId, String destinationId, String destinationRepository)
            throws RepositoryException, SecurityException {

        try {
            javax.jcr.Node node = internalCopyNode(ticket, sourceId, destinationId);
            javax.jcr.Node root = JCRUtils.getRootNode(node.getSession());
            return JCRAdapter.createDocument(node, node.getParent().getUUID(), root.getUUID());
        } catch (javax.jcr.RepositoryException e) {
            logger.error(e.getMessage(), e);
            throw new RepositoryException(e);
        }
    }

    /**
     * Note that destinationRepository is not used on this implementation
     * 
     * @see RepositoryService#moveDirectory(Ticket, String, String, String)
     */
    public Directory moveDirectory(Ticket ticket, String sourceId, String destinationId,
            String destinationRepository) throws RepositoryException, SecurityException {

        try {
            javax.jcr.Node node = internalMoveNode(ticket, sourceId, destinationId);
            javax.jcr.Node root = JCRUtils.getRootNode(node.getSession());
            return JCRAdapter.createDirectory(node, node.getParent().getUUID(), root.getUUID(),
                    ticket.getUser().getId());
        } catch (javax.jcr.RepositoryException e) {
            logger.error(e.getMessage(), e);
            throw new RepositoryException(e);
        }
    }

    /**
     * Note that destinationRepository is not used on this implementation
     * 
     * @see RepositoryService#moveDocument(Ticket, String, String, String)
     */
    public Document moveDocument(Ticket ticket, String documentId, String directoryId, String destinationRepository)
            throws RepositoryException, SecurityException {

        try {
            javax.jcr.Node node = internalMoveNode(ticket, documentId, directoryId);
            javax.jcr.Node root = JCRUtils.getRootNode(node.getSession());
            return JCRAdapter.createDocument(node, node.getParent().getUUID(), root.getUUID());
        } catch (javax.jcr.RepositoryException e) {
            logger.error(e.getMessage(), e);
            throw new RepositoryException(e);
        }
    }

    public List createDocuments(Ticket ticket, List properties) throws RepositoryException, SecurityException {

        List nodes = new ArrayList();
        List documents = new ArrayList();

        SessionManager manager = SessionManager.getInstance();
        Session session = manager.getSession(ticket);

        try {
            javax.jcr.Node root = JCRUtils.getRootNode(session);

            //The operation will be synchronized on the first element. 
            //TODO: Improve synchronization here
            DocumentProperties first = (DocumentProperties) properties.get(0);
            String parentId = (String) first.getProperty(DocumentProperties.DOCUMENT_PARENT).getValue();
            javax.jcr.Node parent = session.getNodeByUUID(parentId);
            Object syncLock = LockUtility.obtainLock(parent);
            Iterator it;
            synchronized (syncLock) {
                it = properties.iterator();
                while (it.hasNext()) {
                    DocumentProperties docProperties = (DocumentProperties) it.next();

                    javax.jcr.Node node = internalCreateDocument(session, ticket, docProperties, null);
                    nodes.add(node);
                }
                if (ticket.isAutocommit()) {
                    SaveUtils.safeSaveSession(session);
                }
            }
            it = nodes.iterator();
            while (it.hasNext()) {
                javax.jcr.Node node = (javax.jcr.Node) it.next();
                documents.add(JCRAdapter.createDocument(node, node.getParent().getUUID(), root.getUUID()));
            }
            nodes.clear();
        } catch (AccessDeniedException e) {
            logger.error(e.getMessage(), e);
            throw new SecurityException(e);
        } catch (javax.jcr.RepositoryException e) {
            logger.error(e.getMessage(), e);
            throw new RepositoryException(e);
        }

        return documents;
    }

    public Document createDocument(Ticket ticket, DocumentProperties properties)
            throws RepositoryException, SecurityException {
        return createDocument(ticket, properties, null);
    }

    public Document createDocument(Ticket ticket, DocumentProperties properties, InputStream content)
            throws RepositoryException, SecurityException {

        SessionManager manager = SessionManager.getInstance();
        Session session = manager.getSession(ticket);
        try {
            String parentId = (String) properties.getProperty(DocumentProperties.DOCUMENT_PARENT).getValue();
            javax.jcr.Node parent = session.getNodeByUUID(parentId);
            javax.jcr.Node node = null;
            synchronized (LockUtility.obtainLock(parent)) {
                node = internalCreateDocument(session, ticket, properties, content);
                if (ticket.isAutocommit()) {
                    SaveUtils.safeSaveSession(session);
                    if (content != null) {
                        // data stream has been given, so we should probably
                        // create a version
                        node.checkin();
                        node.checkout();
                    }
                }
            }
            javax.jcr.Node root = JCRUtils.getRootNode(session);
            return JCRAdapter.createDocument(node, node.getParent().getUUID(), root.getUUID());
        } catch (AccessDeniedException e) {
            logger.error(e.getMessage(), e);
            throw new SecurityException(e);
        } catch (javax.jcr.RepositoryException e) {
            logger.error(e.getMessage(), e);
            throw new RepositoryException(e);
        }
    }

    private javax.jcr.Node internalCreateDocument(javax.jcr.Session session, Ticket ticket,
            DocumentProperties properties, InputStream contentStream)
            throws RepositoryException, SecurityException {

        ByteArrayInputStream bais = null;
        try {
            String parentId = (String) properties.getProperty(DocumentProperties.DOCUMENT_PARENT).getValue();
            String name = ((String) properties.getProperty(DocumentProperties.DOCUMENT_NAME).getValue());
            String path = ((String) properties.getProperty(DocumentProperties.DOCUMENT_PATH).getValue());
            String extension = FileUtils.getExtension(path);
            String description = ((String) properties.getProperty(DocumentProperties.DOCUMENT_DESCRIPTION)
                    .getValue());
            Integer typecode = ((Integer) properties.getProperty(DocumentProperties.DOCUMENT_TYPECODE).getValue());
            Integer position = ((Integer) properties.getProperty(DocumentProperties.DOCUMENT_POSITION).getValue());
            Integer importance = ((Integer) properties.getProperty(DocumentProperties.DOCUMENT_IMPORTANCE)
                    .getValue());
            String title = (String) properties.getProperty(DocumentProperties.DOCUMENT_TITLE).getValue();
            String url = ((String) properties.getProperty(DocumentProperties.DOCUMENT_URL).getValue());
            String language = ((String) properties.getProperty(DocumentProperties.DOCUMENT_LANGUAGE).getValue());
            Author author = ((Author) properties.getProperty(DocumentProperties.DOCUMENT_AUTHOR).getValue());
            Date metadataDate = ((Date) properties.getProperty(DocumentProperties.DOCUMENT_CREATION_DATE)
                    .getValue());
            Calendar date = Calendar.getInstance();
            date.setTime(metadataDate);

            String keywords = ((String) properties.getProperty(DocumentProperties.DOCUMENT_KEYWORDS).getValue());

            javax.jcr.Node parent = session.getNodeByUUID(parentId);

            if (!JCRSecurityService.canWrite(parent, ticket.getUser().getId())) {
                throw new SecurityException(SecurityException.NOT_ENOUGH_PERMISSIONS);
            }
            String escapedName = JCRUtils.buildValidChildNodeName(parent, extension, name);
            name = Text.unescape(escapedName);
            if ((extension != null) && !escapedName.endsWith(extension)) {
                escapedName += extension;
            }

            javax.jcr.Node child = parent.addNode(escapedName, JCRConstants.JCR_FILE);

            child.addMixin(JCRConstants.JCR_REFERENCEABLE);
            child.addMixin(JCRConstants.JCR_LOCKABLE);
            child.addMixin(JCRConstants.JCR_VERSIONABLE);
            child.addMixin(JLibraryConstants.DOCUMENT_MIXIN);

            child.setProperty(JLibraryConstants.JLIBRARY_NAME, name);
            child.setProperty(JLibraryConstants.JLIBRARY_DESCRIPTION, description);
            child.setProperty(JLibraryConstants.JLIBRARY_CREATED, Calendar.getInstance());
            child.setProperty(JLibraryConstants.JLIBRARY_IMPORTANCE, importance.longValue());
            child.setProperty(JLibraryConstants.JLIBRARY_CREATOR, ticket.getUser().getId());
            child.setProperty(JLibraryConstants.JLIBRARY_TYPECODE, typecode.longValue());
            child.setProperty(JLibraryConstants.JLIBRARY_POSITION, position.longValue());

            child.setProperty(JLibraryConstants.JLIBRARY_TITLE, title);
            child.setProperty(JLibraryConstants.JLIBRARY_DOCUMENT_URL, url);
            child.setProperty(JLibraryConstants.JLIBRARY_KEYWORDS, keywords);
            child.setProperty(JLibraryConstants.JLIBRARY_LANGUAGE, language);
            child.setProperty(JLibraryConstants.JLIBRARY_CREATION_DATE, date);

            child.setProperty(JLibraryConstants.JLIBRARY_PATH, getPath(parent) + FileUtils.getFileName(path));

            // Handle authors
            if (author.equals(Author.UNKNOWN)) {
                child.setProperty(JLibraryConstants.JLIBRARY_AUTHOR, authorsModule.findUnknownAuthor(ticket));
            } else {
                javax.jcr.Node authorNode = session.getNodeByUUID(author.getId());
                child.setProperty(JLibraryConstants.JLIBRARY_AUTHOR, authorNode.getUUID());
            }

            // Handle document categories
            child.setProperty(JLibraryConstants.JLIBRARY_CATEGORIES, new Value[] {});
            PropertyDef[] categories = properties.getPropertyList(DocumentProperties.DOCUMENT_ADD_CATEGORY);
            if (categories != null) {
                for (int i = 0; i < categories.length; i++) {
                    String category = (String) categories[i].getValue();
                    javax.jcr.Node categoryNode = categoriesModule.getCategoryNode(session, category);
                    categoriesModule.addCategory(ticket, categoryNode, child);
                }
            } else {
                categoriesModule.addUnknownCategory(ticket, child);
            }

            // Handle document relations
            child.setProperty(JLibraryConstants.JLIBRARY_RELATIONS, new Value[] {});

            // Handle document restrictions
            child.setProperty(JLibraryConstants.JLIBRARY_RESTRICTIONS, new Value[] {});
            javax.jcr.Node userNode = JCRSecurityService.getUserNode(session, ticket.getUser().getId());
            addRestrictionsToNode(child, parent, userNode);

            // Handle document resources. 
            child.setProperty(JLibraryConstants.JLIBRARY_RESOURCES, new Value[] {});

            javax.jcr.Node resNode = child.addNode(JcrConstants.JCR_CONTENT, JCRConstants.JCR_RESOURCE);
            resNode.addMixin(JLibraryConstants.CONTENT_MIXIN);

            InputStream streamToSet;
            if (contentStream == null) {
                byte[] content = (byte[]) properties.getProperty(DocumentProperties.DOCUMENT_CONTENT).getValue();
                if (content == null) {
                    // Empty file
                    content = new byte[] {};
                }
                bais = new ByteArrayInputStream(content);
                streamToSet = bais;
            } else {
                streamToSet = contentStream;
            }

            //TODO: Handle encoding
            String mimeType = Types.getMimeTypeForExtension(FileUtils.getExtension(path));
            resNode.setProperty(JCRConstants.JCR_MIME_TYPE, mimeType);
            resNode.setProperty(JCRConstants.JCR_ENCODING, JCRConstants.DEFAULT_ENCODING);
            resNode.setProperty(JCRConstants.JCR_DATA, streamToSet);
            Calendar lastModified = Calendar.getInstance();
            lastModified.setTimeInMillis(new Date().getTime());
            resNode.setProperty(JCRConstants.JCR_LAST_MODIFIED, lastModified);

            child.setProperty(JLibraryConstants.JLIBRARY_SIZE,
                    resNode.getProperty(JCRConstants.JCR_DATA).getLength());

            return child;
        } catch (Throwable e) {
            logger.error(e.getMessage(), e);
            throw new RepositoryException(e);
        } finally {
            if (bais != null) {
                try {
                    bais.close();
                } catch (IOException ioe) {
                    logger.error(ioe.getMessage(), ioe);
                    throw new RepositoryException(ioe);
                }
            }
        }
    }

    public void removeDocument(Ticket ticket, String docId)
            throws RepositoryException, SecurityException, ResourceLockedException {

        SessionManager manager = SessionManager.getInstance();
        Session session = manager.getSession(ticket);

        try {
            javax.jcr.Node document = session.getNodeByUUID(docId);

            locksModule.checkLockAccess(ticket, document);

            if (!JCRSecurityService.canWrite(document, ticket.getUser().getId())) {
                throw new SecurityException(SecurityException.NOT_ENOUGH_PERMISSIONS);
            }

            // Remove favorites
            favoritesModule.removeFavoriteReferences(document);
            // Remove the other references
            JCRUtils.removeReferences(document);

            if (JCRUtils.allowsPhysicalDeletes(session)) {
                //TODO: Replace this when JCR-134 is fixed
                //See more details at http://issues.apache.org/jira/browse/JCR-134
                VersionHistory vh = document.getVersionHistory();
                VersionIterator vi = vh.getAllVersions();
                while (vi.hasNext()) {
                    Version currenVersion = vi.nextVersion();
                    String versionName = currenVersion.getName();
                    if (!versionName.equals("jcr:rootVersion") && !versionName.equals("1.0")
                            && !versionName.equals("1.1") && !versionName.equals("1.2")
                            && !versionName.equals("1.3")) {
                        try {
                            vh.removeVersion(versionName);
                        } catch (ReferentialIntegrityException rif) {
                            logger.error("Unable to remove version: " + rif.getMessage());
                        }
                    }
                }

                document.remove();
            } else {
                JCRUtils.deactivate(document);
            }
            if (ticket.isAutocommit()) {
                SaveUtils.safeSaveSession(session);
            }
        } catch (ResourceLockedException rle) {
            throw rle;
        } catch (RepositoryException re) {
            throw re;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            throw new RepositoryException(e);
        }
    }

    public void loadDocumentContent(String docId, Ticket ticket, OutputStream stream)
            throws RepositoryException, SecurityException {

        SessionManager manager = SessionManager.getInstance();
        Session session = manager.getSession(ticket);

        InputStream is = null;
        try {
            javax.jcr.Node resource = session.getNodeByUUID(docId);

            if (!JCRSecurityService.canRead(resource, ticket.getUser().getId())) {
                throw new SecurityException(SecurityException.NOT_ENOUGH_PERMISSIONS);
            }

            javax.jcr.Node nodeContent = resource.getNode(JCRConstants.JCR_CONTENT);
            is = nodeContent.getProperty("jcr:data").getValue().getStream();
            IOUtils.copy(is, stream);

        } catch (IOException e) {
            logger.error(e.getMessage(), e);
            throw new RepositoryException(e);
        } catch (javax.jcr.RepositoryException e) {
            logger.error(e.getMessage(), e);
            throw new RepositoryException(e);
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException ioe) {
                    logger.error(ioe.getMessage(), ioe);
                    throw new RepositoryException(ioe);
                }
            }
        }
    }

    public byte[] loadDocumentContent(String docId, Ticket ticket) throws RepositoryException, SecurityException {

        SessionManager manager = SessionManager.getInstance();
        Session session = manager.getSession(ticket);
        ByteArrayOutputStream baos = null;
        try {
            baos = new ByteArrayOutputStream();
            loadDocumentContent(docId, ticket, baos);
            return baos.toByteArray();

        } finally {
            if (baos != null) {
                try {
                    baos.close();
                } catch (IOException ioe) {
                    logger.error(ioe.getMessage(), ioe);
                    throw new RepositoryException(ioe);
                }
            }
        }
    }

    public Category findCategoryByPath(Ticket ticket, String path)
            throws RepositoryException, CategoryNotFoundException, SecurityException {

        return categoriesModule.findCategoryByPath(ticket, path);
    }

    public Node findNodeByPath(Ticket ticket, String path)
            throws RepositoryException, NodeNotFoundException, SecurityException {

        SessionManager manager = SessionManager.getInstance();
        Session session = manager.getSession(ticket);
        if (!path.startsWith(JLibraryConstants.JLIBRARY_ROOT)) {
            if (path.charAt(0) == '/') {
                path = path.substring(1, path.length());
            }
            path = JLibraryConstants.JLIBRARY_ROOT + "/" + path;
        }
        try {

            javax.jcr.Node root = JCRUtils.getRootNode(session);
            javax.jcr.Node node = session.getRootNode().getNode(path);
            if (!JCRSecurityService.canRead(node, ticket.getUser().getId())) {
                throw new SecurityException(SecurityException.NOT_ENOUGH_PERMISSIONS);
            }

            javax.jcr.Node parent = node.getParent();
            String uuid = null;
            if (parent.isNodeType(JCRConstants.JCR_REFERENCEABLE)) {
                uuid = parent.getUUID();
            } else {
                uuid = root.getUUID();
            }

            if (node.isNodeType(JLibraryConstants.DOCUMENT_MIXIN)) {
                return JCRAdapter.createDocument(node, uuid, root.getUUID());
            } else if (node.isNodeType(JLibraryConstants.DIRECTORY_MIXIN)) {
                return JCRAdapter.createDirectory(node, uuid, root.getUUID(), ticket.getUser().getId());
            } else if (node.isNodeType(JLibraryConstants.RESOURCE_MIXIN)) {
                return JCRAdapter.createResource(node, uuid, root.getUUID());
            }
            throw new NodeNotFoundException();
        } catch (PathNotFoundException pnfe) {
            logger.error("Node with path [" + path + "] not found");
            throw new NodeNotFoundException();
        } catch (ItemNotFoundException infe) {
            logger.error(infe.getMessage(), infe);
            throw new NodeNotFoundException(infe);
        } catch (javax.jcr.RepositoryException e) {
            logger.error(e.getMessage(), e);
            throw new RepositoryException(e);
        }
    }

    public Node findNode(Ticket ticket, String id)
            throws RepositoryException, NodeNotFoundException, SecurityException {

        SessionManager manager = SessionManager.getInstance();
        Session session = manager.getSession(ticket);

        try {
            javax.jcr.Node node = session.getNodeByUUID(id);
            javax.jcr.Node parent = node.getParent();
            javax.jcr.Node root = JCRUtils.getRootNode(session);

            String uuid = null;
            if (parent.isNodeType(JCRConstants.JCR_REFERENCEABLE)) {
                uuid = parent.getUUID();
            } else {
                uuid = root.getUUID();
            }

            if (!JCRSecurityService.canRead(node, ticket.getUser().getId())) {
                throw new SecurityException(SecurityException.NOT_ENOUGH_PERMISSIONS);
            }

            if (node.isNodeType(JLibraryConstants.DOCUMENT_MIXIN)) {
                return JCRAdapter.createDocument(node, uuid, root.getUUID());
            } else if (node.isNodeType(JLibraryConstants.DIRECTORY_MIXIN)) {
                return JCRAdapter.createDirectory(node, uuid, root.getUUID(), ticket.getUser().getId());
            } else if (node.isNodeType(JLibraryConstants.RESOURCE_MIXIN)) {
                return JCRAdapter.createResource(node, uuid, root.getUUID());
            }
            throw new NodeNotFoundException();
        } catch (ItemNotFoundException infe) {
            logger.error(infe.getMessage(), infe);
            throw new NodeNotFoundException(infe);
        } catch (javax.jcr.RepositoryException e) {
            logger.error(e.getMessage(), e);
            throw new RepositoryException(e);
        }
    }

    public Document findDocument(Ticket ticket, String id)
            throws RepositoryException, NodeNotFoundException, SecurityException {

        SessionManager manager = SessionManager.getInstance();
        Session session = manager.getSession(ticket);

        try {
            javax.jcr.Node document = session.getNodeByUUID(id);

            if (!JCRSecurityService.canRead(document, ticket.getUser().getId())) {
                throw new SecurityException(SecurityException.NOT_ENOUGH_PERMISSIONS);
            }

            return JCRAdapter.createDocument(document, document.getParent().getUUID(),
                    JCRUtils.getRootNode(session).getUUID());
        } catch (ItemNotFoundException infe) {
            logger.error(infe.getMessage(), infe);
            throw new NodeNotFoundException(infe);
        } catch (javax.jcr.RepositoryException e) {
            logger.error(e.getMessage(), e);
            throw new RepositoryException(e);
        }
    }

    public Collection findDocumentsByName(Ticket ticket, String name) throws RepositoryException {

        ArrayList documents = new ArrayList();
        try {
            javax.jcr.Session session = SessionManager.getInstance().getSession(ticket);
            if (session == null) {
                throw new RepositoryException("Session has expired. Please log in again.");
            }
            javax.jcr.Node root = JCRUtils.getRootNode(session);
            String rootPath = root.getPath();

            Workspace workspace = session.getWorkspace();
            QueryManager queryManager = workspace.getQueryManager();
            String statement = "/jcr:root" + rootPath + "//element(*,jlib:document)[@jlib:active='true'"
                    + " and @jlib:name='" + name + "']";

            javax.jcr.query.Query query = queryManager.createQuery(statement, javax.jcr.query.Query.XPATH);
            QueryResult result = query.execute();

            NodeIterator it = result.getNodes();
            while (it.hasNext()) {
                javax.jcr.Node node = (javax.jcr.Node) it.next();
                if (!JCRSecurityService.canRead(node, ticket.getUser().getId())) {
                    continue;
                }
                documents
                        .add(JCRAdapter.createDocument(node, node.getParent().getUUID(), ticket.getRepositoryId()));
            }
        } catch (SecurityException se) {
            logger.error(se.getMessage(), se);
            throw new RepositoryException(se);
        } catch (javax.jcr.RepositoryException e) {
            logger.error(e.getMessage(), e);
            throw new RepositoryException(e);
        }
        return documents;
    }

    public Directory findDirectory(Ticket ticket, String id)
            throws RepositoryException, NodeNotFoundException, SecurityException {

        SessionManager manager = SessionManager.getInstance();
        Session session = manager.getSession(ticket);

        try {
            javax.jcr.Node directory = session.getNodeByUUID(id);

            if (!JCRSecurityService.canRead(directory, ticket.getUser().getId())) {
                throw new SecurityException(SecurityException.NOT_ENOUGH_PERMISSIONS);
            }

            String parentUUID = null;
            if (directory.getParent() != session.getRootNode()) {
                parentUUID = directory.getParent().getUUID();
            }
            return JCRAdapter.createDirectory(directory, parentUUID, JCRUtils.getRootNode(session).getUUID(),
                    ticket.getUser().getId());
        } catch (ItemNotFoundException infe) {
            logger.error(infe.getMessage(), infe);
            throw new NodeNotFoundException(infe);
        } catch (javax.jcr.RepositoryException e) {
            logger.error(e.getMessage(), e);
            throw new RepositoryException(e);
        }
    }

    public Document updateDocument(Ticket ticket, DocumentProperties properties)
            throws RepositoryException, SecurityException, ResourceLockedException {
        return updateDocument(ticket, properties, null);
    }

    public Document updateDocument(Ticket ticket, DocumentProperties properties, InputStream contentStream)
            throws RepositoryException, SecurityException, ResourceLockedException {

        // TODO: Check name updates with new code

        ByteArrayInputStream bais = null;
        try {
            String docId = (String) properties.getProperty(DocumentProperties.DOCUMENT_ID).getValue();
            String parentId = (String) properties.getProperty(DocumentProperties.DOCUMENT_PARENT).getValue();
            String name = ((String) properties.getProperty(DocumentProperties.DOCUMENT_NAME).getValue());
            String description = ((String) properties.getProperty(DocumentProperties.DOCUMENT_DESCRIPTION)
                    .getValue());
            Integer typecode = ((Integer) properties.getProperty(DocumentProperties.DOCUMENT_TYPECODE).getValue());
            Integer position = ((Integer) properties.getProperty(DocumentProperties.DOCUMENT_POSITION).getValue());
            Integer importance = ((Integer) properties.getProperty(DocumentProperties.DOCUMENT_IMPORTANCE)
                    .getValue());
            String title = (String) properties.getProperty(DocumentProperties.DOCUMENT_TITLE).getValue();
            String url = ((String) properties.getProperty(DocumentProperties.DOCUMENT_URL).getValue());
            String language = ((String) properties.getProperty(DocumentProperties.DOCUMENT_LANGUAGE).getValue());
            Author author = ((Author) properties.getProperty(DocumentProperties.DOCUMENT_AUTHOR).getValue());
            Date metadataDate = ((Date) properties.getProperty(DocumentProperties.DOCUMENT_CREATION_DATE)
                    .getValue());
            Calendar date = Calendar.getInstance();
            date.setTime(metadataDate);

            String keywords = ((String) properties.getProperty(DocumentProperties.DOCUMENT_KEYWORDS).getValue());

            SessionManager manager = SessionManager.getInstance();
            Session session = manager.getSession(ticket);

            javax.jcr.Node node = session.getNodeByUUID(docId);
            if (!JCRSecurityService.canWrite(node, ticket.getUser().getId())) {
                throw new SecurityException(SecurityException.NOT_ENOUGH_PERMISSIONS);
            }

            Object syncLock = LockUtility.obtainLock(node);
            synchronized (syncLock) {
                locksModule.checkLockAccess(ticket, node);

                JCRUtils.checkinIfNecessary(node);

                node.checkout();

                node.setProperty(JLibraryConstants.JLIBRARY_DESCRIPTION, description);
                node.setProperty(JLibraryConstants.JLIBRARY_CREATED, Calendar.getInstance());
                node.setProperty(JLibraryConstants.JLIBRARY_IMPORTANCE, importance.longValue());
                node.setProperty(JLibraryConstants.JLIBRARY_CREATOR, ticket.getUser().getId());
                node.setProperty(JLibraryConstants.JLIBRARY_TYPECODE, typecode.longValue());
                node.setProperty(JLibraryConstants.JLIBRARY_POSITION, position.longValue());

                node.setProperty(JLibraryConstants.JLIBRARY_TITLE, title);
                node.setProperty(JLibraryConstants.JLIBRARY_DOCUMENT_URL, url);
                node.setProperty(JLibraryConstants.JLIBRARY_KEYWORDS, keywords);
                node.setProperty(JLibraryConstants.JLIBRARY_LANGUAGE, language);
                node.setProperty(JLibraryConstants.JLIBRARY_CREATION_DATE, date);

                //Handle relations         
                PropertyDef[] relations = properties.getPropertyList(DocumentProperties.DOCUMENT_DELETE_RELATION);
                if (relations != null) {
                    for (int i = 0; i < relations.length; i++) {
                        Relation relation = (Relation) relations[i].getValue();
                        String destinationId = relation.getDestinationNode().getId();

                        JCRUtils.removeNodeFromProperty(destinationId, node, JLibraryConstants.JLIBRARY_RELATIONS);
                        if (relation.isBidirectional()) {
                            javax.jcr.Node referencedNode = session.getNodeByUUID(destinationId);
                            JCRUtils.removeNodeFromProperty(node.getUUID(), referencedNode,
                                    JLibraryConstants.JLIBRARY_RELATIONS);

                        }
                    }
                }

                relations = properties.getPropertyList(DocumentProperties.DOCUMENT_ADD_RELATION);
                if (relations != null) {
                    for (int i = 0; i < relations.length; i++) {
                        Relation relation = (Relation) relations[i].getValue();
                        String destinationId = relation.getDestinationNode().getId();

                        JCRUtils.addNodeToProperty(destinationId, node, JLibraryConstants.JLIBRARY_RELATIONS);
                        if (relation.isBidirectional()) {
                            javax.jcr.Node referencedNode = session.getNodeByUUID(destinationId);
                            JCRUtils.addNodeToProperty(node.getUUID(), referencedNode,
                                    JLibraryConstants.JLIBRARY_RELATIONS);

                        }
                    }
                }

                //Handle authors
                if (author.equals(Author.UNKNOWN)) {
                    node.setProperty(JLibraryConstants.JLIBRARY_AUTHOR, authorsModule.findUnknownAuthor(ticket));
                } else {
                    javax.jcr.Node authorNode = session.getNodeByUUID(author.getId());
                    node.setProperty(JLibraryConstants.JLIBRARY_AUTHOR, authorNode.getUUID());
                }

                // Handle document categories
                PropertyDef[] categories = properties.getPropertyList(DocumentProperties.DOCUMENT_DELETE_CATEGORY);
                if (categories != null) {
                    for (int i = 0; i < categories.length; i++) {
                        String category = (String) categories[i].getValue();
                        javax.jcr.Node categoryNode = categoriesModule.getCategoryNode(session, category);
                        categoriesModule.removeCategory(ticket, categoryNode, node);
                    }
                    if (categoriesModule.numberOfCategories(node) == 0) {
                        categoriesModule.addUnknownCategory(ticket, node);
                    }
                }

                categories = properties.getPropertyList(DocumentProperties.DOCUMENT_ADD_CATEGORY);
                if (categories != null) {
                    if (categoriesModule.containsUnknownCategory(ticket, node)) {
                        categoriesModule.removeUnknownCategory(ticket, node);
                    }
                    for (int i = 0; i < categories.length; i++) {
                        String category = (String) categories[i].getValue();
                        javax.jcr.Node categoryNode = categoriesModule.getCategoryNode(session, category);
                        categoriesModule.addCategory(ticket, categoryNode, node);
                    }
                }

                // Handle document notes
                PropertyDef[] notes = properties.getPropertyList(DocumentProperties.DOCUMENT_ADD_NOTE);
                if (notes != null) {
                    javax.jcr.Node userNode = JCRSecurityService.getUserNode(session, ticket.getUser().getId());
                    for (int i = 0; i < notes.length; i++) {
                        Note note = (Note) notes[i].getValue();
                        javax.jcr.Node noteNode = node.addNode(JLibraryConstants.JLIBRARY_NOTE,
                                JLibraryConstants.NOTE_MIXIN);
                        noteNode.addMixin(JCRConstants.JCR_REFERENCEABLE);
                        Calendar noteDate = Calendar.getInstance();
                        noteDate.setTime(new Date());

                        noteNode.setProperty(JLibraryConstants.JLIBRARY_DATE, noteDate);
                        noteNode.setProperty(JLibraryConstants.JLIBRARY_TEXT, note.getNote());
                        noteNode.setProperty(JLibraryConstants.JLIBRARY_USER, userNode.getUUID());
                    }
                }

                notes = properties.getPropertyList(DocumentProperties.DOCUMENT_UPDATE_NOTE);
                if (notes != null) {
                    for (int i = 0; i < notes.length; i++) {
                        Note note = (Note) notes[i].getValue();
                        javax.jcr.Node noteNode = session.getNodeByUUID(note.getId());
                        Calendar noteDate = Calendar.getInstance();
                        noteDate.setTime(new Date());

                        noteNode.setProperty(JLibraryConstants.JLIBRARY_DATE, noteDate);
                        noteNode.setProperty(JLibraryConstants.JLIBRARY_TEXT, note.getNote());
                        noteNode.setProperty(JLibraryConstants.JLIBRARY_USER, ticket.getUser().getId());
                    }
                }

                notes = properties.getPropertyList(DocumentProperties.DOCUMENT_DELETE_NOTE);
                if (notes != null) {
                    for (int i = 0; i < notes.length; i++) {
                        Note note = (Note) notes[i].getValue();
                        NodeIterator it = node.getNodes();
                        while (it.hasNext()) {
                            javax.jcr.Node childNode = (javax.jcr.Node) it.next();
                            if (childNode.isNodeType(JLibraryConstants.NOTE_MIXIN)) {
                                String noteId = childNode.getUUID();
                                if (noteId.equals(note.getId())) {
                                    childNode.remove();
                                }
                            }
                        }
                    }
                }

                // Handle custom properties
                List customProperties = properties.getCustomProperties();
                Iterator it = customProperties.iterator();
                while (it.hasNext()) {
                    PropertyDef property = (PropertyDef) it.next();
                    node.setProperty(property.getKey().toString(), JCRUtils.getValue(property.getValue()));
                }

                // Handle content
                byte[] content = null;
                if (properties.getProperty(DocumentProperties.DOCUMENT_CONTENT) != null) {
                    content = (byte[]) properties.getProperty(DocumentProperties.DOCUMENT_CONTENT).getValue();
                }
                if (contentStream != null || content != null) {
                    // need to update content too
                    javax.jcr.Node child = node.getNode(JCRConstants.JCR_CONTENT);

                    InputStream streamToSet;
                    if (contentStream == null) {
                        bais = new ByteArrayInputStream(content);
                        streamToSet = bais;
                    } else {
                        streamToSet = contentStream;
                    }
                    String path = node.getProperty(JLibraryConstants.JLIBRARY_PATH).getString();
                    String mimeType = Types.getMimeTypeForExtension(FileUtils.getExtension(path));
                    child.setProperty(JCRConstants.JCR_MIME_TYPE, mimeType);
                    child.setProperty(JCRConstants.JCR_ENCODING, JCRConstants.DEFAULT_ENCODING);
                    child.setProperty(JCRConstants.JCR_DATA, streamToSet);
                    Calendar lastModified = Calendar.getInstance();
                    lastModified.setTimeInMillis(new Date().getTime());
                    child.setProperty(JCRConstants.JCR_LAST_MODIFIED, lastModified);

                    node.setProperty(JLibraryConstants.JLIBRARY_SIZE,
                            child.getProperty(JCRConstants.JCR_DATA).getLength());
                }

                String previousName = node.getProperty(JLibraryConstants.JLIBRARY_NAME).getString();
                if (!previousName.equals(name)) {
                    String path = node.getProperty(JLibraryConstants.JLIBRARY_NAME).getString();
                    String extension = FileUtils.getExtension(path);
                    String escapedName = JCRUtils.buildValidChildNodeName(node.getParent(), extension, name);
                    if ((extension != null) && !escapedName.endsWith(extension)) {
                        escapedName += extension;
                    }
                    name = Text.unescape(escapedName);
                    node.setProperty(JLibraryConstants.JLIBRARY_NAME, name);
                    session.move(node.getPath(), node.getParent().getPath() + "/" + escapedName);
                }

                if (ticket.isAutocommit()) {
                    SaveUtils.safeSaveSession(session);
                }

                // create first version
                node.checkin();
                // restore to read-write state
                node.checkout();
            }

            javax.jcr.Node root = JCRUtils.getRootNode(session);
            return JCRAdapter.createDocument(node, parentId, root.getUUID());
        } catch (Throwable e) {
            logger.error(e.getMessage(), e);
            throw new RepositoryException(e);
        } finally {
            if (bais != null) {
                try {
                    bais.close();
                } catch (IOException ioe) {
                    logger.error(ioe.getMessage(), ioe);
                    throw new RepositoryException(ioe);
                }
            }
        }

    }

    public Directory updateDirectory(Ticket ticket, DirectoryProperties directoryProperties)
            throws RepositoryException, SecurityException {

        try {
            SessionManager manager = SessionManager.getInstance();
            Session session = manager.getSession(ticket);

            String directoryId = (String) directoryProperties.getProperty(DirectoryProperties.DIRECTORY_ID)
                    .getValue();
            String parentId = (String) directoryProperties.getProperty(DirectoryProperties.DIRECTORY_PARENT)
                    .getValue();
            String name = (String) directoryProperties.getProperty(DirectoryProperties.DIRECTORY_NAME).getValue();
            String description = (String) directoryProperties.getProperty(DirectoryProperties.DIRECTORY_DESCRIPTION)
                    .getValue();
            Integer position = (Integer) directoryProperties.getProperty(DirectoryProperties.DIRECTORY_POSITION)
                    .getValue();

            javax.jcr.Node root = JCRUtils.getRootNode(session);
            javax.jcr.Node directory = session.getNodeByUUID(directoryId);

            if (!JCRSecurityService.canWrite(directory, ticket.getUser().getId())) {
                throw new SecurityException(SecurityException.NOT_ENOUGH_PERMISSIONS);
            }

            Object syncLock = LockUtility.obtainLock(directory);
            synchronized (syncLock) {

                directory.setProperty(JLibraryConstants.JLIBRARY_DESCRIPTION, description);
                directory.setProperty(JLibraryConstants.JLIBRARY_POSITION, position.longValue());

                String previousName = directory.getProperty(JLibraryConstants.JLIBRARY_NAME).getString();
                if (!previousName.equals(name)) {
                    String escapedName = JCRUtils.buildValidChildNodeName(directory.getParent(), null, name);
                    name = Text.unescape(escapedName);
                    directory.setProperty(JLibraryConstants.JLIBRARY_NAME, name);
                    session.move(directory.getPath(), directory.getParent().getPath() + "/" + escapedName);
                }

                if (ticket.isAutocommit()) {
                    SaveUtils.safeSaveSession(session);
                }
            }
            return JCRAdapter.createDirectory(directory, parentId, root.getUUID(), ticket.getUser().getId());
        } catch (Exception e) {
            throw new RepositoryException(e);
        }
    }

    public Repository updateRepository(Ticket ticket, RepositoryProperties repositoryProperties)
            throws RepositoryException, SecurityException {

        try {

            javax.jcr.Session session = SessionManager.getInstance().getSession(ticket);
            if (session == null) {
                throw new RepositoryException("Session has expired. Please log in again.");
            }

            javax.jcr.Node repositoryNode = JCRUtils.getRootNode(session);

            if (!JCRSecurityService.canWrite(repositoryNode, ticket.getUser().getId())) {
                throw new SecurityException(SecurityException.NOT_ENOUGH_PERMISSIONS);
            }

            String name;
            Object syncLock = LockUtility.obtainLock(repositoryNode);
            synchronized (syncLock) {
                name = (String) repositoryProperties.getProperty(RepositoryProperties.REPOSITORY_NAME).getValue();
                String description = (String) repositoryProperties
                        .getProperty(RepositoryProperties.REPOSITORY_DESCRIPTION).getValue();
                Boolean physicalDelete = (Boolean) repositoryProperties
                        .getProperty(RepositoryProperties.PHYSICAL_DELETE_DOCUMENTS).getValue();
                Boolean extractKeywords = (Boolean) repositoryProperties
                        .getProperty(RepositoryProperties.EXTRACT_DOCUMENT_METADATA).getValue();
                Boolean lazyLoading = (Boolean) repositoryProperties
                        .getProperty(RepositoryProperties.DO_LAZY_LOADING).getValue();

                repositoryNode.setProperty(JLibraryConstants.JLIBRARY_NAME, name);
                repositoryNode.setProperty(JLibraryConstants.JLIBRARY_DESCRIPTION, description);

                // Create config entries
                if (extractKeywords != null) {
                    JCRUtils.setConfigEntry(session, RepositoryProperties.EXTRACT_DOCUMENT_METADATA,
                            extractKeywords);
                }
                if (physicalDelete != null) {
                    JCRUtils.setConfigEntry(session, RepositoryProperties.PHYSICAL_DELETE_DOCUMENTS,
                            physicalDelete);
                }
                if (lazyLoading != null) {
                    JCRUtils.setConfigEntry(session, RepositoryProperties.DO_LAZY_LOADING, lazyLoading);
                }

                if (ticket.isAutocommit()) {
                    SaveUtils.safeSaveSession(session);
                }
            }
            return JCRAdapter.createRepository(ticket, name, repositoryNode);
        } catch (Exception e) {
            throw new RepositoryException(e);
        }
    }

    public void renameNode(Ticket ticket, String nodeId, String name)
            throws RepositoryException, SecurityException {

        try {
            SessionManager manager = SessionManager.getInstance();
            Session session = manager.getSession(ticket);
            javax.jcr.Node node = session.getNodeByUUID(nodeId);

            if (!JCRSecurityService.canWrite(node, ticket.getUser().getId())) {
                throw new SecurityException(SecurityException.NOT_ENOUGH_PERMISSIONS);
            }

            Object syncLock = LockUtility.obtainLock(node);
            synchronized (syncLock) {
                String extension = null;
                if (node.isNodeType(JLibraryConstants.RESOURCE_MIXIN)
                        || node.isNodeType(JLibraryConstants.DOCUMENT_MIXIN)) {
                    node.checkout();
                    String path = node.getProperty(JLibraryConstants.JLIBRARY_PATH).getString();
                    extension = FileUtils.getExtension(path);
                }

                String escapedName = JCRUtils.buildValidChildNodeName(node.getParent(), extension, name);
                name = Text.unescape(escapedName);
                if ((extension != null) && !escapedName.endsWith(extension)) {
                    escapedName += extension;
                }
                node.setProperty(JLibraryConstants.JLIBRARY_NAME, name);

                // Now we have to update relative paths
                session.move(node.getPath(), node.getParent().getPath() + "/" + escapedName);

                if (ticket.isAutocommit()) {
                    SaveUtils.safeSaveSession(session);
                }
                if (node.isNodeType(JLibraryConstants.RESOURCE_MIXIN)
                        || node.isNodeType(JLibraryConstants.DOCUMENT_MIXIN)) {
                    // Create a new version
                    node.checkin();
                    // Restore it to read-write state
                    node.checkout();
                }
            }
        } catch (Exception e) {
            throw new RepositoryException(e);
        }

    }

    public List findAllAuthors(Ticket ticket) throws RepositoryException {

        return authorsModule.findAllAuthors(ticket);
    }

    public Author findAuthorByName(Ticket ticket, String name) throws AuthorNotFoundException, RepositoryException {

        return authorsModule.findAuthorByName(ticket, name);
    }

    public Author findAuthorById(Ticket ticket, String id) throws AuthorNotFoundException, RepositoryException {

        return authorsModule.findAuthorById(ticket, id);
    }

    public List findAllCategories(Ticket ticket) throws RepositoryException {

        return categoriesModule.findAllCategories(ticket);
    }

    public Category createCategory(Ticket ticket, CategoryProperties categoryProperties)
            throws CategoryAlreadyExistsException, RepositoryException, SecurityException {

        return categoriesModule.createCategory(ticket, categoryProperties);
    }

    public void deleteCategory(Ticket ticket, String categoryId) throws RepositoryException, SecurityException {

        categoriesModule.deleteCategory(ticket, categoryId);
    }

    public Category updateCategory(Ticket ticket, String categoryId, CategoryProperties categoryProperties)
            throws CategoryNotFoundException, RepositoryException, SecurityException {

        return categoriesModule.updateCategory(ticket, categoryId, categoryProperties);
    }

    public Category findCategoryById(Ticket ticket, String id)
            throws CategoryNotFoundException, RepositoryException {

        return categoriesModule.findCategoryById(ticket, id);
    }

    public Category findCategoryByName(Ticket ticket, String name)
            throws CategoryNotFoundException, RepositoryException {

        return categoriesModule.findCategoryByName(ticket, name);
    }

    public Favorite createFavorite(Ticket ticket, Favorite favorite) throws RepositoryException, SecurityException {

        return favoritesModule.createFavorite(ticket, favorite);
    }

    public void deleteFavorite(Ticket ticket, String favoriteId) throws RepositoryException, SecurityException {

        favoritesModule.deleteFavorite(ticket, favoriteId);
    }

    public Bookmark createBookmark(Ticket ticket, Bookmark bookmark) throws RepositoryException {

        return bookmarksModule.createBookmark(ticket, bookmark);
    }

    public Author createAuthor(Ticket ticket, AuthorProperties properties)
            throws RepositoryException, SecurityException, AuthorAlreadyExistsException {

        return authorsModule.createAuthor(ticket, properties);
    }

    public void updateAuthor(Ticket ticket, String authorId, AuthorProperties properties)
            throws RepositoryException, SecurityException, AuthorNotFoundException {

        authorsModule.updateAuthor(ticket, authorId, properties);
    }

    public void deleteAuthor(Ticket ticket, String authorId)
            throws RepositoryException, SecurityException, AuthorNotFoundException {

        authorsModule.deleteAuthor(ticket, authorId);
    }

    public void removeBookmark(Ticket ticket, String bookmarkId) throws RepositoryException {

        bookmarksModule.removeBookmark(ticket, bookmarkId);
    }

    public Bookmark updateBookmark(Ticket ticket, Bookmark bookmark) throws RepositoryException {

        return bookmarksModule.updateBookmark(ticket, bookmark);
    }

    public List findCategoriesForNode(Ticket ticket, String nodeId) throws RepositoryException, SecurityException {

        return categoriesModule.findCategoriesForNode(ticket, nodeId);
    }

    public List findNodesForCategory(Ticket ticket, String categoryId)
            throws CategoryNotFoundException, RepositoryException {

        return categoriesModule.findNodesForCategory(ticket, categoryId);
    }

    public byte[] exportRepository(Ticket ticket)
            throws RepositoryNotFoundException, RepositoryException, SecurityException {

        return importExportModule.exportRepository(ticket);
    }

    public void exportRepository(Ticket ticket, OutputStream stream)
            throws RepositoryNotFoundException, RepositoryException, SecurityException {

        importExportModule.exportRepository(ticket, stream);
    }

    public void importRepository(Ticket ticket, byte[] content, String name)
            throws RepositoryAlreadyExistsException, RepositoryException, SecurityException {

        try {
            importExportModule.importRepository(ticket, content, name);
        } catch (RepositoryAlreadyExistsException raee) {
            throw raee;
        } catch (RepositoryException re) {
            try {
                // In this case we must delete the recently created workspace            
                deleteWorkspace(ticket, name);
                throw re;
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
                // throw previous exception, otherwise it will be confusing
                throw re;
            }
        } catch (SecurityException se) {
            throw se;
        }
    }

    private void deleteWorkspace(Ticket ticket, String name)
            throws javax.jcr.RepositoryException, RepositoryException {

        javax.jcr.Repository repository = SessionManager.getInstance().getRepository();
        SimpleCredentials creds = new SimpleCredentials(ticket.getUser().getName(),
                ticket.getUser().getPassword().toCharArray());

        javax.jcr.Session session = repository.login(creds, name);
        String workspacePath = ((WorkspaceImpl) session.getWorkspace()).getConfig().getHomeDir();
        session.logout();

        File f = new File(workspacePath);
        try {
            org.apache.commons.io.FileUtils.forceDelete(f);
        } catch (IOException e) {
            //logger.error(e.getMessage(),e);
        }

        javax.jcr.Session systemSession = SessionManager.getInstance().getSystemSession();
        cleanupModule.scheduleForDelete(systemSession, name, workspacePath);
    }

    public byte[] loadVersionContent(Ticket ticket, String versionId)
            throws RepositoryException, SecurityException {

        ByteArrayOutputStream baos = null;
        try {
            baos = new ByteArrayOutputStream();
            loadVersionContent(ticket, versionId, baos);
            return baos.toByteArray();
        } finally {
            if (baos != null) {
                try {
                    baos.close();
                } catch (IOException e) {
                    logger.error(e.getMessage(), e);
                    throw new RepositoryException(e);
                }
            }
        }
    }

    public void loadVersionContent(Ticket ticket, String versionId, OutputStream stream)
            throws RepositoryException, SecurityException {

        SessionManager manager = SessionManager.getInstance();
        Session session = manager.getSession(ticket);

        InputStream is = null;
        try {
            javax.jcr.version.Version version = (javax.jcr.version.Version) session.getNodeByUUID(versionId);
            javax.jcr.Node frozenNode = version.getNode(JCRConstants.JCR_FROZEN_NODE);

            if (frozenNode.hasNode(JCRConstants.JCR_CONTENT)) {
                javax.jcr.Node nodeContent = frozenNode.getNode(JCRConstants.JCR_CONTENT);
                is = nodeContent.getProperty("jcr:data").getValue().getStream();
                IOUtils.copy(is, stream);

            } else {
                // lookup in older versions
                javax.jcr.version.Version[] predecessors = version.getPredecessors();
                for (int i = 0; i < predecessors.length; i++) {
                    frozenNode = predecessors[i].getNode(JCRConstants.JCR_FROZEN_NODE);
                    if (frozenNode.hasNode(JCRConstants.JCR_CONTENT)) {
                        javax.jcr.Node nodeContent = frozenNode.getNode(JCRConstants.JCR_CONTENT);
                        is = nodeContent.getProperty("jcr:data").getValue().getStream();
                        IOUtils.copy(is, stream);

                    }
                }
                // content not found
                frozenNode = version.getNode(JCRConstants.JCR_FROZEN_NODE);
                String nodeUUID = frozenNode.getProperty(JCRConstants.JCR_FROZEN_UUID).getString();
                loadDocumentContent(nodeUUID, ticket, stream);
            }
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
            throw new RepositoryException(e);
        } catch (javax.jcr.RepositoryException e) {
            logger.error(e.getMessage(), e);
            throw new RepositoryException(e);
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException ioe) {
                    logger.error(ioe.getMessage(), ioe);
                    throw new RepositoryException(ioe);
                }
            }
        }

    }

    public Lock lockDocument(Ticket ticket, String docId)
            throws RepositoryException, SecurityException, ResourceLockedException {

        return locksModule.lockDocument(ticket, docId);
    }

    public void unlockDocument(Ticket ticket, String docId)
            throws RepositoryException, SecurityException, ResourceLockedException {

        locksModule.unlockDocument(ticket, docId);
    }

    public List findAllLocks(Ticket ticket) throws RepositoryException, SecurityException {

        return locksModule.findAllLocks(ticket);
    }

    public ResourceNode createResource(Ticket ticket, ResourceNodeProperties properties)
            throws RepositoryException, SecurityException {

        return resourcesModule.createResource(ticket, properties);
    }

    public void addResourceToDocument(Ticket ticket, String resourceId, String documentId)
            throws RepositoryException, SecurityException {

        resourcesModule.addResourceToDocument(ticket, resourceId, documentId);
    }

    public List findNodesForResource(Ticket ticket, String resourceId) throws RepositoryException {

        return resourcesModule.findNodesForResource(ticket, resourceId);
    }

    public byte[] loadResourceNodeContent(Ticket ticket, String resourceId)
            throws RepositoryException, SecurityException {

        return resourcesModule.loadResourceNodeContent(ticket, resourceId);
    }

    public void loadResourceNodeContent(Ticket ticket, String resourceId, OutputStream stream)
            throws RepositoryException, SecurityException {

        resourcesModule.loadResourceNodeContent(ticket, resourceId, stream);
    }

    public ResourceNode updateResourceNode(Ticket ticket, ResourceNodeProperties properties)
            throws RepositoryException, SecurityException {

        return resourcesModule.updateResourceNode(ticket, properties);
    }

    public void removeResourceNode(Ticket ticket, String resourceId) throws RepositoryException, SecurityException {

        resourcesModule.removeResourceNode(ticket, resourceId);
    }

    public void removeResourceNode(Ticket ticket, String resourceId, String docId)
            throws RepositoryException, SecurityException {

        resourcesModule.removeResourceNode(ticket, resourceId, docId);
    }

    /**
     * Note that destinationRepository is not used on this implementation
     * 
     * @see RepositoryService#copyResource(Ticket, String, String, String)
     */
    public ResourceNode copyResource(Ticket ticket, String resourceId, String directoryId,
            String destinationRepository) throws RepositoryException, SecurityException {

        try {
            javax.jcr.Node node = internalCopyNode(ticket, resourceId, directoryId);
            javax.jcr.Node root = JCRUtils.getRootNode(node.getSession());
            return JCRAdapter.createResource(node, node.getParent().getUUID(), root.getUUID());
        } catch (javax.jcr.RepositoryException e) {
            logger.error(e.getMessage(), e);
            throw new RepositoryException(e);
        }
    }

    public javax.jcr.Node internalMoveNode(Ticket ticket, String resourceId, String directoryId)
            throws RepositoryException, SecurityException {

        try {
            SessionManager manager = SessionManager.getInstance();
            Session session = manager.getSession(ticket);
            javax.jcr.Node source = session.getNodeByUUID(resourceId);
            javax.jcr.Node destination = session.getNodeByUUID(directoryId);

            if (!JCRSecurityService.canRead(source, ticket.getUser().getId())) {
                throw new SecurityException(SecurityException.NOT_ENOUGH_PERMISSIONS);
            }

            if (!JCRSecurityService.canWrite(destination, ticket.getUser().getId())) {
                throw new SecurityException(SecurityException.NOT_ENOUGH_PERMISSIONS);
            }

            javax.jcr.Node resultNode;
            Object syncLock = LockUtility.obtainLock(destination);
            synchronized (syncLock) {

                String extension = null;
                if (source.isNodeType(JLibraryConstants.RESOURCE_MIXIN)
                        || source.isNodeType(JLibraryConstants.DOCUMENT_MIXIN)) {
                    String path = source.getProperty(JLibraryConstants.JLIBRARY_PATH).getString();
                    extension = FileUtils.getExtension(path);
                }

                String validName = JCRUtils.buildValidChildNodeName(source.getParent(), extension,
                        source.getName());
                if ((extension != null) && !validName.endsWith(extension)) {
                    validName += extension;
                }

                String destinationPath = destination.getPath() + "/" + validName;
                destinationPath = StringUtils.replace(destinationPath, "//", "/");
                session.move(source.getPath(), destinationPath);

                resultNode = destination.getNode(validName);
                changePathRecursively(resultNode, destination);

                if (ticket.isAutocommit()) {
                    SaveUtils.safeSaveSession(session);
                }
            }
            return resultNode;
        } catch (javax.jcr.RepositoryException e) {
            logger.error(e.getMessage(), e);
            throw new RepositoryException(e);
        }
    }

    private void changePathRecursively(javax.jcr.Node resultNode, javax.jcr.Node parent)
            throws RepositoryException {

        try {
            String destinationName = "";
            if (resultNode.isNodeType(JLibraryConstants.DIRECTORY_MIXIN)) {
                destinationName = obtainAvailableDirectoryName(parent, resultNode);
            } else {
                String path = resultNode.getProperty(JLibraryConstants.JLIBRARY_PATH).getString();
                destinationName = FileUtils.getFileName(path);
            }

            resultNode.setProperty(JLibraryConstants.JLIBRARY_PATH, getPath(parent) + destinationName);

            NodeIterator it = resultNode.getNodes();
            while (it.hasNext()) {
                javax.jcr.Node child = (javax.jcr.Node) it.next();
                if (!JCRUtils.isActive(child)) {
                    continue;
                }
                if ((child.isNodeType(JLibraryConstants.DOCUMENT_MIXIN))
                        || (child.isNodeType(JLibraryConstants.DIRECTORY_MIXIN))
                        || (child.isNodeType(JLibraryConstants.RESOURCE_MIXIN))) {
                    changePathRecursively(child, resultNode);
                }
            }

        } catch (javax.jcr.RepositoryException e) {
            logger.error(e.getMessage(), e);
            throw new RepositoryException(e);
        }

    }

    public javax.jcr.Node internalCopyNode(Ticket ticket, String resourceId, String directoryId)
            throws RepositoryException, SecurityException {

        try {
            SessionManager manager = SessionManager.getInstance();
            Session session = manager.getSession(ticket);
            javax.jcr.Node source = session.getNodeByUUID(resourceId);
            javax.jcr.Node destination = session.getNodeByUUID(directoryId);

            if (!JCRSecurityService.canRead(source, ticket.getUser().getId())) {
                throw new SecurityException(SecurityException.NOT_ENOUGH_PERMISSIONS);
            }

            if (!JCRSecurityService.canWrite(destination, ticket.getUser().getId())) {
                throw new SecurityException(SecurityException.NOT_ENOUGH_PERMISSIONS);
            }

            javax.jcr.Node resultNode;
            Object syncLock = LockUtility.obtainLock(destination);
            synchronized (syncLock) {

                String extension = null;
                if (source.isNodeType(JLibraryConstants.RESOURCE_MIXIN)
                        || source.isNodeType(JLibraryConstants.DOCUMENT_MIXIN)) {
                    String path = source.getProperty(JLibraryConstants.JLIBRARY_PATH).getString();
                    extension = FileUtils.getExtension(path);
                }

                String validName = JCRUtils.buildValidChildNodeName(source.getParent(), extension,
                        source.getName());
                if ((extension != null) && !validName.endsWith(extension)) {
                    validName += extension;
                }

                String destinationPath = destination.getPath() + "/" + validName;
                destinationPath = StringUtils.replace(destinationPath, "//", "/");
                session.getWorkspace().copy(source.getPath(), destinationPath);

                resultNode = destination.getNode(validName);
                changePathRecursively(resultNode, destination);

                if (ticket.isAutocommit()) {
                    SaveUtils.safeSaveSession(session);
                    if (source.isNodeType(JLibraryConstants.DOCUMENT_MIXIN)) {
                        // that's document, let's make its version
                        resultNode.checkin();
                        resultNode.checkout();
                    }
                }
            }
            return resultNode;
        } catch (javax.jcr.RepositoryException e) {
            logger.error(e.getMessage(), e);
            throw new RepositoryException(e);
        }
    }

    /**
     * Note that destinationRepository is not used on this implementation
     * 
     * @see RepositoryService#moveResource(Ticket, String, String, String)
     */
    public ResourceNode moveResource(Ticket ticket, String resourceId, String directoryId,
            String destinationRepository) throws RepositoryException, SecurityException {

        try {
            javax.jcr.Node node = internalMoveNode(ticket, resourceId, directoryId);
            javax.jcr.Node root = JCRUtils.getRootNode(node.getSession());
            return JCRAdapter.createResource(node, node.getParent().getUUID(), root.getUUID());
        } catch (javax.jcr.RepositoryException e) {
            logger.error(e.getMessage(), e);
            throw new RepositoryException(e);
        }
    }

    /**
     * Note that destinationRepository is not used on this implementation
     * 
     * @see RepositoryService#copyNode(Ticket, String, String, String)
     */
    public Node copyNode(Ticket ticket, String sourceId, String destinationId, String destinationRepository)
            throws RepositoryException, SecurityException {

        try {
            SessionManager manager = SessionManager.getInstance();
            Session session = manager.getSession(ticket);
            javax.jcr.Node node = session.getNodeByUUID(sourceId);
            if (node.isNodeType(JLibraryConstants.DOCUMENT_MIXIN)) {
                return copyDocument(ticket, sourceId, destinationId, destinationRepository);
            } else if (node.isNodeType(JLibraryConstants.DIRECTORY_MIXIN)) {
                return copyDirectory(ticket, sourceId, destinationId, destinationRepository);
            } else if (node.isNodeType(JLibraryConstants.RESOURCE_MIXIN)) {
                return copyResource(ticket, sourceId, destinationId, destinationRepository);
            } else {
                return null;
            }
        } catch (javax.jcr.RepositoryException e) {
            logger.error(e.getMessage(), e);
            throw new RepositoryException(e);
        }

    }

    /**
     * Note that destinationRepository is not used on this implementation
     * 
     * @see RepositoryService#moveNode(Ticket, String, String, String)
     */
    public Node moveNode(Ticket ticket, String sourceId, String destinationId, String destinationRepository)
            throws RepositoryException, SecurityException {

        try {
            SessionManager manager = SessionManager.getInstance();
            Session session = manager.getSession(ticket);
            javax.jcr.Node node = session.getNodeByUUID(sourceId);
            if (node.isNodeType(JLibraryConstants.DOCUMENT_MIXIN)) {
                return moveDocument(ticket, sourceId, destinationId, destinationRepository);
            } else if (node.isNodeType(JLibraryConstants.DIRECTORY_MIXIN)) {
                return moveDirectory(ticket, sourceId, destinationId, destinationRepository);
            } else if (node.isNodeType(JLibraryConstants.RESOURCE_MIXIN)) {
                return moveResource(ticket, sourceId, destinationId, destinationRepository);
            } else {
                return null;
            }
        } catch (javax.jcr.RepositoryException e) {
            logger.error(e.getMessage(), e);
            throw new RepositoryException(e);
        }
    }

    /**
     * <p>Obtain a valid directory name for a directory path. In jLibrary, all 
     * the nodes will have a unique non repository dependent path. With this 
     * path we ensure that changes on node hierarchy names does not conflict 
     * with node rendering on external applications. Anyways, external 
     * applications can choose to use or ignore this path.</p>
     * 
     * <p>The node paths are assigned on sequency, being the first number 0. This 
     * method returns the first available node path name. Not that this number 
     * must not be mandatory the next number to the biggest one available, as 
     * probably some nodes could have been removed and so they will left empty 
     * slots.</p> 
     * 
     * <p>This method is synchronized as otherwise we could have concurrency 
     * problems with several threads trying to obtain available directory names 
     * on the same path.</p>
     * 
     * @param parent Parent from which we will look for a path
     * @param node Node for which we are looking for a path
     * 
     * @return String First available path slot
     * 
     * @throws javax.jcr.RepositoryException If the path cannot be obtained
     */
    public synchronized String obtainAvailableDirectoryName(javax.jcr.Node parent, javax.jcr.Node node)
            throws javax.jcr.RepositoryException {

        ArrayList names = new ArrayList();
        javax.jcr.NodeIterator it = parent.getNodes();
        while (it.hasNext()) {
            javax.jcr.Node child = it.nextNode();
            // Ignore the node for which we are looking a path, if exists
            if (child.getUUID().equals(node.getUUID()))
                continue;

            if (child.isNodeType(JLibraryConstants.DIRECTORY_MIXIN)) {
                if (child.hasProperty(JLibraryConstants.JLIBRARY_PATH)) {
                    String path = child.getProperty(JLibraryConstants.JLIBRARY_PATH).getValue().getString();
                    String name = FileUtils.getFileName(path);
                    names.add(name);
                }
            }
        }

        ArrayList ids = new ArrayList();
        Iterator it2 = names.iterator();
        while (it2.hasNext()) {
            String name = (String) it2.next();
            ids.add(new Integer(Integer.parseInt(name)));
        }

        Collections.sort(ids);

        String dirId = null;
        for (int i = 0; i < ids.size(); i++) {
            Integer id = (Integer) ids.get(i);
            if (!(id.intValue() == i)) {
                dirId = String.valueOf(i);
            }
        }
        if (dirId == null) {
            dirId = String.valueOf(ids.size());
        }

        return dirId;
    }

    public List getVersions(Ticket ticket, String documentId) throws RepositoryException, SecurityException {

        try {
            SessionManager manager = SessionManager.getInstance();
            Session session = manager.getSession(ticket);
            javax.jcr.Node node = session.getNodeByUUID(documentId);

            if (!JCRSecurityService.canRead(node, ticket.getUser().getId())) {
                throw new SecurityException(SecurityException.NOT_ENOUGH_PERMISSIONS);
            }
            javax.jcr.Node root = JCRUtils.getRootNode(node.getSession());

            Document document = JCRAdapter.createDocument(node, node.getParent().getUUID(), root.getUUID());
            List versions = new ArrayList();

            VersionHistory history = node.getVersionHistory();
            for (VersionIterator it = history.getAllVersions(); it.hasNext();) {
                Version version = (Version) it.next();
                if (version.equals(node.getBaseVersion())) {
                    continue;
                }
                versions.add(JCRAdapter.createVersion(node, version, document));
            }

            return versions;
        } catch (javax.jcr.RepositoryException e) {
            logger.error(e.getMessage(), e);
            throw new RepositoryException(e);
        }

    }

    public Collection findNodeChildren(Ticket ticket, String id)
            throws RepositoryException, NodeNotFoundException, SecurityException {

        ArrayList children = new ArrayList();
        SessionManager manager = SessionManager.getInstance();
        Session session = manager.getSession(ticket);

        try {
            javax.jcr.Node node = session.getNodeByUUID(id);
            javax.jcr.Node root = JCRUtils.getRootNode(session);

            if (!JCRSecurityService.canRead(node, ticket.getUser().getId())) {
                throw new SecurityException(SecurityException.NOT_ENOUGH_PERMISSIONS);
            }

            if (node.isNodeType(JLibraryConstants.DOCUMENT_MIXIN))
                return Collections.EMPTY_LIST;

            NodeIterator it = node.getNodes();
            Value lazyLoading = JCRUtils.getConfigProperty(node.getSession(), RepositoryProperties.DO_LAZY_LOADING);
            boolean lazy = lazyLoading.getBoolean();
            while (it.hasNext()) {
                javax.jcr.Node child = (javax.jcr.Node) it.next();
                if (!JCRUtils.isActive(child)) {
                    continue;
                }
                if (!JCRSecurityService.canRead(child, ticket.getUser().getId())) {
                    continue;
                }
                if (child.isNodeType(JLibraryConstants.DOCUMENT_MIXIN)) {
                    children.add(JCRAdapter.createDocument(child, id, root.getUUID()));
                } else if (child.isNodeType(JLibraryConstants.DIRECTORY_MIXIN)) {
                    children.add(
                            JCRAdapter.createDirectory(child, id, root.getUUID(), ticket.getUser().getId(), lazy));
                } else if (child.isNodeType(JLibraryConstants.RESOURCE_MIXIN)) {
                    children.add(JCRAdapter.createResource(child, id, root.getUUID()));
                }
            }
            return children;

        } catch (ItemNotFoundException infe) {
            logger.error(infe.getMessage(), infe);
            throw new NodeNotFoundException(infe);
        } catch (javax.jcr.RepositoryException e) {
            logger.error(e.getMessage(), e);
            throw new RepositoryException(e);
        }
    }

    /**
     * @see RepositoryService#saveSession(Ticket)
     */
    public void saveSession(Ticket ticket) throws RepositoryException {

        javax.jcr.Session session = SessionManager.getInstance().getSession(ticket);
        if (session == null) {
            throw new RepositoryException("Session has expired. Please log in again.");
        }
        try {
            SaveUtils.safeSaveSession(session);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            throw new RepositoryException(e);
        }
    }

    public void registerCustomProperty(Ticket ticket, CustomPropertyDefinition property)
            throws RepositoryException {

        builder.registerCustomProperty(ticket, property);
    }

    public void unregisterCustomProperty(Ticket ticket, CustomPropertyDefinition property)
            throws RepositoryException {

        builder.unregisterCustomProperty(ticket, property);
    }

    public boolean isPropertyRegistered(Ticket ticket, String propertyName) throws RepositoryException {

        return builder.isPropertyRegistered(ticket, null, propertyName);
    }

    public boolean isPropertyRegistered(Ticket ticket, String uri, String propertyName) throws RepositoryException {

        return builder.isPropertyRegistered(ticket, uri, propertyName);
    }

    public void importRepository(Ticket ticket, String name, InputStream stream)
            throws RepositoryAlreadyExistsException, RepositoryException, SecurityException {

        try {
            importExportModule.importRepository(ticket, name, stream);
        } catch (RepositoryException re) {
            try {
                // In this case we must delete the recently created workspace            
                deleteWorkspace(ticket, name);
                throw re;
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
                // throw previous exception, otherwise it will be confusing
                throw re;
            }
        } catch (SecurityException se) {
            throw se;
        }
    }

    public Node updateContent(Ticket ticket, String nodeId, byte[] content)
            throws SecurityException, RepositoryException {

        ByteArrayInputStream bais = null;
        try {
            bais = new ByteArrayInputStream(content);
            Node updatedNode = updateContent(ticket, nodeId, bais);
            return updatedNode;
        } finally {
            try {
                if (bais != null) {
                    bais.close();
                }
            } catch (IOException e) {
                logger.error(e.getMessage(), e);
                throw new RepositoryException(e);
            }
        }

    }

    public Node updateContent(Ticket ticket, String nodeId, InputStream stream)
            throws SecurityException, RepositoryException {

        try {
            SessionManager manager = SessionManager.getInstance();
            Session session = manager.getSession(ticket);

            javax.jcr.Node node = session.getNodeByUUID(nodeId);
            if (!JCRSecurityService.canWrite(node, ticket.getUser().getId())) {
                throw new SecurityException(SecurityException.NOT_ENOUGH_PERMISSIONS);
            }

            Object syncLock = LockUtility.obtainLock(node);
            synchronized (syncLock) {
                locksModule.checkLockAccess(ticket, node);

                if (ticket.isAutocommit()) {
                    // If commit is disabled we still don't have the node in the repository
                    if (node.isNodeType(JCRConstants.JCR_VERSIONABLE)) {
                        JCRUtils.checkinIfNecessary(node);
                        node.checkout();
                    }
                }

                javax.jcr.Node child = node.getNode(JCRConstants.JCR_CONTENT);
                String path = node.getProperty(JLibraryConstants.JLIBRARY_PATH).getString();
                child.setProperty(JCRConstants.JCR_DATA, stream);
                Calendar lastModified = Calendar.getInstance();
                lastModified.setTimeInMillis(new Date().getTime());
                child.setProperty(JCRConstants.JCR_LAST_MODIFIED, lastModified);

                //TODO: Check how to update size                  
                //node.setProperty(JLibraryConstants.JLIBRARY_SIZE,content.length);
                if (ticket.isAutocommit()) {
                    SaveUtils.safeSaveSession(session);

                    if (node.isNodeType(JCRConstants.JCR_VERSIONABLE)) {
                        // create first version
                        node.checkin();
                        // restore to read-write state
                        node.checkout();
                    }
                }
            }

            javax.jcr.Node root = JCRUtils.getRootNode(session);
            if (node.isNodeType(JLibraryConstants.DOCUMENT_MIXIN)) {
                return JCRAdapter.createDocument(node, node.getParent().getUUID(), root.getUUID());
            } else if (node.isNodeType(JLibraryConstants.RESOURCE_MIXIN)) {
                return JCRAdapter.createResource(node, node.getParent().getUUID(), root.getUUID());
            }
            return null;
        } catch (Throwable e) {
            logger.error(e.getMessage(), e);
            throw new RepositoryException(e);
        }
    }

    public String getJLibraryAPIVersion() {
        // Implementation note: this method should be as simple as possible,
        // so when the callers get the NoSuchMethodException when calling this
        // method, they could be sure that this is because the server API is
        // old and it does not contain this method, and not because of some
        // induced exception.
        return JLibraryConstants.VERSION_1_2;
    }
}