org.fcrepo.apix.integration.KarafIT.java Source code

Java tutorial

Introduction

Here is the source code for org.fcrepo.apix.integration.KarafIT.java

Source

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

package org.fcrepo.apix.integration;

import static org.apache.commons.io.FilenameUtils.getBaseName;
import static org.ops4j.pax.exam.CoreOptions.maven;
import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.editConfigurationFilePut;
import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.features;
import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.karafDistributionConfiguration;
import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.keepRuntimeFolder;
import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.logLevel;
import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.replaceConfigurationFile;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;

import org.fcrepo.apix.model.WebResource;
import org.fcrepo.client.FcrepoClient;
import org.fcrepo.client.FcrepoOperationFailedException;
import org.fcrepo.client.FcrepoResponse;

import org.apache.commons.io.FilenameUtils;
import org.apache.http.HttpStatus;
import org.ops4j.pax.exam.Configuration;
import org.ops4j.pax.exam.ConfigurationManager;
import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.karaf.options.LogLevelOption.LogLevel;
import org.ops4j.pax.exam.options.MavenArtifactUrlReference;
import org.ops4j.pax.exam.options.MavenUrlReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Base Karaf + Pax Exam boilerplace.
 * <p>
 * This is an interface, in case the test needs to inherit some base test class.
 * </p>
 *
 * @author apb@jhu.edu
 */
public interface KarafIT {

    Logger _log = LoggerFactory.getLogger(KarafIT.class);

    String apixBaseURI = String.format("http://localhost:%s",
            System.getProperty("apix.dynamic.test.port", "32080"));

    String fcrepoBaseURI = String.format("http://localhost:%s/%s/rest/",
            System.getProperty("fcrepo.dynamic.test.port", "8080"), System.getProperty("fcrepo.cxtPath", "fcrepo"));

    File testResources = new File(System.getProperty("project.basedir"), "src/test/resources");

    FcrepoClient client = FcrepoClient.client().throwExceptionOnFailure().credentials("fedoraAdmin", "secret3")
            .build();

    URI testContainer = URI.create(System.getProperty("test.container", ""));

    URI objectContainer = URI.create(testContainer + "/objects");

    URI extensionContainer = URI.create(System.getProperty("registry.extension.container", ""));

    URI serviceContainer = URI.create(System.getProperty("registry.service.container", ""));

    URI ontologyContainer = URI.create(System.getProperty("registry.ontology.container", ""));

    /**
     * Karaf config.
     *
     * @return Karaf config.
     */
    // What really sucks about pax exam is that this is called *ONLY ONCE, EVER*,
    // yet Karaf is entirely re-deployed from scratch *EVERY TEST*. There is no straightforward
    // way to configure separate service, ontology, or extension registry containers on a per-test basis.
    // Instead, the containers persist (and are shared) between tests. Had this been invoked every time
    // Karaf is configured and deployed, life would be much, much easier.
    @Configuration
    public default Option[] config() {
        final MavenArtifactUrlReference karafUrl = maven().groupId("org.apache.karaf").artifactId("apache-karaf")
                .version(karafVersion()).type("zip");

        final MavenUrlReference apixRepo = maven().groupId("org.fcrepo.apix").artifactId("fcrepo-api-x-karaf")
                .versionAsInProject().classifier("features").type("xml");

        final MavenArtifactUrlReference testBundle = maven().groupId("org.fcrepo.apix")
                .artifactId("fcrepo-api-x-test").versionAsInProject();

        // This dependency is not in any features files, so we have to add it manually.
        final MavenArtifactUrlReference fcrepoClient = maven().groupId("org.fcrepo.client")
                .artifactId("fcrepo-java-client").versionAsInProject();

        final String container = String.format("%s%s", fcrepoBaseURI, testClassName());

        final ArrayList<Option> options = new ArrayList<>();

        final Option[] defaultOptions = new Option[] {

                // Fcrepo client is not a dependency of anything else, but tests need it.
                // As this test runs as a maven bundle in Karaf, the test's reactor dependencies are not
                // available a priori.
                mavenBundle(fcrepoClient),

                karafDistributionConfiguration().frameworkUrl(karafUrl).unpackDirectory(new File("target", "exam"))
                        .useDeployFolder(false),
                // KarafDistributionOption.configureConsole().ignoreLocalConsole(),
                logLevel(LogLevel.WARN),

                // KarafDistributionOption.debugConfiguration("5005", true),

                keepRuntimeFolder(),

                mavenBundle(testBundle), features(apixRepo, "fcrepo-api-x-model"),
                features(apixRepo, "fcrepo-api-x-registry"), features(apixRepo, "fcrepo-api-x-jena"),
                features(apixRepo, "fcrepo-api-x-binding"), features(apixRepo, "fcrepo-api-x-execution"),
                features(apixRepo, "fcrepo-api-x-routing"), features(apixRepo, "fcrepo-api-x-ontology"),

                // We need to tell Karaf to set any system properties we need.
                // This code is run prior to executing Karaf, the tests themselves are run in Karaf, in a separate
                // VM.
                editConfigurationFilePut("etc/system.properties", "apix.dynamic.test.port",
                        System.getProperty("apix.dynamic.test.port")),
                editConfigurationFilePut("etc/system.properties", "fcrepo.dynamic.test.port",
                        System.getProperty("fcrepo.dynamic.test.port")),
                editConfigurationFilePut("etc/system.properties", "loader.dynamic.test.port",
                        System.getProperty("loader.dynamic.test.port")),
                editConfigurationFilePut("etc/system.properties", "services.dynamic.test.port",
                        System.getProperty("services.dynamic.test.port")),
                editConfigurationFilePut("etc/system.properties", "fcrepo.dynamic.jms.port",
                        System.getProperty("fcrepo.dynamic.jms.port")),
                editConfigurationFilePut("etc/system.properties", "project.basedir",
                        System.getProperty("project.basedir")),
                editConfigurationFilePut("/etc/system.properties", "fcrepo.cxtPath",
                        System.getProperty("fcrepo.cxtPath")),
                editConfigurationFilePut("/etc/system.properties", "test.container", container),
                editConfigurationFilePut("/etc/system.properties", "registry.extension.container",
                        container + "/extensions"),
                editConfigurationFilePut("/etc/system.properties", "registry.service.container",
                        container + "/services"),
                editConfigurationFilePut("/etc/system.properties", "registry.ontology.container",
                        container + "/ontologies"),

                deployFile("cfg/org.fcrepo.apix.jena.cfg"), deployFile("cfg/org.fcrepo.apix.registry.http.cfg"),
                deployFile("cfg/org.fcrepo.apix.routing.cfg"), deployFile("cfg/org.fcrepo.apix.listener.cfg"),
                deployFile("cfg/org.fcrepo.apix.loader.cfg"), deployFile("cfg/org.fcrepo.apix.test.cfg") };

        options.addAll(Arrays.asList(defaultOptions));
        options.addAll(additionalKarafConfig());

        return options.toArray(defaultOptions);

    }

    /**
     * Use this to add additional karaf config options, or return an empty array for none
     *
     * @return list of Karaf options.
     */
    public default List<Option> additionalKarafConfig() {
        return new ArrayList<>();
    }

    /**
     * The test class name.
     *
     * @return test class name
     */
    public String testClassName();

    /**
     * The test method name.
     *
     * @return Test method name.
     */
    public String testMethodName();

    /**
     * Karaf version.
     *
     * @return
     */
    public static String karafVersion() {
        final ConfigurationManager cm = new ConfigurationManager();
        final String karafVersion = cm.getProperty("pax.exam.karaf.version", "4.0.0");
        return karafVersion;
    }

    /**
     * Deploy a configuration with the given classpath.
     *
     * @param path the path in the classpath.
     * @return
     */
    public default Option deployFile(String path) {
        try {
            return replaceConfigurationFile("etc/" + new File(path).getName(),
                    new File("target/test-classes", path));
        } catch (final Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Get a test resource from test-classes
     *
     * @param path the resource path relative to {@link #testResources}
     * @return the resulting WebResource
     */
    public default WebResource testResource(String path) {
        return testResource(path, "text/turtle");
    }

    /**
     * Get a test resource from test-classes
     *
     * @param path the resource path relative to {@link #testResources}
     * @return the resulting WebResource
     */
    public default WebResource testResource(String path, String contentType) {
        final File file = new File(testResources, path);
        try {
            return WebResource.of(new FileInputStream(file), contentType,
                    URI.create(FilenameUtils.getBaseName(path)), null);
        } catch (final Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Post from test resource in the classpath
     *
     * @param filePath Path in the classpath
     * @param intoContainer Container to deposit into.
     * @return URI of new container.
     * @throws Exception if fail.
     */
    public default URI postFromTestResource(final String filePath, final URI intoContainer) throws Exception {

        try (final WebResource object = testResource(filePath);
                final FcrepoResponse response = client.post(intoContainer)
                        .body(object.representation(), object.contentType())
                        .slug(String.format("%s_%s", testMethodName(), getBaseName(filePath))).perform()) {
            return response.getLocation();
        }
    }

    /**
     * POST from a test resource in the classpath.
     *
     * @param filePath path in the classpath.
     * @param intoContainer container URI
     * @param contentType mime type
     * @return URI of created resource
     * @throws Exception if fail.
     */
    public default URI postFromTestResource(final String filePath, final URI intoContainer,
            final String contentType) throws Exception {
        return postFromTestResource(filePath, intoContainer, contentType,
                String.format("%s_%s", testMethodName(), getBaseName(filePath)));
    }

    /**
     * POST content into a container from the classpath.
     *
     * @param filePath path in the classpath
     * @param intoContainer container URI
     * @param contentType mime type
     * @param slug slug name
     * @return URI of created resource
     * @throws Exception if fail.
     */
    public default URI postFromTestResource(final String filePath, final URI intoContainer,
            final String contentType, final String slug) throws Exception {
        try (final WebResource object = testResource(filePath, contentType);
                final FcrepoResponse response = client.post(intoContainer)
                        .body(object.representation(), object.contentType()).slug(slug).perform()) {
            return response.getLocation();
        }
    }

    /**
     * POST content into a container from a stream
     *
     * @param in The stream
     * @param intoContainer The container URI
     * @param contentType mime type
     * @param slug Slug name
     * @return URI of created resource
     * @throws Exception if fail
     */
    public default URI postFromStream(final InputStream in, final URI intoContainer, final String contentType,
            final String slug) throws Exception {
        try (final WebResource object = WebResource.of(in, contentType);
                final FcrepoResponse response = client.post(intoContainer)
                        .body(object.representation(), object.contentType()).slug(slug).perform()) {
            return response.getLocation();
        }
    }

    /**
     * Attempt a task a given number of times.
     *
     * @param times number of times
     * @param it the task
     * @return the result.
     */
    public static <T> T attempt(final int times, final Callable<T> it) {

        Throwable caught = null;

        for (int tries = 0; tries < times; tries++) {
            try {
                return it.call();
            } catch (final Throwable e) {
                caught = e;
                try {
                    Thread.sleep(1000);
                    System.out.println(".");
                } catch (final InterruptedException i) {
                    Thread.currentThread().interrupt();
                    return null;
                }
            }
        }
        throw new RuntimeException("Failed executing task", caught);
    }

    /**
     * Create all necessary containers for registries, etc.
     * <p>
     * Tests that need functional registries need to do this <code>@BeforeClass</code>, or deploy alternate
     * configuration files for jena registry impls.
     * </p>
     *
     * @throws Exception when something goes wrong
     */
    public static void createContainers() throws Exception {

        for (final URI container : Arrays.asList(testContainer, objectContainer, extensionContainer,
                serviceContainer, ontologyContainer)) {
            // Add the container, if it doesn't exist.

            attempt(60, () -> {
                try (FcrepoResponse head = client.head(container).perform()) {
                    return true;
                } catch (final FcrepoOperationFailedException e) {
                    if (e.getStatusCode() == HttpStatus.SC_NOT_FOUND) {
                        try (FcrepoResponse response = client.put(container).perform()) {
                            if (response.getStatusCode() != HttpStatus.SC_CREATED
                                    && response.getStatusCode() != HttpStatus.SC_NO_CONTENT) {
                                _log.info("Could not create container {}, retrying...", container);
                                try {
                                    Thread.sleep(1000);
                                } catch (final InterruptedException i) {
                                    Thread.currentThread().interrupt();
                                }
                            }
                        }
                    }
                }
                return true;
            });
        }
    }
}