JCRRepositoryRule.java

/*
 * Copyright 2015-2024 Brian Thomas Matthews
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.buralotech.oss.jcrunit;

import org.apache.jackrabbit.oak.Oak;
import org.apache.jackrabbit.oak.jcr.Jcr;
import org.junit.rules.ExternalResource;

import javax.jcr.Credentials;
import javax.jcr.Repository;
import javax.jcr.RepositoryException;
import javax.jcr.SimpleCredentials;
import java.io.IOException;
import java.io.InputStream;

/**
 * A JUnit Rule to help test applications that use the Java Content Repository API. This rule creates an in-memory
 * content repository using <a href="https://jackrabbit.apache.org/oak/">Jackrabbit Oak</a>.
 *
 * @author <a href="mailto:brian.matthews@buralo.com">Brian Matthews</a>
 * @since 1.0
 */
public final class JCRRepositoryRule extends ExternalResource {

    /**
     * The credentials used to authenticate when connecting to the repository.
     */
    private final Credentials credentials;

    /**
     * The JCR repository helper.
     */
    private JCRRepositoryTester repositoryHelper;

    /**
     * Private constructor to initialise the rule state with the credentials.
     *
     * @param credentials The credentials.
     */
    private JCRRepositoryRule(final Credentials credentials) {
        this.credentials = credentials;
    }

    /**
     * Factory method to create a {@link JCRRepositoryRule} with default credentials.
     *
     * @return The {@link JCRRepositoryRule}.
     */
    public static JCRRepositoryRule withDefaultCredentials() {
        return withCredentials("admin", "admin");
    }

    /**
     * Factory method to create a {@link JCRRepositoryRule} with username and password credentials.
     *
     * @param username The username.
     * @param password The password.
     * @return The {@link JCRRepositoryRule}.
     */
    public static JCRRepositoryRule withCredentials(final String username,
                                                    final String password) {
        return withCredentials(username, password.toCharArray());
    }

    /**
     * Factory method to create a {@link JCRRepositoryRule} with username and password credentials.
     *
     * @param username The username.
     * @param password The password.
     * @return The {@link JCRRepositoryRule}.
     */
    public static JCRRepositoryRule withCredentials(final String username,
                                                    final char[] password) {
        return withCredentials(new SimpleCredentials(username, password));
    }

    /**
     * Factory method to create a {@link JCRRepositoryRule} with custom credentials.
     *
     * @param credentials The custom credentials.
     * @return The {@link JCRRepositoryRule}.
     */
    public static JCRRepositoryRule withCredentials(final Credentials credentials) {
        return new JCRRepositoryRule(credentials);
    }

    /**
     * Invoked by JUnit before the test case is run and is responsible for instantiating the in-memory JCR
     * repository.
     */
    @Override
    public void before() {
        repositoryHelper = new JCRRepositoryTester(new Jcr(new Oak()).createRepository(), credentials);
    }

    /**
     * Invoked by JUnit after test case has completed and is responsible for resetting the rule allowing the
     * in-memory JCR repository to be garbage collected.
     */
    @Override
    public void after() {
        repositoryHelper = null;
    }

    /**
     * Return the JCR repository.
     *
     * @return The JCR repository.
     */
    public Repository getRepository() {
        return repositoryHelper.getRepository();
    }

    /**
     * Create a top-level folder in the repository.
     *
     * @param name The name of the new top-level folder to be created.
     * @return A reference to <code>this</code> to allow fluent-style chaining of invocations.
     */
    public JCRRepositoryRule createRootFolder(final String name)
            throws RepositoryException {
        repositoryHelper.createRootFolder(name);
        return this;
    }

    /**
     * Create a sub-folder in the repository.
     *
     * @param path The fully qualified path of the parent folder.
     * @param name The name of the new sub-folder to be created.
     * @return A reference to <code>this</code> to allow fluent-style chaining of invocations.
     */
    public JCRRepositoryRule createFolder(final String path,
                                          final String name)
            throws RepositoryException {
        repositoryHelper.createFolder(path, name);
        return this;
    }

    /**
     * Create a file in the repository.
     *
     * @param path     The fully qualified path of the parent folder.
     * @param name     The name of the file to be created.
     * @param type     The content type of the file.
     * @param encoding The content encoding of the file.
     * @param data     The binary content of the file.
     * @return A reference to <code>this</code> to allow fluent-style chaining of invocations.
     */
    public JCRRepositoryRule createFile(final String path,
                                        final String name,
                                        final String type,
                                        final String encoding,
                                        final byte[] data)
            throws IOException, RepositoryException {
        repositoryHelper.createFile(path, name, type, encoding, data);
        return this;
    }

    /**
     * Create a file in the repository.
     *
     * @param path     The fully qualified path of the parent folder.
     * @param name     The name of the file to be created.
     * @param type     The content type of the file.
     * @param encoding The content encoding of the file.
     * @param data     The string content of the file.
     * @return A reference to <code>this</code> to allow fluent-style chaining of invocations.
     */
    public JCRRepositoryRule createFile(final String path,
                                        final String name,
                                        final String type,
                                        final String encoding,
                                        final String data)
            throws IOException, RepositoryException {
        repositoryHelper.createFile(path, name, type, encoding, data);
        return this;
    }

    /**
     * Create a file in the repository.
     *
     * @param path        The fully qualified path of the parent folder.
     * @param name        The name of the file to be created.
     * @param type        The content type of the file.
     * @param encoding    The content encoding of the file.
     * @param inputStream The input stream that provides the binary content of the file.
     * @return A reference to <code>this</code> to allow fluent-style chaining of invocations.
     */
    public JCRRepositoryRule createFile(final String path,
                                        final String name,
                                        final String type,
                                        final String encoding,
                                        final InputStream inputStream)
            throws RepositoryException {
        repositoryHelper.createFile(path, name, type, encoding, inputStream);
        return this;
    }

    /**
     * Assert that a folder exists in the repository.
     *
     * @param path The fully qualified path of the folder.
     * @return A reference to <code>this</code> to allow fluent-style chaining of invocations.
     */
    public JCRRepositoryRule assertFolderExists(final String path) {
        repositoryHelper.assertFolderExists(path);
        return this;
    }

    /**
     * Assert that a file exists in the repository.
     *
     * @param path The fully qualified path of the file.
     * @return A reference to <code>this</code> to allow fluent-style chaining of invocations.
     */
    public JCRRepositoryRule assertFileExists(final String path) {
        repositoryHelper.assertFileExists(path);
        return this;
    }

    /**
     * Import file and folder nodes into the repository with from an XML resource on the class path.
     *
     * @param path The path of the XML resource on the class path.
     * @return A reference to <code>this</code> to allow fluent-style chaining of invocations.
     * @throws IOException         If there was a problem reading the XML resource.
     * @throws RepositoryException If there was a problem importing the XML resource.
     */
    public JCRRepositoryRule importFromXML(final String path) throws IOException, RepositoryException {
        repositoryHelper.importFromXML(path);
        return this;
    }

    /**
     * Import file and folder nodes into the repository with from an XML resource loaded from an input stream.
     *
     * @param inputStream The input stream from which the XML resource can be loaded.
     * @return A reference to <code>this</code> to allow fluent-style chaining of invocations.
     * @throws IOException         If there was a problem reading the XML resource.
     * @throws RepositoryException If there was a problem importing the XML resource.
     */
    public JCRRepositoryRule importFromXML(final InputStream inputStream) throws IOException, RepositoryException {
        repositoryHelper.importFromXML(inputStream);
        return this;
    }

    /**
     * Import file and folder nodes into the repository with from an XML resource loaded from an input stream using
     * the specified credentials.
     *
     * @param credentials The credentials to use for the operation.
     * @param inputStream The input stream from which the XML resource can be loaded.
     * @return A reference to <code>this</code> to allow fluent-style chaining of invocations.
     * @throws IOException         If there was a problem reading the XML resource.
     * @throws RepositoryException If there was a problem importing the XML resource.
     */
    public JCRRepositoryRule importFromXML(final Credentials credentials, final InputStream inputStream)
            throws IOException, RepositoryException {
        repositoryHelper.importFromXML(credentials, inputStream);
        return this;
    }
}