JcrManager.java

/*
 * The coLAB project
 * Copyright (C) 2021-2023 AlbaSim, MEI, HEIG-VD, HES-SO
 *
 * Licensed under the MIT License
 */
package ch.colabproject.colab.api.persistence.jcr;

import ch.colabproject.colab.api.model.project.Project;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import javax.ejb.Stateless;
import javax.inject.Inject;
import javax.jcr.Binary;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Manages the persistence of files with JackRabbit Oak
 *
 * @author xaviergood
 */
@Stateless
public class JcrManager {

    /**
     * Session manager
     */
    @Inject
    private JcrSessionManager jcrSessionManager;

    /**
     * Key of node property containing file contents
     */
    private static final String CONTENT = "COLAB_CONTENT";

    /**
     * Logger
     */
    private static final Logger logger = LoggerFactory.getLogger(JcrManager.class);

    /**
     * @param project    related project
     * @param identifier document id
     *
     * @return True if the node exists
     *
     * @throws RepositoryException in case of JCR problem
     */
    public boolean nodeExists(Project project, Long identifier) throws RepositoryException {
        var session = this.jcrSessionManager.getSession(project);

        return session.nodeExists(identifier.toString());
    }

    /**
     * @param project    related project
     * @param identifier document id
     *
     * @return stream to the file
     *
     * @throws javax.jcr.RepositoryException in case of JCR problem
     */
    public InputStream getFileStream(Project project, Long identifier) throws RepositoryException {
        var session = this.jcrSessionManager.getSession(project);

        var node = session.getNode(identifier.toString());
        if (node == null) {
            return new ByteArrayInputStream(new byte[0]);
        }

        var prop = node.getProperty(CONTENT);

        // TODO figure out when to call dispose
        return prop.getBinary().getStream();
    }

    /**
     * @param project     project related to file
     * @param identifier  document id
     * @param fileContent file content
     *
     * @throws RepositoryException in case of JCR problem
     */
    public void updateOrCreateFile(Project project, Long identifier, InputStream fileContent)
        throws RepositoryException {
        var session = this.jcrSessionManager.getSession(project);

        if (!session.nodeExists(identifier.toString())) {
            createFile(project, identifier, fileContent);
        } else {
            var node = session.getNode(identifier.toString());
            Binary binary = session.createBinary(fileContent);
            node.setProperty(CONTENT, binary);
        }
    }

    /**
     * Creates a node for the given file id, and store its content
     *
     * @param project    related project
     * @param identifier document id
     * @param content    file content
     *
     * @throws RepositoryException in case of JCR problem
     */
    private void createFile(Project project, Long identifier, InputStream content)
        throws RepositoryException {
        var session = this.jcrSessionManager.getSession(project);

        Node root = session.getWorkspaceRoot();
        Node newNode = root.addNode(identifier.toString());

        Binary binary = session.createBinary(content);
        newNode.setProperty(CONTENT, binary);
    }

    /**
     * Deletes any existing node. Call is ignored if the file doesn't exist
     *
     * @param project    related project
     * @param identifier doc id
     *
     * @throws RepositoryException in case of JCR problem
     */
    public void deleteFile(Project project, Long identifier) throws RepositoryException {
        var session = this.jcrSessionManager.getSession(project);
        if (session.nodeExists(identifier.toString())) {
            session.removeNode(identifier.toString());
        }

    }

    /**
     * Computes the disk space used by a project
     *
     * @param project related project
     *
     * @return used memory in bytes
     *
     * @throws RepositoryException in case of JCR problem
     */
    public Long computeMemoryUsage(Project project) throws RepositoryException {

        var session = this.jcrSessionManager.getSession(project);

        var node = session.getWorkspaceRoot();
        if (node == null) {
            return 0L;
        }

        Long total = 0L;

        try {
            var iterator = node.getNodes();
            while (iterator.hasNext()) {
                Node next = iterator.nextNode();
                total += next.getProperty(CONTENT).getLength();// getSize()?
            }

        } catch (RepositoryException re) {
            logger.warn("Could not compute memory usage of project {} id {}. "
                + "Exception : {}",
                project.getName(), project.getId(), re.getMessage());
        }
        return total;
    }
}