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

Java tutorial

Introduction

Here is the source code for org.fcrepo.apix.integration.LoaderIT.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.fcrepo.apix.integration.KarafIT.attempt;
import static org.fcrepo.apix.jena.Util.ltriple;
import static org.fcrepo.apix.jena.Util.parse;
import static org.fcrepo.apix.jena.Util.query;
import static org.fcrepo.apix.jena.Util.triple;
import static org.fcrepo.apix.model.Ontologies.RDF_TYPE;
import static org.fcrepo.apix.model.Ontologies.Apix.CLASS_EXTENSION;
import static org.fcrepo.apix.model.Ontologies.Apix.PROP_EXPOSES_SERVICE;
import static org.fcrepo.apix.model.Ontologies.Apix.PROP_EXPOSES_SERVICE_AT;
import static org.fcrepo.apix.model.Ontologies.Service.CLASS_SERVICE_INSTANCE;
import static org.fcrepo.apix.model.Ontologies.Service.PROP_HAS_ENDPOINT;
import static org.fcrepo.apix.model.Ontologies.Service.PROP_IS_SERVICE_INSTANCE_OF;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
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.features;

import java.io.ByteArrayInputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

import javax.inject.Inject;

import org.fcrepo.apix.model.Service;
import org.fcrepo.apix.model.WebResource;
import org.fcrepo.apix.model.components.Registry;
import org.fcrepo.apix.model.components.RoutingFactory;
import org.fcrepo.apix.model.components.ServiceRegistry;
import org.fcrepo.client.FcrepoResponse;

import org.apache.camel.Exchange;
import org.apache.commons.io.IOUtils;
import org.apache.jena.rdf.model.Model;
import org.jsoup.Connection.Method;
import org.jsoup.Connection.Response;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.FormElement;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import org.junit.runner.RunWith;
import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.junit.PaxExam;
import org.ops4j.pax.exam.options.MavenArtifactUrlReference;
import org.ops4j.pax.exam.options.MavenUrlReference;
import org.ops4j.pax.exam.util.Filter;

/**
 * @author apb@jhu.edu
 */
@RunWith(PaxExam.class)
public class LoaderIT extends ServiceBasedTest {

    static final String LOADER_URI = "http://127.0.0.1:" + System.getProperty("loader.dynamic.test.port") + "/load";

    final URI SERVICE_MINIMAL = URI.create("http://example.org/LoaderIT/minimal");

    final URI SERVICE_FULL = URI.create("http://example.org/LoaderIT/full");

    final URI SERVICE_ONT = URI.create("http://example.org/LoaderIT/ont");

    final URI REQUEST_URI = URI.create(apixBaseURI);

    final AtomicReference<Object> optionsResponse = new AtomicReference<>();

    final AtomicReference<Object> serviceResponse = new AtomicReference<>();

    @Rule
    public TestName name = new TestName();

    @Inject
    @Filter("(org.fcrepo.apix.registry.role=default)")
    Registry repository;

    @Inject
    ServiceRegistry serviceRegistry;

    @Override
    public String testClassName() {
        return LoaderIT.class.getSimpleName();
    }

    @Override
    public String testMethodName() {
        return name.getMethodName();
    }

    @Inject
    RoutingFactory routing;

    @BeforeClass
    public static void init() throws Exception {
        KarafIT.createContainers();
    }

    @Override
    public List<Option> additionalKarafConfig() {
        final List<Option> options = new ArrayList<>(super.additionalKarafConfig());

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

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

        options.addAll(Arrays.asList(mavenBundle(jsoup), features(apixRepo, "fcrepo-api-x-loader")));

        return options;
    }

    @Before
    public void setUp() {

        onServiceRequest(ex -> {
            ex.setOut(ex.getIn());
            if ("OPTIONS".equals(ex.getIn().getHeader(Exchange.HTTP_METHOD))) {
                ex.getOut().setBody(optionsResponse.get());
            } else if ("GET".equals(ex.getIn().getHeader(Exchange.HTTP_METHOD))) {
                ex.getOut().setBody(serviceResponse.get());
            } else {
                ex.getOut().setHeader(Exchange.HTTP_RESPONSE_CODE, 405);
            }
        });
    }

    @Test
    public void htmlMinimalTest() throws Exception {

        final String SERVICE_RESPONSE_BODY = "BODY";

        optionsResponse.set(
                IOUtils.toString(testResource("objects/options_LoaderIT_minimal.ttl").representation(), "utf8"));
        serviceResponse.set(SERVICE_RESPONSE_BODY);

        final Document html = attempt(60,
                () -> Jsoup.connect(LOADER_URI).method(Method.GET).timeout(1000).execute().parse());
        final FormElement form = ((FormElement) html.getElementById("uriForm"));
        form.getElementById("uri").val(serviceEndpoint);

        final Response response = form.submit().ignoreHttpErrors(true).followRedirects(false).execute();
        update();

        assertEquals("OPTIONS", requestToService.getHeader(Exchange.HTTP_METHOD));
        assertEquals(303, response.statusCode());
        assertNotNull(response.header("Location"));

        // Verify that extension works!

        // Get the intercept/proxy URI for a fedora container
        final URI container = routing.of(REQUEST_URI).interceptUriFor(objectContainer);

        // Deposit an object into the container
        final URI deposited = client.post(container).slug("LoaderIT_htmlMinimalTest")
                .body(IOUtils.toInputStream("<> a <test:LoaderIT#minimal> .", "utf8"), "text/turtle").perform()
                .getLocation();

        // Get the service discovery document
        final URI discoveryDoc = client.options(deposited).perform().getLinkHeaders("service").get(0);

        // Invoke the "minimal" service, and verify that the response body is as expected
        final String body = attempt(10,
                () -> IOUtils.toString(
                        client.get(serviceEndpoints(discoveryDoc).get(SERVICE_MINIMAL)).perform().getBody(),
                        "utf8"));
        assertEquals(SERVICE_RESPONSE_BODY, body);
    }

    @Test
    public void definedServiceTest() throws Exception {
        final String SERVICE_RESPONSE_BODY = "BODY";

        optionsResponse
                .set(IOUtils.toString(testResource("objects/options_LoaderIT_full.ttl").representation(), "utf8"));
        serviceResponse.set(SERVICE_RESPONSE_BODY);

        // Now add the extension
        attempt(60, () -> textPost(LOADER_URI, serviceEndpoint)).getHeaderValue("Location");
        update();

        // Verify that extension works!

        // Get the intercept/proxy URI for a fedora container
        final URI container = routing.of(REQUEST_URI).interceptUriFor(objectContainer);

        // Deposit an object into the container
        final URI deposited = client.post(container).slug("LoaderIT_definedServiceTest")
                .body(IOUtils.toInputStream("<> a <test:LoaderIT#full> .", "utf8"), "text/turtle").perform()
                .getLocation();

        // Get the service discovery document
        final URI discoveryDoc = client.options(deposited).perform().getLinkHeaders("service").get(0);

        // Invoke the "minimal" service, and verify that the response body is as expected
        final String body = attempt(10, () -> IOUtils.toString(
                client.get(serviceEndpoints(discoveryDoc).get(SERVICE_FULL)).perform().getBody(), "utf8"));
        assertEquals(SERVICE_RESPONSE_BODY, body);

    }

    @Test
    public void ontologyEmbeddedExtensionTest() throws Exception {
        final String SERVICE_RESPONSE_BODY = "BODY";
        final String TEST_TYPE = "test:type";

        optionsResponse
                .set(IOUtils.toString(testResource("objects/options_LoaderIT_ont.ttl").representation(), "utf8"));
        serviceResponse.set(SERVICE_RESPONSE_BODY);

        // Now add the extension
        final String origLocation = attempt(60, () -> textPost(LOADER_URI, serviceEndpoint))
                .getHeaderValue("Location");
        update();

        // Verify that extension works!

        // Get the intercept/proxy URI for a fedora container
        final URI container = routing.of(REQUEST_URI).interceptUriFor(objectContainer);

        // Deposit an object into the container, as a text/plain binary
        final URI deposited = client.post(container).slug("LoaderIT_" + name.getMethodName())
                .body(IOUtils.toInputStream("THIS IS TEXT", "utf8"), "text/plain").perform().getLocation();

        // Get the service discovery document
        final URI discoveryDoc = client.options(deposited).perform().getLinkHeaders("service").get(0);

        // Invoke the "minimal" service, and verify that the response body is as expected
        final String body = attempt(10, () -> IOUtils
                .toString(client.get(serviceEndpoints(discoveryDoc).get(SERVICE_ONT)).perform().getBody(), "utf8"));
        assertEquals(SERVICE_RESPONSE_BODY, body);

        // Now check that we can update it

        // We should be able to re-load it without issue.
        assertEquals(origLocation, textPost(LOADER_URI, serviceEndpoint).getHeaderValue("Location"));

        // Now update it without issue
        optionsResponse.set(optionsResponse.get() + triple("", RDF_TYPE, TEST_TYPE));
        final FcrepoResponse response = textPost(LOADER_URI, serviceEndpoint);
        assertEquals(origLocation, response.getHeaderValue("Location"));

        // Now assure that our new triple is present in its representation
        IOUtils.toString(response.getBody(), "utf8").contains(TEST_TYPE);
    }

    @Test
    public void extensionUpdateTest() throws Exception {

        final String TEST_TYPE = "test:type";
        final String SERVICE_CANONICAL = "test:" + name.getMethodName();
        final URI SERVICE_CANONICAL_URI = URI.create(SERVICE_CANONICAL);
        final String EXPOSED_AT = SERVICE_CANONICAL;

        optionsResponse.set(triple("", RDF_TYPE, CLASS_EXTENSION) + ltriple("", PROP_EXPOSES_SERVICE_AT, EXPOSED_AT)
                + triple("", PROP_EXPOSES_SERVICE, SERVICE_CANONICAL));

        // Now add the extension twice, making note of its URI
        final String uri = attempt(60, () -> textPost(LOADER_URI, serviceEndpoint)).getHeaderValue("Location");
        update();

        // First, force an update so that we're not at the whim of the asynchronous updater. Then load the extension
        // for the second time.
        update();
        textPost(LOADER_URI, serviceEndpoint);

        // Now alter the content of the available extension doc, the loader should slurp up the
        // changes
        optionsResponse.set(optionsResponse.get() + triple("", RDF_TYPE, TEST_TYPE));
        final FcrepoResponse response = textPost(LOADER_URI, serviceEndpoint);

        // Make sure the extension document URI didn't change (i.e. we're working with one
        // persisted extension doc, rather than creating a new one each time.
        assertEquals(uri, response.getHeaderValue("Location"));

        // Now, verify that the extension doc has our new statement
        IOUtils.toString(response.getBody(), "utf8").contains(TEST_TYPE);

        update();

        // Make sure we only have ONE entry in the service registry for our service
        assertEquals(1, countServiceRegistryEntries(SERVICE_CANONICAL_URI));
    }

    private final FcrepoResponse textPost(final String uri, final String content) throws Exception {

        final String body = content;

        return client.post(URI.create(uri)).body(new ByteArrayInputStream(body.getBytes()), "text/plain").perform();
    }

    private long countServiceRegistryEntries(final URI canonicalURI) {
        return serviceRegistry.list().stream().map(serviceRegistry::getService).map(Service::canonicalURI)
                .filter(u -> canonicalURI.equals(u)).count();
    }

    private Map<URI, URI> serviceEndpoints(final URI discoveryDoc) throws Exception {
        try (WebResource resource = repository.get(discoveryDoc)) {
            final Model doc = parse(resource);

            final String sparql = "CONSTRUCT { ?endpoint <test:/endpointFor> ?service . } WHERE { "
                    + String.format("?serviceInstance <%s> <%s> . ", RDF_TYPE, CLASS_SERVICE_INSTANCE)
                    + String.format("?serviceInstance <%s> ?service . ", PROP_IS_SERVICE_INSTANCE_OF)
                    + String.format("?serviceInstance <%s> ?endpoint . ", PROP_HAS_ENDPOINT) + "}";

            return query(sparql, doc).collect(Collectors.toMap(t -> URI.create(t.getObject().getURI()),
                    t -> URI.create(t.getSubject().getURI())));
        }
    }
}